Move binders out of the main repo
Fixes spring-cloud/spring-cloud-stream#546 Fixing merge conflicts
This commit is contained in:
committed by
Soby Chacko
parent
daf454410c
commit
97f4b0f5f4
2
pom.xml
2
pom.xml
@@ -35,7 +35,7 @@
|
||||
<modules>
|
||||
<module>spring-cloud-stream-dependencies</module>
|
||||
<module>spring-cloud-stream</module>
|
||||
<module>spring-cloud-stream-binders</module>
|
||||
<module>spring-cloud-stream-binder-test</module>
|
||||
<module>spring-cloud-stream-codec</module>
|
||||
<module>spring-cloud-stream-starters</module>
|
||||
<module>spring-cloud-stream-rxjava</module>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-binders-parent</artifactId>
|
||||
<artifactId>spring-cloud-stream-parent</artifactId>
|
||||
<version>1.1.0.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>spring-cloud-stream-binders-parent</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<url>https://github.com/spring-cloud/spring-cloud-stream</url>
|
||||
<organization>
|
||||
<name>Pivotal Software, Inc.</name>
|
||||
<url>http://www.spring.io</url>
|
||||
</organization>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-parent</artifactId>
|
||||
<version>1.1.0.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
<module>spring-cloud-stream-binder-kafka</module>
|
||||
<module>spring-cloud-stream-binder-rabbit</module>
|
||||
<module>spring-cloud-stream-binder-test</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,143 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-cloud-stream-binder-kafka</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>spring-cloud-stream-binder-kafka</name>
|
||||
<description>Kafka binder implementation</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-binders-parent</artifactId>
|
||||
<version>1.1.0.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<kafka.version>0.8.2.2</kafka.version>
|
||||
<curator.version>2.6.0</curator.version>
|
||||
<spring-integration-kafka.version>1.3.1.RELEASE</spring-integration-kafka.version>
|
||||
<rxjava-math.version>1.0.0</rxjava-math.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-codec</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-binder-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-test-support-internal</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-kafka</artifactId>
|
||||
<version>${spring-integration-kafka.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.avro</groupId>
|
||||
<artifactId>avro-compiler</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.kafka</groupId>
|
||||
<artifactId>kafka_2.10</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.kafka</groupId>
|
||||
<artifactId>kafka-clients</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.reactivex</groupId>
|
||||
<artifactId>rxjava</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.reactivex</groupId>
|
||||
<artifactId>rxjava-math</artifactId>
|
||||
<version>${rxjava-math.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-recipes</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.kafka</groupId>
|
||||
<artifactId>kafka_2.10</artifactId>
|
||||
<classifier>test</classifier>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.kafka</groupId>
|
||||
<artifactId>kafka_2.10</artifactId>
|
||||
<version>${kafka.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>log4j</groupId>
|
||||
<artifactId>log4j</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.kafka</groupId>
|
||||
<artifactId>kafka_2.10</artifactId>
|
||||
<classifier>test</classifier>
|
||||
<version>${kafka.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.kafka</groupId>
|
||||
<artifactId>kafka-clients</artifactId>
|
||||
<version>${kafka.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-framework</artifactId>
|
||||
<version>${curator.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-recipes</artifactId>
|
||||
<version>${curator.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-test</artifactId>
|
||||
<version>${curator.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
</project>
|
||||
@@ -1,95 +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;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import kafka.cluster.Broker;
|
||||
import kafka.utils.ZKStringSerializer$;
|
||||
import kafka.utils.ZkUtils$;
|
||||
import org.I0Itec.zkclient.ZkClient;
|
||||
import scala.collection.JavaConversions;
|
||||
import scala.collection.Seq;
|
||||
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
import org.springframework.cloud.stream.binder.kafka.config.KafkaBinderConfigurationProperties;
|
||||
import org.springframework.integration.kafka.core.BrokerAddress;
|
||||
import org.springframework.integration.kafka.core.Partition;
|
||||
|
||||
/**
|
||||
* Health indicator for Kafka.
|
||||
*
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
public class KafkaBinderHealthIndicator implements HealthIndicator {
|
||||
|
||||
private final KafkaMessageChannelBinder binder;
|
||||
|
||||
private final KafkaBinderConfigurationProperties configurationProperties;
|
||||
|
||||
public KafkaBinderHealthIndicator(KafkaMessageChannelBinder binder,
|
||||
KafkaBinderConfigurationProperties configurationProperties) {
|
||||
this.binder = binder;
|
||||
this.configurationProperties = configurationProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Health health() {
|
||||
ZkClient zkClient = null;
|
||||
try {
|
||||
zkClient = new ZkClient(configurationProperties.getZkConnectionString(),
|
||||
configurationProperties.getZkSessionTimeout(),
|
||||
configurationProperties.getZkConnectionTimeout(), ZKStringSerializer$.MODULE$);
|
||||
Set<String> brokersInClusterSet = new HashSet<>();
|
||||
Seq<Broker> allBrokersInCluster = ZkUtils$.MODULE$.getAllBrokersInCluster(zkClient);
|
||||
Collection<Broker> brokersInCluster = JavaConversions.asJavaCollection(allBrokersInCluster);
|
||||
for (Broker broker : brokersInCluster) {
|
||||
brokersInClusterSet.add(broker.connectionString());
|
||||
}
|
||||
Set<String> downMessages = new HashSet<>();
|
||||
for (Map.Entry<String, Collection<Partition>> entry : binder.getTopicsInUse().entrySet()) {
|
||||
for (Partition partition : entry.getValue()) {
|
||||
BrokerAddress address = binder.getConnectionFactory().getLeader(partition);
|
||||
if (!brokersInClusterSet.contains(address.toString())) {
|
||||
downMessages.add(address.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (downMessages.isEmpty()) {
|
||||
return Health.up().build();
|
||||
}
|
||||
return Health.down().withDetail("Following brokers are down: ", downMessages.toString()).build();
|
||||
}
|
||||
catch (Exception e) {
|
||||
return Health.down(e).build();
|
||||
}
|
||||
finally {
|
||||
if (zkClient != null) {
|
||||
try {
|
||||
zkClient.close();
|
||||
}
|
||||
catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +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;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class KafkaBindingProperties {
|
||||
|
||||
private KafkaConsumerProperties consumer = new KafkaConsumerProperties();
|
||||
|
||||
private KafkaProducerProperties producer = new KafkaProducerProperties();
|
||||
|
||||
public KafkaConsumerProperties getConsumer() {
|
||||
return consumer;
|
||||
}
|
||||
|
||||
public void setConsumer(KafkaConsumerProperties consumer) {
|
||||
this.consumer = consumer;
|
||||
}
|
||||
|
||||
public KafkaProducerProperties getProducer() {
|
||||
return producer;
|
||||
}
|
||||
|
||||
public void setProducer(KafkaProducerProperties producer) {
|
||||
this.producer = producer;
|
||||
}
|
||||
}
|
||||
@@ -1,83 +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;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class KafkaConsumerProperties {
|
||||
|
||||
private boolean autoCommitOffset = true;
|
||||
|
||||
private Boolean autoCommitOnError;
|
||||
|
||||
private boolean resetOffsets;
|
||||
|
||||
private KafkaMessageChannelBinder.StartOffset startOffset;
|
||||
|
||||
private boolean enableDlq;
|
||||
|
||||
private int recoveryInterval = 5000;
|
||||
|
||||
public boolean isAutoCommitOffset() {
|
||||
return autoCommitOffset;
|
||||
}
|
||||
|
||||
public void setAutoCommitOffset(boolean autoCommitOffset) {
|
||||
this.autoCommitOffset = autoCommitOffset;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public boolean isEnableDlq() {
|
||||
return enableDlq;
|
||||
}
|
||||
|
||||
public void setEnableDlq(boolean enableDlq) {
|
||||
this.enableDlq = enableDlq;
|
||||
}
|
||||
|
||||
public Boolean getAutoCommitOnError() {
|
||||
return autoCommitOnError;
|
||||
}
|
||||
|
||||
public void setAutoCommitOnError(Boolean autoCommitOnError) {
|
||||
this.autoCommitOnError = autoCommitOnError;
|
||||
}
|
||||
|
||||
public int getRecoveryInterval() {
|
||||
return recoveryInterval;
|
||||
}
|
||||
|
||||
public void setRecoveryInterval(int recoveryInterval) {
|
||||
this.recoveryInterval = recoveryInterval;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +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;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.stream.binder.ExtendedBindingProperties;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
@ConfigurationProperties("spring.cloud.stream.kafka")
|
||||
public class KafkaExtendedBindingProperties implements ExtendedBindingProperties<KafkaConsumerProperties, KafkaProducerProperties> {
|
||||
|
||||
private Map<String, KafkaBindingProperties> bindings = new HashMap<>();
|
||||
|
||||
public Map<String, KafkaBindingProperties> getBindings() {
|
||||
return bindings;
|
||||
}
|
||||
|
||||
public void setBindings(Map<String, KafkaBindingProperties> bindings) {
|
||||
this.bindings = bindings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KafkaConsumerProperties getExtendedConsumerProperties(String channelName) {
|
||||
if (bindings.containsKey(channelName) && bindings.get(channelName).getConsumer() != null) {
|
||||
return bindings.get(channelName).getConsumer();
|
||||
}
|
||||
else {
|
||||
return new KafkaConsumerProperties();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public KafkaProducerProperties getExtendedProducerProperties(String channelName) {
|
||||
if (bindings.containsKey(channelName) && bindings.get(channelName).getProducer() != null) {
|
||||
return bindings.get(channelName).getProducer();
|
||||
}
|
||||
else {
|
||||
return new KafkaProducerProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,800 +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.kafka;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import kafka.admin.AdminUtils;
|
||||
import kafka.api.OffsetRequest;
|
||||
import kafka.api.TopicMetadata;
|
||||
import kafka.common.ErrorMapping;
|
||||
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.Callback;
|
||||
import org.apache.kafka.clients.producer.Producer;
|
||||
import org.apache.kafka.clients.producer.ProducerConfig;
|
||||
import org.apache.kafka.clients.producer.ProducerRecord;
|
||||
import org.apache.kafka.clients.producer.RecordMetadata;
|
||||
import org.apache.kafka.common.serialization.ByteArraySerializer;
|
||||
import org.apache.kafka.common.utils.Utils;
|
||||
import scala.collection.Seq;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
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.Binding;
|
||||
import org.springframework.cloud.stream.binder.DefaultBinding;
|
||||
import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.ExtendedProducerProperties;
|
||||
import org.springframework.cloud.stream.binder.ExtendedPropertiesBinder;
|
||||
import org.springframework.cloud.stream.binder.HeaderMode;
|
||||
import org.springframework.cloud.stream.binder.MessageValues;
|
||||
import org.springframework.cloud.stream.binder.PartitionHandler;
|
||||
import org.springframework.cloud.stream.binder.kafka.config.KafkaBinderConfigurationProperties;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.integration.channel.FixedSubscriberChannel;
|
||||
import org.springframework.integration.endpoint.EventDrivenConsumer;
|
||||
import org.springframework.integration.handler.AbstractMessageHandler;
|
||||
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
|
||||
import org.springframework.integration.kafka.core.ConnectionFactory;
|
||||
import org.springframework.integration.kafka.core.DefaultConnectionFactory;
|
||||
import org.springframework.integration.kafka.core.KafkaMessage;
|
||||
import org.springframework.integration.kafka.core.Partition;
|
||||
import org.springframework.integration.kafka.core.ZookeeperConfiguration;
|
||||
import org.springframework.integration.kafka.inbound.KafkaMessageDrivenChannelAdapter;
|
||||
import org.springframework.integration.kafka.listener.AcknowledgingMessageListener;
|
||||
import org.springframework.integration.kafka.listener.Acknowledgment;
|
||||
import org.springframework.integration.kafka.listener.ErrorHandler;
|
||||
import org.springframework.integration.kafka.listener.KafkaMessageListenerContainer;
|
||||
import org.springframework.integration.kafka.listener.KafkaNativeOffsetManager;
|
||||
import org.springframework.integration.kafka.listener.MessageListener;
|
||||
import org.springframework.integration.kafka.listener.OffsetManager;
|
||||
import org.springframework.integration.kafka.support.KafkaHeaders;
|
||||
import org.springframework.integration.kafka.support.ProducerConfiguration;
|
||||
import org.springframework.integration.kafka.support.ProducerFactoryBean;
|
||||
import org.springframework.integration.kafka.support.ProducerListener;
|
||||
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.MessageHeaders;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.retry.RetryCallback;
|
||||
import org.springframework.retry.RetryContext;
|
||||
import org.springframework.retry.RetryOperations;
|
||||
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
|
||||
import org.springframework.retry.policy.SimpleRetryPolicy;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A {@link Binder} that uses Kafka as the underlying middleware.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
* @author Marius Bogoevici
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author David Turanski
|
||||
* @author Gary Russell
|
||||
* @author Mark Fisher
|
||||
* @author Soby Chacko
|
||||
*/
|
||||
public class KafkaMessageChannelBinder extends
|
||||
AbstractBinder<MessageChannel, ExtendedConsumerProperties<KafkaConsumerProperties>, ExtendedProducerProperties<KafkaProducerProperties>>
|
||||
implements ExtendedPropertiesBinder<MessageChannel, KafkaConsumerProperties, KafkaProducerProperties>,
|
||||
DisposableBean {
|
||||
|
||||
public static final ByteArraySerializer BYTE_ARRAY_SERIALIZER = new ByteArraySerializer();
|
||||
|
||||
public static final ThreadFactory DAEMON_THREAD_FACTORY;
|
||||
|
||||
static {
|
||||
CustomizableThreadFactory threadFactory = new CustomizableThreadFactory("kafka-binder-");
|
||||
threadFactory.setDaemon(true);
|
||||
DAEMON_THREAD_FACTORY = threadFactory;
|
||||
}
|
||||
|
||||
private final KafkaBinderConfigurationProperties configurationProperties;
|
||||
|
||||
private final String[] headersToMap;
|
||||
|
||||
private RetryOperations metadataRetryOperations;
|
||||
|
||||
private final Map<String, Collection<Partition>> topicsInUse = new HashMap<>();
|
||||
|
||||
// -------- Default values for properties -------
|
||||
|
||||
private ConnectionFactory connectionFactory;
|
||||
|
||||
private ProducerListener producerListener;
|
||||
|
||||
private volatile Producer<byte[], byte[]> dlqProducer;
|
||||
|
||||
private KafkaExtendedBindingProperties extendedBindingProperties = new KafkaExtendedBindingProperties();
|
||||
|
||||
public KafkaMessageChannelBinder(KafkaBinderConfigurationProperties configurationProperties) {
|
||||
this.configurationProperties = configurationProperties;
|
||||
String[] configuredHeaders = configurationProperties.getHeaders();
|
||||
if (ObjectUtils.isEmpty(configuredHeaders)) {
|
||||
this.headersToMap = BinderHeaders.STANDARD_HEADERS;
|
||||
}
|
||||
else {
|
||||
String[] combinedHeadersToMap = Arrays.copyOfRange(BinderHeaders.STANDARD_HEADERS, 0,
|
||||
BinderHeaders.STANDARD_HEADERS.length + configuredHeaders.length);
|
||||
System.arraycopy(configuredHeaders, 0, combinedHeadersToMap, BinderHeaders.STANDARD_HEADERS.length,
|
||||
configuredHeaders.length);
|
||||
this.headersToMap = combinedHeadersToMap;
|
||||
}
|
||||
}
|
||||
|
||||
String getZkAddress() {
|
||||
return this.configurationProperties.getZkConnectionString();
|
||||
}
|
||||
|
||||
public ConnectionFactory getConnectionFactory() {
|
||||
return connectionFactory;
|
||||
}
|
||||
|
||||
public void setProducerListener(ProducerListener producerListener) {
|
||||
this.producerListener = producerListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry configuration for operations such as validating topic creation
|
||||
* @param metadataRetryOperations the retry configuration
|
||||
*/
|
||||
public void setMetadataRetryOperations(RetryOperations metadataRetryOperations) {
|
||||
this.metadataRetryOperations = metadataRetryOperations;
|
||||
}
|
||||
|
||||
public void setExtendedBindingProperties(KafkaExtendedBindingProperties extendedBindingProperties) {
|
||||
this.extendedBindingProperties = extendedBindingProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInit() throws Exception {
|
||||
ZookeeperConfiguration configuration = new ZookeeperConfiguration(
|
||||
new ZookeeperConnect(configurationProperties.getZkConnectionString()));
|
||||
configuration.setBufferSize(configurationProperties.getSocketBufferSize());
|
||||
configuration.setMaxWait(configurationProperties.getMaxWait());
|
||||
DefaultConnectionFactory defaultConnectionFactory = new DefaultConnectionFactory(configuration);
|
||||
defaultConnectionFactory.afterPropertiesSet();
|
||||
this.connectionFactory = defaultConnectionFactory;
|
||||
if (metadataRetryOperations == null) {
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
|
||||
SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
|
||||
simpleRetryPolicy.setMaxAttempts(10);
|
||||
retryTemplate.setRetryPolicy(simpleRetryPolicy);
|
||||
|
||||
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
|
||||
backOffPolicy.setInitialInterval(100);
|
||||
backOffPolicy.setMultiplier(2);
|
||||
backOffPolicy.setMaxInterval(1000);
|
||||
retryTemplate.setBackOffPolicy(backOffPolicy);
|
||||
metadataRetryOperations = retryTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
if (dlqProducer != null) {
|
||||
dlqProducer.close();
|
||||
dlqProducer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allowed chars are ASCII alphanumerics, '.', '_' and '-'.
|
||||
*/
|
||||
public static void validateTopicName(String topicName) {
|
||||
try {
|
||||
byte[] utf8 = topicName.getBytes("UTF-8");
|
||||
for (byte b : utf8) {
|
||||
if (!((b >= 'a') && (b <= 'z') || (b >= 'A') && (b <= 'Z') || (b >= '0') && (b <= '9') || (b == '.')
|
||||
|| (b == '-') || (b == '_'))) {
|
||||
throw new IllegalArgumentException(
|
||||
"Topic name can only have ASCII alphanumerics, '.', '_' and '-'");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (UnsupportedEncodingException e) {
|
||||
throw new AssertionError(e); // Can't happen
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public KafkaConsumerProperties getExtendedConsumerProperties(String channelName) {
|
||||
return extendedBindingProperties.getExtendedConsumerProperties(channelName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KafkaProducerProperties getExtendedProducerProperties(String channelName) {
|
||||
return extendedBindingProperties.getExtendedProducerProperties(channelName);
|
||||
}
|
||||
|
||||
Map<String, Collection<Partition>> getTopicsInUse() {
|
||||
return this.topicsInUse;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Binding<MessageChannel> doBindConsumer(String name, String group, MessageChannel inputChannel,
|
||||
ExtendedConsumerProperties<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
|
||||
// messages sent after they've been bound. That behavior can be changed with the
|
||||
// "resetOffsets" and "startOffset" properties.
|
||||
boolean anonymous = !StringUtils.hasText(group);
|
||||
Assert.isTrue(!anonymous || !properties.getExtension().isEnableDlq(),
|
||||
"DLQ support is not available for anonymous subscriptions");
|
||||
String consumerGroup = anonymous ? "anonymous." + UUID.randomUUID().toString() : group;
|
||||
// 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 = properties.getExtension().getStartOffset() != null
|
||||
? properties.getExtension().getStartOffset().getReferencePoint()
|
||||
: (anonymous ? OffsetRequest.LatestTime() : OffsetRequest.EarliestTime());
|
||||
return createKafkaConsumer(name, inputChannel, properties, consumerGroup, referencePoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Binding<MessageChannel> doBindProducer(String name, MessageChannel moduleOutputChannel,
|
||||
ExtendedProducerProperties<KafkaProducerProperties> properties) {
|
||||
|
||||
Assert.isInstanceOf(SubscribableChannel.class, moduleOutputChannel);
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Using kafka topic for outbound: " + name);
|
||||
}
|
||||
|
||||
validateTopicName(name);
|
||||
|
||||
Collection<Partition> partitions = ensureTopicCreated(name, properties.getPartitionCount());
|
||||
|
||||
if (properties.getPartitionCount() < partitions.size()) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("The `partitionCount` of the producer for topic " + name + " is "
|
||||
+ properties.getPartitionCount() + ", smaller than the actual partition count of "
|
||||
+ partitions.size() + " of the topic. The larger number will be used instead.");
|
||||
}
|
||||
}
|
||||
|
||||
topicsInUse.put(name, partitions);
|
||||
|
||||
ProducerMetadata<byte[], byte[]> producerMetadata = new ProducerMetadata<>(name, byte[].class, byte[].class,
|
||||
BYTE_ARRAY_SERIALIZER, BYTE_ARRAY_SERIALIZER);
|
||||
producerMetadata.setSync(properties.getExtension().isSync());
|
||||
producerMetadata.setCompressionType(properties.getExtension().getCompressionType());
|
||||
producerMetadata.setBatchBytes(properties.getExtension().getBufferSize());
|
||||
Properties additionalProps = new Properties();
|
||||
additionalProps.put(ProducerConfig.ACKS_CONFIG, String.valueOf(configurationProperties.getRequiredAcks()));
|
||||
additionalProps.put(ProducerConfig.LINGER_MS_CONFIG,
|
||||
String.valueOf(properties.getExtension().getBatchTimeout()));
|
||||
ProducerFactoryBean<byte[], byte[]> producerFB = new ProducerFactoryBean<>(producerMetadata,
|
||||
configurationProperties.getKafkaConnectionString(), additionalProps);
|
||||
|
||||
try {
|
||||
final ProducerConfiguration<byte[], byte[]> producerConfiguration = new ProducerConfiguration<>(
|
||||
producerMetadata, producerFB.getObject());
|
||||
producerConfiguration.setProducerListener(producerListener);
|
||||
|
||||
MessageHandler handler = new SendingHandler(name, properties, partitions.size(), producerConfiguration);
|
||||
EventDrivenConsumer consumer = new EventDrivenConsumer((SubscribableChannel) moduleOutputChannel, handler) {
|
||||
|
||||
@Override
|
||||
protected void doStop() {
|
||||
super.doStop();
|
||||
producerConfiguration.stop();
|
||||
}
|
||||
};
|
||||
consumer.setBeanFactory(this.getBeanFactory());
|
||||
consumer.setBeanName("outbound." + name);
|
||||
consumer.afterPropertiesSet();
|
||||
DefaultBinding<MessageChannel> producerBinding = new DefaultBinding<>(name, null, moduleOutputChannel,
|
||||
consumer);
|
||||
consumer.start();
|
||||
return producerBinding;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Kafka topic if needed, or try to increase its partition count to the
|
||||
* desired number.
|
||||
*/
|
||||
private Collection<Partition> ensureTopicCreated(final String topicName, final int partitionCount) {
|
||||
|
||||
final ZkClient zkClient = new ZkClient(configurationProperties.getZkConnectionString(),
|
||||
configurationProperties.getZkSessionTimeout(), configurationProperties.getZkConnectionTimeout(),
|
||||
ZKStringSerializer$.MODULE$);
|
||||
try {
|
||||
final Properties topicConfig = new Properties();
|
||||
TopicMetadata topicMetadata = AdminUtils.fetchTopicMetadataFromZk(topicName, zkClient);
|
||||
if (topicMetadata.errorCode() == ErrorMapping.NoError()) {
|
||||
// only consider minPartitionCount for resizing if autoAddPartitions is
|
||||
// true
|
||||
int effectivePartitionCount = configurationProperties.isAutoAddPartitions()
|
||||
? Math.max(configurationProperties.getMinPartitionCount(), partitionCount) : partitionCount;
|
||||
if (topicMetadata.partitionsMetadata().size() < effectivePartitionCount) {
|
||||
if (configurationProperties.isAutoAddPartitions()) {
|
||||
AdminUtils.addPartitions(zkClient, topicName, effectivePartitionCount, null, false,
|
||||
new Properties());
|
||||
}
|
||||
else {
|
||||
int topicSize = topicMetadata.partitionsMetadata().size();
|
||||
throw new BinderException("The number of expected partitions was: " + partitionCount + ", but "
|
||||
+ topicSize + (topicSize > 1 ? " have " : " has ") + "been found instead."
|
||||
+ "Consider either increasing the partition count of the topic or enabling `autoAddPartitions`");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (topicMetadata.errorCode() == ErrorMapping.UnknownTopicOrPartitionCode()) {
|
||||
if (configurationProperties.isAutoCreateTopics()) {
|
||||
Seq<Object> brokerList = ZkUtils.getSortedBrokerList(zkClient);
|
||||
// always consider minPartitionCount for topic creation
|
||||
int effectivePartitionCount = Math.max(configurationProperties.getMinPartitionCount(),
|
||||
partitionCount);
|
||||
final scala.collection.Map<Object, Seq<Object>> replicaAssignment = AdminUtils
|
||||
.assignReplicasToBrokers(brokerList, effectivePartitionCount,
|
||||
configurationProperties.getReplicationFactor(), -1, -1);
|
||||
metadataRetryOperations.execute(new RetryCallback<Object, RuntimeException>() {
|
||||
@Override
|
||||
public Object doWithRetry(RetryContext context) throws RuntimeException {
|
||||
AdminUtils.createOrUpdateTopicPartitionAssignmentPathInZK(zkClient, topicName,
|
||||
replicaAssignment, topicConfig, true);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
throw new BinderException("Topic " + topicName + " does not exist");
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new BinderException("Error fetching Kafka topic metadata: ",
|
||||
ErrorMapping.exceptionFor(topicMetadata.errorCode()));
|
||||
}
|
||||
try {
|
||||
Collection<Partition> partitions = metadataRetryOperations
|
||||
.execute(new RetryCallback<Collection<Partition>, Exception>() {
|
||||
|
||||
@Override
|
||||
public Collection<Partition> doWithRetry(RetryContext context) throws Exception {
|
||||
connectionFactory.refreshMetadata(Collections.singleton(topicName));
|
||||
Collection<Partition> partitions = connectionFactory.getPartitions(topicName);
|
||||
// do a sanity check on the partition set
|
||||
if (partitions.size() < partitionCount) {
|
||||
throw new IllegalStateException("The number of expected partitions was: "
|
||||
+ partitionCount + ", but " + partitions.size()
|
||||
+ (partitions.size() > 1 ? " have " : " has ") + "been found instead");
|
||||
}
|
||||
connectionFactory.getLeaders(partitions);
|
||||
return partitions;
|
||||
}
|
||||
});
|
||||
return partitions;
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.error("Cannot initialize Binder", e);
|
||||
throw new BinderException("Cannot initialize binder:", e);
|
||||
}
|
||||
|
||||
}
|
||||
finally {
|
||||
zkClient.close();
|
||||
}
|
||||
}
|
||||
|
||||
private Binding<MessageChannel> createKafkaConsumer(String name, final MessageChannel moduleInputChannel,
|
||||
ExtendedConsumerProperties<KafkaConsumerProperties> properties, String group, long referencePoint) {
|
||||
|
||||
validateTopicName(name);
|
||||
if (properties.getInstanceCount() == 0) {
|
||||
throw new IllegalArgumentException("Instance count cannot be zero");
|
||||
}
|
||||
Collection<Partition> allPartitions = ensureTopicCreated(name,
|
||||
properties.getInstanceCount() * properties.getConcurrency());
|
||||
|
||||
Decoder<byte[]> valueDecoder = new DefaultDecoder(null);
|
||||
Decoder<byte[]> keyDecoder = new DefaultDecoder(null);
|
||||
|
||||
Collection<Partition> listenedPartitions;
|
||||
|
||||
if (properties.getInstanceCount() == 1) {
|
||||
listenedPartitions = allPartitions;
|
||||
}
|
||||
else {
|
||||
listenedPartitions = new ArrayList<>();
|
||||
for (Partition partition : allPartitions) {
|
||||
// divide partitions across modules
|
||||
if ((partition.getId() % properties.getInstanceCount()) == properties.getInstanceIndex()) {
|
||||
listenedPartitions.add(partition);
|
||||
}
|
||||
}
|
||||
}
|
||||
topicsInUse.put(name, listenedPartitions);
|
||||
ReceivingHandler rh = new ReceivingHandler(properties);
|
||||
rh.setOutputChannel(moduleInputChannel);
|
||||
|
||||
final FixedSubscriberChannel bridge = new FixedSubscriberChannel(rh);
|
||||
bridge.setBeanName("bridge." + name);
|
||||
|
||||
Assert.isTrue(!CollectionUtils.isEmpty(listenedPartitions), "A list of partitions must be provided");
|
||||
final KafkaMessageListenerContainer messageListenerContainer = new KafkaMessageListenerContainer(
|
||||
connectionFactory, listenedPartitions.toArray(new Partition[listenedPartitions.size()]));
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Listened partitions: " + StringUtils.collectionToCommaDelimitedString(listenedPartitions));
|
||||
}
|
||||
|
||||
OffsetManager offsetManager = createOffsetManager(group, referencePoint);
|
||||
if (properties.getExtension().isResetOffsets()) {
|
||||
offsetManager.resetOffsets(listenedPartitions);
|
||||
}
|
||||
messageListenerContainer.setOffsetManager(offsetManager);
|
||||
messageListenerContainer.setQueueSize(configurationProperties.getQueueSize());
|
||||
messageListenerContainer.setMaxFetch(configurationProperties.getFetchSize());
|
||||
boolean autoCommitOnError = properties.getExtension().getAutoCommitOnError() != null
|
||||
? properties.getExtension().getAutoCommitOnError()
|
||||
: properties.getExtension().isAutoCommitOffset() && properties.getExtension().isEnableDlq();
|
||||
messageListenerContainer.setAutoCommitOnError(autoCommitOnError);
|
||||
messageListenerContainer.setRecoveryInterval(properties.getExtension().getRecoveryInterval());
|
||||
|
||||
int concurrency = Math.min(properties.getConcurrency(), listenedPartitions.size());
|
||||
messageListenerContainer.setConcurrency(concurrency);
|
||||
final ExecutorService dispatcherTaskExecutor = Executors.newFixedThreadPool(concurrency, DAEMON_THREAD_FACTORY);
|
||||
messageListenerContainer.setDispatcherTaskExecutor(dispatcherTaskExecutor);
|
||||
final KafkaMessageDrivenChannelAdapter kafkaMessageDrivenChannelAdapter = new KafkaMessageDrivenChannelAdapter(
|
||||
messageListenerContainer);
|
||||
kafkaMessageDrivenChannelAdapter.setBeanFactory(this.getBeanFactory());
|
||||
kafkaMessageDrivenChannelAdapter.setKeyDecoder(keyDecoder);
|
||||
kafkaMessageDrivenChannelAdapter.setPayloadDecoder(valueDecoder);
|
||||
kafkaMessageDrivenChannelAdapter.setOutputChannel(bridge);
|
||||
kafkaMessageDrivenChannelAdapter.setAutoCommitOffset(properties.getExtension().isAutoCommitOffset());
|
||||
kafkaMessageDrivenChannelAdapter.afterPropertiesSet();
|
||||
|
||||
// we need to wrap the adapter listener into a retrying listener so that the retry
|
||||
// logic is applied before the ErrorHandler is executed
|
||||
final RetryTemplate retryTemplate = buildRetryTemplateIfRetryEnabled(properties);
|
||||
if (retryTemplate != null) {
|
||||
if (properties.getExtension().isAutoCommitOffset()) {
|
||||
final MessageListener originalMessageListener = (MessageListener) messageListenerContainer
|
||||
.getMessageListener();
|
||||
messageListenerContainer.setMessageListener(new MessageListener() {
|
||||
@Override
|
||||
public void onMessage(final KafkaMessage message) {
|
||||
try {
|
||||
retryTemplate.execute(new RetryCallback<Object, Throwable>() {
|
||||
@Override
|
||||
public Object doWithRetry(RetryContext context) {
|
||||
originalMessageListener.onMessage(message);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Throwable throwable) {
|
||||
if (throwable instanceof RuntimeException) {
|
||||
throw (RuntimeException) throwable;
|
||||
}
|
||||
else {
|
||||
throw new RuntimeException(throwable);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
messageListenerContainer.setMessageListener(new AcknowledgingMessageListener() {
|
||||
final AcknowledgingMessageListener originalMessageListener = (AcknowledgingMessageListener) messageListenerContainer
|
||||
.getMessageListener();
|
||||
|
||||
@Override
|
||||
public void onMessage(final KafkaMessage message, final Acknowledgment acknowledgment) {
|
||||
retryTemplate.execute(new RetryCallback<Object, RuntimeException>() {
|
||||
@Override
|
||||
public Object doWithRetry(RetryContext context) {
|
||||
originalMessageListener.onMessage(message, acknowledgment);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (properties.getExtension().isEnableDlq()) {
|
||||
final String dlqTopic = "error." + name + "." + group;
|
||||
initDlqProducer();
|
||||
messageListenerContainer.setErrorHandler(new ErrorHandler() {
|
||||
@Override
|
||||
public void handle(Exception thrownException, final KafkaMessage message) {
|
||||
final byte[] key = message.getMessage().key() != null ? Utils.toArray(message.getMessage().key())
|
||||
: null;
|
||||
final byte[] payload = message.getMessage().payload() != null
|
||||
? Utils.toArray(message.getMessage().payload()) : null;
|
||||
dlqProducer.send(new ProducerRecord<>(dlqTopic, key, payload), new Callback() {
|
||||
@Override
|
||||
public void onCompletion(RecordMetadata metadata, Exception exception) {
|
||||
StringBuffer messageLog = new StringBuffer();
|
||||
messageLog.append(" a message with key='"
|
||||
+ toDisplayString(ObjectUtils.nullSafeToString(key), 50) + "'");
|
||||
messageLog.append(" and payload='"
|
||||
+ toDisplayString(ObjectUtils.nullSafeToString(payload), 50) + "'");
|
||||
messageLog.append(" received from " + message.getMetadata().getPartition());
|
||||
if (exception != null) {
|
||||
logger.error("Error sending to DLQ" + messageLog.toString(), exception);
|
||||
}
|
||||
else {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Sent to DLQ " + messageLog.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
kafkaMessageDrivenChannelAdapter.start();
|
||||
|
||||
EventDrivenConsumer edc = new EventDrivenConsumer(bridge, rh) {
|
||||
|
||||
@Override
|
||||
protected void doStop() {
|
||||
// stop the offset manager and the channel adapter before unbinding
|
||||
// this means that the upstream channel adapter has a chance to stop
|
||||
kafkaMessageDrivenChannelAdapter.stop();
|
||||
if (messageListenerContainer.getOffsetManager() instanceof DisposableBean) {
|
||||
try {
|
||||
((DisposableBean) messageListenerContainer.getOffsetManager()).destroy();
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.error("Error while closing the offset manager", e);
|
||||
}
|
||||
}
|
||||
super.doStop();
|
||||
}
|
||||
};
|
||||
String groupedName = groupedName(name, group);
|
||||
edc.setBeanName("inbound." + groupedName);
|
||||
|
||||
DefaultBinding<MessageChannel> consumerBinding = new DefaultBinding<MessageChannel>(name, group,
|
||||
moduleInputChannel, edc) {
|
||||
@Override
|
||||
protected void afterUnbind() {
|
||||
dispatcherTaskExecutor.shutdown();
|
||||
}
|
||||
};
|
||||
edc.start();
|
||||
return consumerBinding;
|
||||
}
|
||||
|
||||
private synchronized void initDlqProducer() {
|
||||
try {
|
||||
if (dlqProducer == null) {
|
||||
synchronized (this) {
|
||||
if (dlqProducer == null) {
|
||||
// we can use the producer defaults as we do not need to tune
|
||||
// performance
|
||||
ProducerMetadata<byte[], byte[]> producerMetadata = new ProducerMetadata<>("dlqKafkaProducer",
|
||||
byte[].class, byte[].class, BYTE_ARRAY_SERIALIZER, BYTE_ARRAY_SERIALIZER);
|
||||
producerMetadata.setSync(false);
|
||||
producerMetadata.setCompressionType(ProducerMetadata.CompressionType.none);
|
||||
producerMetadata.setBatchBytes(16384);
|
||||
Properties additionalProps = new Properties();
|
||||
additionalProps.put(ProducerConfig.ACKS_CONFIG,
|
||||
String.valueOf(configurationProperties.getRequiredAcks()));
|
||||
additionalProps.put(ProducerConfig.LINGER_MS_CONFIG, String.valueOf(0));
|
||||
ProducerFactoryBean<byte[], byte[]> producerFactoryBean = new ProducerFactoryBean<>(
|
||||
producerMetadata, configurationProperties.getKafkaConnectionString(), additionalProps);
|
||||
dlqProducer = producerFactoryBean.getObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException("Cannot initialize DLQ producer:", e);
|
||||
}
|
||||
}
|
||||
|
||||
private OffsetManager createOffsetManager(String group, long referencePoint) {
|
||||
try {
|
||||
|
||||
KafkaNativeOffsetManager kafkaOffsetManager = new KafkaNativeOffsetManager(connectionFactory,
|
||||
new ZookeeperConnect(configurationProperties.getZkConnectionString()),
|
||||
Collections.<Partition, Long>emptyMap());
|
||||
kafkaOffsetManager.setConsumerId(group);
|
||||
kafkaOffsetManager.setReferenceTimestamp(referencePoint);
|
||||
kafkaOffsetManager.afterPropertiesSet();
|
||||
|
||||
WindowingOffsetManager windowingOffsetManager = new WindowingOffsetManager(kafkaOffsetManager);
|
||||
windowingOffsetManager.setTimespan(configurationProperties.getOffsetUpdateTimeWindow());
|
||||
windowingOffsetManager.setCount(configurationProperties.getOffsetUpdateCount());
|
||||
windowingOffsetManager.setShutdownTimeout(configurationProperties.getOffsetUpdateShutdownTimeout());
|
||||
|
||||
windowingOffsetManager.afterPropertiesSet();
|
||||
return windowingOffsetManager;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String toDisplayString(String original, int maxCharacters) {
|
||||
if (original.length() <= maxCharacters) {
|
||||
return original;
|
||||
}
|
||||
return original.substring(0, maxCharacters) + "...";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doManualAck(LinkedList<MessageHeaders> messageHeadersList) {
|
||||
Iterator<MessageHeaders> iterator = messageHeadersList.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
MessageHeaders headers = iterator.next();
|
||||
Acknowledgment acknowledgment = (Acknowledgment) headers.get(KafkaHeaders.ACKNOWLEDGMENT);
|
||||
Assert.notNull(acknowledgment,
|
||||
"Acknowledgement shouldn't be null when acknowledging kafka message " + "manually.");
|
||||
acknowledgment.acknowledge();
|
||||
}
|
||||
}
|
||||
|
||||
private final class ReceivingHandler extends AbstractReplyProducingMessageHandler {
|
||||
|
||||
private final ExtendedConsumerProperties<KafkaConsumerProperties> consumerProperties;
|
||||
|
||||
private ReceivingHandler(ExtendedConsumerProperties<KafkaConsumerProperties> consumerProperties) {
|
||||
this.consumerProperties = consumerProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object handleRequestMessage(Message<?> requestMessage) {
|
||||
if (HeaderMode.embeddedHeaders.equals(consumerProperties.getHeaderMode())) {
|
||||
MessageValues messageValues = extractMessageValues(requestMessage);
|
||||
return MessageBuilder.createMessage(messageValues.getPayload(), new KafkaBinderHeaders(messageValues));
|
||||
}
|
||||
else {
|
||||
return requestMessage;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldCopyRequestHeaders() {
|
||||
// prevent the message from being copied again in superclass
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private final class KafkaBinderHeaders extends MessageHeaders {
|
||||
KafkaBinderHeaders(Map<String, Object> headers) {
|
||||
super(headers, MessageHeaders.ID_VALUE_NONE, -1L);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class SendingHandler extends AbstractMessageHandler {
|
||||
|
||||
private final AtomicInteger roundRobinCount = new AtomicInteger();
|
||||
|
||||
private final String topicName;
|
||||
|
||||
private final ExtendedProducerProperties<KafkaProducerProperties> producerProperties;
|
||||
|
||||
private final int numberOfKafkaPartitions;
|
||||
|
||||
private final ProducerConfiguration<byte[], byte[]> producerConfiguration;
|
||||
|
||||
private final PartitionHandler partitionHandler;
|
||||
|
||||
private SendingHandler(String topicName, ExtendedProducerProperties<KafkaProducerProperties> properties,
|
||||
int numberOfPartitions, ProducerConfiguration<byte[], byte[]> 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleMessageInternal(Message<?> message) throws Exception {
|
||||
int targetPartition;
|
||||
if (producerProperties.isPartitioned()) {
|
||||
targetPartition = this.partitionHandler.determinePartition(message);
|
||||
}
|
||||
else {
|
||||
targetPartition = roundRobin() % numberOfKafkaPartitions;
|
||||
}
|
||||
if (HeaderMode.embeddedHeaders.equals(producerProperties.getHeaderMode())) {
|
||||
MessageValues transformed = serializePayloadIfNecessary(message);
|
||||
byte[] messageToSend = embeddedHeadersMessageConverter.embedHeaders(transformed,
|
||||
KafkaMessageChannelBinder.this.headersToMap);
|
||||
producerConfiguration.send(topicName, targetPartition, null, messageToSend);
|
||||
}
|
||||
else if (HeaderMode.raw.equals(producerProperties.getHeaderMode())) {
|
||||
Object contentType = message.getHeaders().get(MessageHeaders.CONTENT_TYPE);
|
||||
if (contentType != null && !contentType.equals(MediaType.APPLICATION_OCTET_STREAM_VALUE)) {
|
||||
logger.error("Raw mode supports only " + MediaType.APPLICATION_OCTET_STREAM_VALUE + " content type"
|
||||
+ message.getPayload().getClass());
|
||||
}
|
||||
if (message.getPayload() instanceof byte[]) {
|
||||
producerConfiguration.send(topicName, targetPartition, null, (byte[]) message.getPayload());
|
||||
}
|
||||
else {
|
||||
logger.error("Raw mode supports only byte[] payloads but value sent was of type "
|
||||
+ message.getPayload().getClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int roundRobin() {
|
||||
int result = roundRobinCount.incrementAndGet();
|
||||
if (result == Integer.MAX_VALUE) {
|
||||
roundRobinCount.set(0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum StartOffset {
|
||||
earliest(OffsetRequest.EarliestTime()), latest(OffsetRequest.LatestTime());
|
||||
|
||||
private final long referencePoint;
|
||||
|
||||
StartOffset(long referencePoint) {
|
||||
this.referencePoint = referencePoint;
|
||||
}
|
||||
|
||||
public long getReferencePoint() {
|
||||
return referencePoint;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,68 +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;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.springframework.integration.kafka.support.ProducerMetadata;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class KafkaProducerProperties {
|
||||
|
||||
private int bufferSize = 16384;
|
||||
|
||||
private ProducerMetadata.CompressionType compressionType = ProducerMetadata.CompressionType.none;
|
||||
|
||||
private boolean sync;
|
||||
|
||||
private int batchTimeout;
|
||||
|
||||
public int getBufferSize() {
|
||||
return bufferSize;
|
||||
}
|
||||
|
||||
public void setBufferSize(int bufferSize) {
|
||||
this.bufferSize = bufferSize;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
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 int getBatchTimeout() {
|
||||
return batchTimeout;
|
||||
}
|
||||
|
||||
public void setBatchTimeout(int batchTimeout) {
|
||||
this.batchTimeout = batchTimeout;
|
||||
}
|
||||
}
|
||||
@@ -1,262 +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.kafka;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import rx.Observable;
|
||||
import rx.Subscription;
|
||||
import rx.functions.Action0;
|
||||
import rx.functions.Action1;
|
||||
import rx.functions.Func1;
|
||||
import rx.functions.Func2;
|
||||
import rx.observables.GroupedObservable;
|
||||
import rx.observables.MathObservable;
|
||||
import rx.subjects.PublishSubject;
|
||||
import rx.subjects.SerializedSubject;
|
||||
import rx.subjects.Subject;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.integration.kafka.core.Partition;
|
||||
import org.springframework.integration.kafka.listener.OffsetManager;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link OffsetManager} that aggregates writes over a time or count window, using an underlying delegate to
|
||||
* do the actual operations. Its purpose is to reduce the performance impact of writing operations
|
||||
* wherever this is desirable.
|
||||
*
|
||||
* Either a time window or a number of writes can be specified, but not both.
|
||||
*
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class WindowingOffsetManager implements OffsetManager, InitializingBean, DisposableBean {
|
||||
|
||||
private final CreatePartitionAndOffsetFunction createPartitionAndOffsetFunction = new CreatePartitionAndOffsetFunction();
|
||||
|
||||
private final GetOffsetFunction getOffsetFunction = new GetOffsetFunction();
|
||||
|
||||
private final ComputeMaximumOffsetByPartitionFunction findHighestOffsetInPartitionGroup = new ComputeMaximumOffsetByPartitionFunction();
|
||||
|
||||
private final GetPartitionFunction getPartition = new GetPartitionFunction();
|
||||
|
||||
private final FindHighestOffsetsByPartitionFunction findHighestOffsetsByPartition = new FindHighestOffsetsByPartitionFunction();
|
||||
|
||||
private final DelegateUpdateOffsetAction delegateUpdateOffsetAction = new DelegateUpdateOffsetAction();
|
||||
|
||||
private final NotifyObservableClosedAction notifyObservableClosed = new NotifyObservableClosedAction();
|
||||
|
||||
private final OffsetManager delegate;
|
||||
|
||||
private long timespan = 10 * 1000;
|
||||
|
||||
private int count;
|
||||
|
||||
private Subject<PartitionAndOffset, PartitionAndOffset> offsets;
|
||||
|
||||
private Subscription subscription;
|
||||
|
||||
private int shutdownTimeout = 2000;
|
||||
|
||||
private CountDownLatch shutdownLatch;
|
||||
|
||||
public WindowingOffsetManager(OffsetManager offsetManager) {
|
||||
this.delegate = offsetManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* The timespan for aggregating write operations, before invoking the underlying {@link OffsetManager}.
|
||||
*
|
||||
* @param timespan duration in milliseconds
|
||||
*/
|
||||
public void setTimespan(long timespan) {
|
||||
Assert.isTrue(timespan >= 0, "Timespan must be a positive value");
|
||||
this.timespan = timespan;
|
||||
}
|
||||
|
||||
/**
|
||||
* How many writes should be aggregated, before invoking the underlying {@link OffsetManager}. Setting this value
|
||||
* to 1 effectively disables windowing.
|
||||
*
|
||||
* @param count number of writes
|
||||
*/
|
||||
public void setCount(int count) {
|
||||
Assert.isTrue(count >= 0, "Count must be a positive value");
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
/**
|
||||
* The timeout that {@link #close()} and {@link #destroy()} operations will wait for receving a confirmation that the
|
||||
* underlying writes have been processed.
|
||||
*
|
||||
* @param shutdownTimeout duration in milliseconds
|
||||
*/
|
||||
public void setShutdownTimeout(int shutdownTimeout) {
|
||||
this.shutdownTimeout = shutdownTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.isTrue(timespan > 0 ^ count > 0, "Only one of the timespan or count must be set");
|
||||
// create the stream if windowing is set, and count is higher than 1
|
||||
if (timespan > 0 || count > 1) {
|
||||
offsets = new SerializedSubject<>(PublishSubject.<PartitionAndOffset>create());
|
||||
// window by either count or time
|
||||
Observable<Observable<PartitionAndOffset>> window =
|
||||
timespan > 0 ? offsets.window(timespan, TimeUnit.MILLISECONDS) : offsets.window(count);
|
||||
Observable<PartitionAndOffset> maximumOffsetsByWindow = window
|
||||
.flatMap(findHighestOffsetsByPartition)
|
||||
.doOnCompleted(notifyObservableClosed);
|
||||
subscription = maximumOffsetsByWindow.subscribe(delegateUpdateOffsetAction);
|
||||
}
|
||||
else {
|
||||
offsets = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
this.flush();
|
||||
this.close();
|
||||
if (delegate instanceof DisposableBean) {
|
||||
((DisposableBean) delegate).destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateOffset(Partition partition, long offset) {
|
||||
if (offsets != null) {
|
||||
offsets.onNext(new PartitionAndOffset(partition, offset));
|
||||
}
|
||||
else {
|
||||
delegate.updateOffset(partition, offset);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getOffset(Partition partition) {
|
||||
return delegate.getOffset(partition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteOffset(Partition partition) {
|
||||
delegate.deleteOffset(partition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetOffsets(Collection<Partition> partition) {
|
||||
delegate.resetOffsets(partition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (offsets != null) {
|
||||
shutdownLatch = new CountDownLatch(1);
|
||||
offsets.onCompleted();
|
||||
try {
|
||||
shutdownLatch.await(shutdownTimeout, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// ignore
|
||||
}
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
delegate.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
delegate.flush();
|
||||
}
|
||||
|
||||
private final class PartitionAndOffset {
|
||||
|
||||
private final Partition partition;
|
||||
|
||||
private final Long offset;
|
||||
|
||||
private PartitionAndOffset(Partition partition, Long offset) {
|
||||
this.partition = partition;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public Partition getPartition() {
|
||||
return partition;
|
||||
}
|
||||
|
||||
public Long getOffset() {
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
|
||||
private class DelegateUpdateOffsetAction implements Action1<PartitionAndOffset> {
|
||||
@Override
|
||||
public void call(PartitionAndOffset partitionAndOffset) {
|
||||
delegate.updateOffset(partitionAndOffset.getPartition(), partitionAndOffset.getOffset());
|
||||
}
|
||||
}
|
||||
|
||||
private class NotifyObservableClosedAction implements Action0 {
|
||||
@Override
|
||||
public void call() {
|
||||
if (shutdownLatch != null) {
|
||||
shutdownLatch.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CreatePartitionAndOffsetFunction implements Func2<Partition, Long, PartitionAndOffset> {
|
||||
@Override
|
||||
public PartitionAndOffset call(Partition partition, Long offset) {
|
||||
return new PartitionAndOffset(partition, offset);
|
||||
}
|
||||
}
|
||||
|
||||
private class GetOffsetFunction implements Func1<PartitionAndOffset, Long> {
|
||||
@Override
|
||||
public Long call(PartitionAndOffset partitionAndOffset) {
|
||||
return partitionAndOffset.getOffset();
|
||||
}
|
||||
}
|
||||
|
||||
private class ComputeMaximumOffsetByPartitionFunction implements Func1<GroupedObservable<Partition, PartitionAndOffset>, Observable<PartitionAndOffset>> {
|
||||
@Override
|
||||
public Observable<PartitionAndOffset> call(GroupedObservable<Partition, PartitionAndOffset> group) {
|
||||
return Observable.zip(Observable.just(group.getKey()),
|
||||
MathObservable.max(group.map(getOffsetFunction)),
|
||||
createPartitionAndOffsetFunction);
|
||||
}
|
||||
}
|
||||
|
||||
private class GetPartitionFunction implements Func1<PartitionAndOffset, Partition> {
|
||||
@Override
|
||||
public Partition call(PartitionAndOffset partitionAndOffset) {
|
||||
return partitionAndOffset.getPartition();
|
||||
}
|
||||
}
|
||||
|
||||
private class FindHighestOffsetsByPartitionFunction implements Func1<Observable<PartitionAndOffset>, Observable<PartitionAndOffset>> {
|
||||
@Override
|
||||
public Observable<PartitionAndOffset> call(Observable<PartitionAndOffset> windowBuffer) {
|
||||
return windowBuffer.groupBy(getPartition).flatMap(findHighestOffsetInPartitionGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.cloud.stream.binder.kafka.KafkaBinderHealthIndicator;
|
||||
import org.springframework.cloud.stream.binder.kafka.KafkaExtendedBindingProperties;
|
||||
import org.springframework.cloud.stream.binder.kafka.KafkaMessageChannelBinder;
|
||||
import org.springframework.cloud.stream.config.codec.kryo.KryoCodecAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.integration.codec.Codec;
|
||||
import org.springframework.integration.kafka.support.LoggingProducerListener;
|
||||
import org.springframework.integration.kafka.support.ProducerListener;
|
||||
|
||||
/**
|
||||
* @author David Turanski
|
||||
* @author Marius Bogoevici
|
||||
* @author Soby Chacko
|
||||
* @author Mark Fisher
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnMissingBean(Binder.class)
|
||||
@Import({ KryoCodecAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
|
||||
@EnableConfigurationProperties({ KafkaBinderConfigurationProperties.class, KafkaExtendedBindingProperties.class })
|
||||
public class KafkaBinderConfiguration {
|
||||
|
||||
@Autowired
|
||||
private Codec codec;
|
||||
|
||||
@Autowired
|
||||
private KafkaBinderConfigurationProperties configurationProperties;
|
||||
|
||||
@Autowired
|
||||
private KafkaExtendedBindingProperties kafkaExtendedBindingProperties;
|
||||
|
||||
@Autowired
|
||||
private ProducerListener producerListener;
|
||||
|
||||
@Bean
|
||||
KafkaMessageChannelBinder kafkaMessageChannelBinder() {
|
||||
KafkaMessageChannelBinder kafkaMessageChannelBinder = new KafkaMessageChannelBinder(configurationProperties);
|
||||
kafkaMessageChannelBinder.setCodec(codec);
|
||||
kafkaMessageChannelBinder.setProducerListener(producerListener);
|
||||
kafkaMessageChannelBinder.setExtendedBindingProperties(kafkaExtendedBindingProperties);
|
||||
return kafkaMessageChannelBinder;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ProducerListener.class)
|
||||
ProducerListener producerListener() {
|
||||
return new LoggingProducerListener();
|
||||
}
|
||||
|
||||
@Bean
|
||||
KafkaBinderHealthIndicator healthIndicator(KafkaMessageChannelBinder kafkaMessageChannelBinder) {
|
||||
return new KafkaBinderHealthIndicator(kafkaMessageChannelBinder, configurationProperties);
|
||||
}
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author David Turanski
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.cloud.stream.kafka.binder")
|
||||
public class KafkaBinderConfigurationProperties {
|
||||
|
||||
private String[] zkNodes = new String[] { "localhost" };
|
||||
|
||||
private String defaultZkPort = "2181";
|
||||
|
||||
private String[] brokers = new String[] { "localhost" };
|
||||
|
||||
private String defaultBrokerPort = "9092";
|
||||
|
||||
private String[] headers = new String[] {};
|
||||
|
||||
private int offsetUpdateTimeWindow = 10000;
|
||||
|
||||
private int offsetUpdateCount;
|
||||
|
||||
private int offsetUpdateShutdownTimeout = 2000;
|
||||
|
||||
private int maxWait = 100;
|
||||
|
||||
private boolean autoCreateTopics = true;
|
||||
|
||||
private boolean autoAddPartitions;
|
||||
|
||||
private int socketBufferSize = 2097152;
|
||||
|
||||
/**
|
||||
* ZK session timeout in milliseconds.
|
||||
*/
|
||||
private int zkSessionTimeout = 10000;
|
||||
|
||||
/**
|
||||
* ZK Connection timeout in milliseconds.
|
||||
*/
|
||||
private int zkConnectionTimeout = 10000;
|
||||
|
||||
private int requiredAcks = 1;
|
||||
|
||||
private int replicationFactor = 1;
|
||||
|
||||
private int fetchSize = 1024 * 1024;
|
||||
|
||||
private int minPartitionCount = 1;
|
||||
|
||||
private int queueSize = 8192;
|
||||
|
||||
public String getZkConnectionString() {
|
||||
return toConnectionString(this.zkNodes, this.defaultZkPort);
|
||||
}
|
||||
|
||||
public String getKafkaConnectionString() {
|
||||
return toConnectionString(this.brokers, this.defaultBrokerPort);
|
||||
}
|
||||
|
||||
public String[] getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
public int getOffsetUpdateTimeWindow() {
|
||||
return this.offsetUpdateTimeWindow;
|
||||
}
|
||||
|
||||
public int getOffsetUpdateCount() {
|
||||
return this.offsetUpdateCount;
|
||||
}
|
||||
|
||||
public int getOffsetUpdateShutdownTimeout() {
|
||||
return this.offsetUpdateShutdownTimeout;
|
||||
}
|
||||
|
||||
public String[] getZkNodes() {
|
||||
return zkNodes;
|
||||
}
|
||||
|
||||
public void setZkNodes(String... zkNodes) {
|
||||
this.zkNodes = zkNodes;
|
||||
}
|
||||
|
||||
public void setDefaultZkPort(String defaultZkPort) {
|
||||
this.defaultZkPort = defaultZkPort;
|
||||
}
|
||||
|
||||
public String[] getBrokers() {
|
||||
return brokers;
|
||||
}
|
||||
|
||||
public void setBrokers(String... brokers) {
|
||||
this.brokers = brokers;
|
||||
}
|
||||
|
||||
public void setDefaultBrokerPort(String defaultBrokerPort) {
|
||||
this.defaultBrokerPort = defaultBrokerPort;
|
||||
}
|
||||
|
||||
public void setHeaders(String... headers) {
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public void setOffsetUpdateTimeWindow(int offsetUpdateTimeWindow) {
|
||||
this.offsetUpdateTimeWindow = offsetUpdateTimeWindow;
|
||||
}
|
||||
|
||||
public void setOffsetUpdateCount(int offsetUpdateCount) {
|
||||
this.offsetUpdateCount = offsetUpdateCount;
|
||||
}
|
||||
|
||||
public void setOffsetUpdateShutdownTimeout(int offsetUpdateShutdownTimeout) {
|
||||
this.offsetUpdateShutdownTimeout = offsetUpdateShutdownTimeout;
|
||||
}
|
||||
|
||||
public int getZkSessionTimeout() {
|
||||
return this.zkSessionTimeout;
|
||||
}
|
||||
|
||||
public void setZkSessionTimeout(int zkSessionTimeout) {
|
||||
this.zkSessionTimeout = zkSessionTimeout;
|
||||
}
|
||||
|
||||
public int getZkConnectionTimeout() {
|
||||
return this.zkConnectionTimeout;
|
||||
}
|
||||
|
||||
public void setZkConnectionTimeout(int zkConnectionTimeout) {
|
||||
this.zkConnectionTimeout = zkConnectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an array of host values to a comma-separated String.
|
||||
*
|
||||
* It will append the default port value, if not already specified.
|
||||
*/
|
||||
private String toConnectionString(String[] hosts, String defaultPort) {
|
||||
String[] fullyFormattedHosts = new String[hosts.length];
|
||||
for (int i = 0; i < hosts.length; i++) {
|
||||
if (hosts[i].contains(":") || StringUtils.isEmpty(defaultPort)) {
|
||||
fullyFormattedHosts[i] = hosts[i];
|
||||
}
|
||||
else {
|
||||
fullyFormattedHosts[i] = hosts[i] + ":" + defaultPort;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
public boolean isAutoCreateTopics() {
|
||||
return autoCreateTopics;
|
||||
}
|
||||
|
||||
public void setAutoCreateTopics(boolean autoCreateTopics) {
|
||||
this.autoCreateTopics = autoCreateTopics;
|
||||
}
|
||||
|
||||
public boolean isAutoAddPartitions() {
|
||||
return autoAddPartitions;
|
||||
}
|
||||
|
||||
public void setAutoAddPartitions(boolean autoAddPartitions) {
|
||||
this.autoAddPartitions = autoAddPartitions;
|
||||
}
|
||||
|
||||
public int getSocketBufferSize() {
|
||||
return socketBufferSize;
|
||||
}
|
||||
|
||||
public void setSocketBufferSize(int socketBufferSize) {
|
||||
this.socketBufferSize = socketBufferSize;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This package contains an implementation of the {@link org.springframework.cloud.stream.binder.Binder} for Kafka.
|
||||
*/
|
||||
package org.springframework.cloud.stream.binder.kafka;
|
||||
@@ -1,2 +0,0 @@
|
||||
kafka:\
|
||||
org.springframework.cloud.stream.binder.kafka.config.KafkaBinderConfiguration
|
||||
@@ -1,808 +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.kafka;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Properties;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import kafka.admin.AdminUtils;
|
||||
import kafka.api.TopicMetadata;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.cloud.stream.binder.BinderException;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.ExtendedProducerProperties;
|
||||
import org.springframework.cloud.stream.binder.PartitionCapableBinderTests;
|
||||
import org.springframework.cloud.stream.binder.Spy;
|
||||
import org.springframework.cloud.stream.binder.TestUtils;
|
||||
import org.springframework.cloud.stream.binder.kafka.config.KafkaBinderConfigurationProperties;
|
||||
import org.springframework.cloud.stream.test.junit.kafka.KafkaTestSupport;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.kafka.core.Partition;
|
||||
import org.springframework.integration.kafka.core.TopicNotFoundException;
|
||||
import org.springframework.integration.kafka.support.KafkaHeaders;
|
||||
import org.springframework.integration.kafka.support.ProducerConfiguration;
|
||||
import org.springframework.integration.kafka.support.ProducerMetadata;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.retry.backoff.FixedBackOffPolicy;
|
||||
import org.springframework.retry.policy.SimpleRetryPolicy;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* Integration tests for the {@link KafkaMessageChannelBinder}.
|
||||
* @author Eric Bottard
|
||||
* @author Marius Bogoevici
|
||||
* @author Mark Fisher
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
public class KafkaBinderTests extends
|
||||
PartitionCapableBinderTests<KafkaTestBinder, ExtendedConsumerProperties<KafkaConsumerProperties>, ExtendedProducerProperties<KafkaProducerProperties>> {
|
||||
|
||||
private final String CLASS_UNDER_TEST_NAME = KafkaMessageChannelBinder.class.getSimpleName();
|
||||
|
||||
@ClassRule
|
||||
public static KafkaTestSupport kafkaTestSupport = new KafkaTestSupport();
|
||||
|
||||
private KafkaTestBinder binder;
|
||||
|
||||
@Override
|
||||
protected void binderBindUnbindLatency() throws InterruptedException {
|
||||
Thread.sleep(500);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected KafkaTestBinder getBinder() {
|
||||
if (binder == null) {
|
||||
KafkaBinderConfigurationProperties binderConfiguration = createConfigurationProperties();
|
||||
binder = new KafkaTestBinder(binderConfiguration);
|
||||
}
|
||||
return binder;
|
||||
}
|
||||
|
||||
private KafkaBinderConfigurationProperties createConfigurationProperties() {
|
||||
KafkaBinderConfigurationProperties binderConfiguration = new KafkaBinderConfigurationProperties();
|
||||
binderConfiguration.setBrokers(kafkaTestSupport.getBrokerAddress());
|
||||
binderConfiguration.setZkNodes(kafkaTestSupport.getZkConnectString());
|
||||
return binderConfiguration;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ExtendedConsumerProperties<KafkaConsumerProperties> createConsumerProperties() {
|
||||
return new ExtendedConsumerProperties<>(new KafkaConsumerProperties());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ExtendedProducerProperties<KafkaProducerProperties> createProducerProperties() {
|
||||
return new ExtendedProducerProperties<>(new KafkaProducerProperties());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
String multiplier = System.getenv("KAFKA_TIMEOUT_MULTIPLIER");
|
||||
if (multiplier != null) {
|
||||
timeoutMultiplier = Double.parseDouble(multiplier);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean usesExplicitRouting() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getClassUnderTestName() {
|
||||
return CLASS_UNDER_TEST_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spy spyOn(final String name) {
|
||||
throw new UnsupportedOperationException("'spyOn' is not used by Kafka tests");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDlqAndRetry() {
|
||||
KafkaTestBinder binder = getBinder();
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
DirectChannel moduleInputChannel = new DirectChannel();
|
||||
QueueChannel dlqChannel = new QueueChannel();
|
||||
FailingInvocationCountingMessageHandler handler = new FailingInvocationCountingMessageHandler();
|
||||
moduleInputChannel.subscribe(handler);
|
||||
ExtendedProducerProperties<KafkaProducerProperties> producerProperties = createProducerProperties();
|
||||
producerProperties.setPartitionCount(10);
|
||||
ExtendedConsumerProperties<KafkaConsumerProperties> consumerProperties = createConsumerProperties();
|
||||
consumerProperties.setMaxAttempts(3);
|
||||
consumerProperties.setBackOffInitialInterval(100);
|
||||
consumerProperties.setBackOffMaxInterval(150);
|
||||
consumerProperties.getExtension().setEnableDlq(true);
|
||||
long uniqueBindingId = System.currentTimeMillis();
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("retryTest." + uniqueBindingId + ".0",
|
||||
moduleOutputChannel, producerProperties);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("retryTest." + uniqueBindingId + ".0",
|
||||
"testGroup", moduleInputChannel, consumerProperties);
|
||||
|
||||
ExtendedConsumerProperties<KafkaConsumerProperties> dlqConsumerProperties = createConsumerProperties();
|
||||
dlqConsumerProperties.setMaxAttempts(1);
|
||||
|
||||
Binding<MessageChannel> dlqConsumerBinding = binder.bindConsumer(
|
||||
"error.retryTest." + uniqueBindingId + ".0.testGroup", null, dlqChannel, dlqConsumerProperties);
|
||||
|
||||
String testMessagePayload = "test." + UUID.randomUUID().toString();
|
||||
Message<String> testMessage = MessageBuilder.withPayload(testMessagePayload).build();
|
||||
moduleOutputChannel.send(testMessage);
|
||||
|
||||
Message<?> receivedMessage = receive(dlqChannel, 3);
|
||||
assertThat(receivedMessage).isNotNull();
|
||||
assertThat(receivedMessage.getPayload()).isEqualTo(testMessagePayload);
|
||||
assertThat(handler.getInvocationCount()).isEqualTo(consumerProperties.getMaxAttempts());
|
||||
dlqConsumerBinding.unbind();
|
||||
consumerBinding.unbind();
|
||||
producerBinding.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultAutoCommitOnErrorWithoutDlq() throws Exception {
|
||||
KafkaTestBinder binder = getBinder();
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
DirectChannel moduleInputChannel = new DirectChannel();
|
||||
FailingInvocationCountingMessageHandler handler = new FailingInvocationCountingMessageHandler();
|
||||
moduleInputChannel.subscribe(handler);
|
||||
ExtendedProducerProperties<KafkaProducerProperties> producerProperties = createProducerProperties();
|
||||
producerProperties.setPartitionCount(10);
|
||||
ExtendedConsumerProperties<KafkaConsumerProperties> consumerProperties = createConsumerProperties();
|
||||
consumerProperties.setMaxAttempts(1);
|
||||
consumerProperties.setBackOffInitialInterval(100);
|
||||
consumerProperties.setBackOffMaxInterval(150);
|
||||
long uniqueBindingId = System.currentTimeMillis();
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("retryTest." + uniqueBindingId + ".0",
|
||||
moduleOutputChannel, producerProperties);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("retryTest." + uniqueBindingId + ".0",
|
||||
"testGroup", moduleInputChannel, consumerProperties);
|
||||
|
||||
String testMessagePayload = "test." + UUID.randomUUID().toString();
|
||||
Message<String> testMessage = MessageBuilder.withPayload(testMessagePayload).build();
|
||||
moduleOutputChannel.send(testMessage);
|
||||
|
||||
assertThat(handler.getLatch().await((int) (timeoutMultiplier * 1000), TimeUnit.MILLISECONDS));
|
||||
// first attempt fails
|
||||
assertThat(handler.getReceivedMessages().entrySet()).hasSize(1);
|
||||
Message<?> receivedMessage = handler.getReceivedMessages().entrySet().iterator().next().getValue();
|
||||
assertThat(receivedMessage).isNotNull();
|
||||
assertThat(receivedMessage.getPayload()).isEqualTo(testMessagePayload);
|
||||
assertThat(handler.getInvocationCount()).isEqualTo(consumerProperties.getMaxAttempts());
|
||||
consumerBinding.unbind();
|
||||
|
||||
// on the second attempt the message is redelivered
|
||||
QueueChannel successfulInputChannel = new QueueChannel();
|
||||
consumerBinding = binder.bindConsumer("retryTest." + uniqueBindingId + ".0", "testGroup",
|
||||
successfulInputChannel, consumerProperties);
|
||||
String testMessage2Payload = "test." + UUID.randomUUID().toString();
|
||||
Message<String> testMessage2 = MessageBuilder.withPayload(testMessage2Payload).build();
|
||||
moduleOutputChannel.send(testMessage2);
|
||||
|
||||
Message<?> firstReceived = receive(successfulInputChannel);
|
||||
assertThat(firstReceived.getPayload()).isEqualTo(testMessagePayload);
|
||||
Message<?> secondReceived = receive(successfulInputChannel);
|
||||
assertThat(secondReceived.getPayload()).isEqualTo(testMessage2Payload);
|
||||
consumerBinding.unbind();
|
||||
producerBinding.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultAutoCommitOnErrorWithDlq() throws Exception {
|
||||
KafkaTestBinder binder = getBinder();
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
DirectChannel moduleInputChannel = new DirectChannel();
|
||||
FailingInvocationCountingMessageHandler handler = new FailingInvocationCountingMessageHandler();
|
||||
moduleInputChannel.subscribe(handler);
|
||||
ExtendedProducerProperties<KafkaProducerProperties> producerProperties = createProducerProperties();
|
||||
producerProperties.setPartitionCount(10);
|
||||
ExtendedConsumerProperties<KafkaConsumerProperties> consumerProperties = createConsumerProperties();
|
||||
consumerProperties.setMaxAttempts(3);
|
||||
consumerProperties.setBackOffInitialInterval(100);
|
||||
consumerProperties.setBackOffMaxInterval(150);
|
||||
consumerProperties.getExtension().setEnableDlq(true);
|
||||
long uniqueBindingId = System.currentTimeMillis();
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("retryTest." + uniqueBindingId + ".0",
|
||||
moduleOutputChannel, producerProperties);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("retryTest." + uniqueBindingId + ".0",
|
||||
"testGroup", moduleInputChannel, consumerProperties);
|
||||
ExtendedConsumerProperties<KafkaConsumerProperties> dlqConsumerProperties = createConsumerProperties();
|
||||
dlqConsumerProperties.setMaxAttempts(1);
|
||||
QueueChannel dlqChannel = new QueueChannel();
|
||||
Binding<MessageChannel> dlqConsumerBinding = binder.bindConsumer(
|
||||
"error.retryTest." + uniqueBindingId + ".0.testGroup", null, dlqChannel, dlqConsumerProperties);
|
||||
|
||||
String testMessagePayload = "test." + UUID.randomUUID().toString();
|
||||
Message<String> testMessage = MessageBuilder.withPayload(testMessagePayload).build();
|
||||
moduleOutputChannel.send(testMessage);
|
||||
|
||||
Message<?> dlqMessage = receive(dlqChannel, 3);
|
||||
assertThat(dlqMessage).isNotNull();
|
||||
assertThat(dlqMessage.getPayload()).isEqualTo(testMessagePayload);
|
||||
|
||||
// first attempt fails
|
||||
assertThat(handler.getReceivedMessages().entrySet()).hasSize(1);
|
||||
Message<?> handledMessage = handler.getReceivedMessages().entrySet().iterator().next().getValue();
|
||||
assertThat(handledMessage).isNotNull();
|
||||
assertThat(handledMessage.getPayload()).isEqualTo(testMessagePayload);
|
||||
assertThat(handler.getInvocationCount()).isEqualTo(consumerProperties.getMaxAttempts());
|
||||
|
||||
dlqConsumerBinding.unbind();
|
||||
consumerBinding.unbind();
|
||||
|
||||
// on the second attempt the message is not redelivered because the DLQ is set
|
||||
QueueChannel successfulInputChannel = new QueueChannel();
|
||||
consumerBinding = binder.bindConsumer("retryTest." + uniqueBindingId + ".0", "testGroup",
|
||||
successfulInputChannel, consumerProperties);
|
||||
String testMessage2Payload = "test." + UUID.randomUUID().toString();
|
||||
Message<String> testMessage2 = MessageBuilder.withPayload(testMessage2Payload).build();
|
||||
moduleOutputChannel.send(testMessage2);
|
||||
|
||||
Message<?> receivedMessage = receive(successfulInputChannel);
|
||||
assertThat(receivedMessage.getPayload()).isEqualTo(testMessage2Payload);
|
||||
|
||||
consumerBinding.unbind();
|
||||
producerBinding.unbind();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testValidateKafkaTopicName() {
|
||||
KafkaMessageChannelBinder.validateTopicName("foo:bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompression() throws Exception {
|
||||
final ProducerMetadata.CompressionType[] codecs = new ProducerMetadata.CompressionType[] {
|
||||
ProducerMetadata.CompressionType.none, ProducerMetadata.CompressionType.gzip,
|
||||
ProducerMetadata.CompressionType.snappy };
|
||||
byte[] testPayload = new byte[2048];
|
||||
Arrays.fill(testPayload, (byte) 65);
|
||||
KafkaTestBinder binder = getBinder();
|
||||
for (ProducerMetadata.CompressionType codec : codecs) {
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
ExtendedProducerProperties<KafkaProducerProperties> producerProperties = createProducerProperties();
|
||||
producerProperties.getExtension().setCompressionType(codec);
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("foo.0", moduleOutputChannel,
|
||||
producerProperties);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("foo.0", "test", moduleInputChannel,
|
||||
createConsumerProperties());
|
||||
Message<?> message = org.springframework.integration.support.MessageBuilder.withPayload(testPayload)
|
||||
.build();
|
||||
// Let the consumer actually bind to the producer before sending a msg
|
||||
binderBindUnbindLatency();
|
||||
moduleOutputChannel.send(message);
|
||||
Message<?> inbound = receive(moduleInputChannel);
|
||||
assertThat(inbound).isNotNull();
|
||||
assertThat((byte[]) inbound.getPayload()).containsExactly(testPayload);
|
||||
producerBinding.unbind();
|
||||
consumerBinding.unbind();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomPartitionCountOverridesDefaultIfLarger() throws Exception {
|
||||
|
||||
byte[] testPayload = new byte[2048];
|
||||
Arrays.fill(testPayload, (byte) 65);
|
||||
KafkaBinderConfigurationProperties binderConfiguration = createConfigurationProperties();
|
||||
binderConfiguration.setMinPartitionCount(10);
|
||||
KafkaTestBinder binder = new KafkaTestBinder(binderConfiguration);
|
||||
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
ExtendedProducerProperties<KafkaProducerProperties> producerProperties = createProducerProperties();
|
||||
producerProperties.setPartitionCount(10);
|
||||
ExtendedConsumerProperties<KafkaConsumerProperties> consumerProperties = createConsumerProperties();
|
||||
long uniqueBindingId = System.currentTimeMillis();
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("foo" + uniqueBindingId + ".0",
|
||||
moduleOutputChannel, producerProperties);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("foo" + uniqueBindingId + ".0", null,
|
||||
moduleInputChannel, consumerProperties);
|
||||
Message<?> message = org.springframework.integration.support.MessageBuilder.withPayload(testPayload)
|
||||
.build();
|
||||
// Let the consumer actually bind to the producer before sending a msg
|
||||
binderBindUnbindLatency();
|
||||
moduleOutputChannel.send(message);
|
||||
Message<?> inbound = receive(moduleInputChannel);
|
||||
assertThat(inbound).isNotNull();
|
||||
assertThat((byte[]) inbound.getPayload()).containsExactly(testPayload);
|
||||
Collection<Partition> partitions = binder.getCoreBinder().getConnectionFactory()
|
||||
.getPartitions("foo" + uniqueBindingId + ".0");
|
||||
assertThat(partitions).hasSize(10);
|
||||
producerBinding.unbind();
|
||||
consumerBinding.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomPartitionCountDoesNotOverridePartitioningIfSmaller() throws Exception {
|
||||
|
||||
byte[] testPayload = new byte[2048];
|
||||
Arrays.fill(testPayload, (byte) 65);
|
||||
KafkaBinderConfigurationProperties binderConfiguration = createConfigurationProperties();
|
||||
binderConfiguration.setMinPartitionCount(6);
|
||||
KafkaTestBinder binder = new KafkaTestBinder(binderConfiguration);
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
ExtendedProducerProperties<KafkaProducerProperties> producerProperties = createProducerProperties();
|
||||
producerProperties.setPartitionCount(5);
|
||||
producerProperties.setPartitionKeyExpression(spelExpressionParser.parseExpression("payload"));
|
||||
ExtendedConsumerProperties<KafkaConsumerProperties> consumerProperties = createConsumerProperties();
|
||||
long uniqueBindingId = System.currentTimeMillis();
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("foo" + uniqueBindingId + ".0",
|
||||
moduleOutputChannel, producerProperties);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("foo" + uniqueBindingId + ".0", null,
|
||||
moduleInputChannel, consumerProperties);
|
||||
Message<?> message = org.springframework.integration.support.MessageBuilder.withPayload(testPayload)
|
||||
.build();
|
||||
// Let the consumer actually bind to the producer before sending a msg
|
||||
binderBindUnbindLatency();
|
||||
moduleOutputChannel.send(message);
|
||||
Message<?> inbound = receive(moduleInputChannel);
|
||||
assertThat(inbound).isNotNull();
|
||||
assertThat((byte[]) inbound.getPayload()).containsExactly(testPayload);
|
||||
Collection<Partition> partitions = binder.getCoreBinder().getConnectionFactory()
|
||||
.getPartitions("foo" + uniqueBindingId + ".0");
|
||||
assertThat(partitions).hasSize(6);
|
||||
producerBinding.unbind();
|
||||
consumerBinding.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomPartitionCountOverridesPartitioningIfLarger() throws Exception {
|
||||
|
||||
byte[] testPayload = new byte[2048];
|
||||
Arrays.fill(testPayload, (byte) 65);
|
||||
KafkaBinderConfigurationProperties binderConfiguration = createConfigurationProperties();
|
||||
binderConfiguration.setMinPartitionCount(4);
|
||||
KafkaTestBinder binder = new KafkaTestBinder(binderConfiguration);
|
||||
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
ExtendedProducerProperties<KafkaProducerProperties> producerProperties = createProducerProperties();
|
||||
producerProperties.setPartitionCount(5);
|
||||
producerProperties.setPartitionKeyExpression(spelExpressionParser.parseExpression("payload"));
|
||||
ExtendedConsumerProperties<KafkaConsumerProperties> consumerProperties = createConsumerProperties();
|
||||
long uniqueBindingId = System.currentTimeMillis();
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("foo" + uniqueBindingId + ".0",
|
||||
moduleOutputChannel, producerProperties);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("foo" + uniqueBindingId + ".0", null,
|
||||
moduleInputChannel, consumerProperties);
|
||||
Message<?> message = org.springframework.integration.support.MessageBuilder.withPayload(testPayload)
|
||||
.build();
|
||||
// Let the consumer actually bind to the producer before sending a msg
|
||||
binderBindUnbindLatency();
|
||||
moduleOutputChannel.send(message);
|
||||
Message<?> inbound = receive(moduleInputChannel);
|
||||
assertThat(inbound).isNotNull();
|
||||
assertThat((byte[]) inbound.getPayload()).containsExactly(testPayload);
|
||||
Collection<Partition> partitions = binder.getCoreBinder().getConnectionFactory()
|
||||
.getPartitions("foo" + uniqueBindingId + ".0");
|
||||
assertThat(partitions).hasSize(5);
|
||||
producerBinding.unbind();
|
||||
consumerBinding.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testDefaultConsumerStartsAtEarliest() throws Exception {
|
||||
KafkaMessageChannelBinder binder = new KafkaMessageChannelBinder(createConfigurationProperties());
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.refresh();
|
||||
binder.setApplicationContext(context);
|
||||
binder.afterPropertiesSet();
|
||||
DirectChannel output = new DirectChannel();
|
||||
QueueChannel input1 = new QueueChannel();
|
||||
|
||||
String testTopicName = UUID.randomUUID().toString();
|
||||
binder.bindProducer(testTopicName, output, createProducerProperties());
|
||||
String testPayload1 = "foo-" + UUID.randomUUID().toString();
|
||||
output.send(new GenericMessage<>(testPayload1.getBytes()));
|
||||
binder.bindConsumer(testTopicName, "startOffsets", input1, createConsumerProperties());
|
||||
Message<byte[]> receivedMessage1 = (Message<byte[]>) receive(input1);
|
||||
assertThat(receivedMessage1).isNotNull();
|
||||
assertThat(new String(receivedMessage1.getPayload())).isEqualTo(testPayload1);
|
||||
String testPayload2 = "foo-" + UUID.randomUUID().toString();
|
||||
output.send(new GenericMessage<>(testPayload2.getBytes()));
|
||||
Message<byte[]> receivedMessage2 = (Message<byte[]>) receive(input1);
|
||||
assertThat(receivedMessage2).isNotNull();
|
||||
assertThat(new String(receivedMessage2.getPayload())).isEqualTo(testPayload2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testEarliest() throws Exception {
|
||||
KafkaTestBinder binder = getBinder();
|
||||
DirectChannel output = new DirectChannel();
|
||||
QueueChannel input1 = new QueueChannel();
|
||||
|
||||
String testTopicName = UUID.randomUUID().toString();
|
||||
binder.bindProducer(testTopicName, output, createProducerProperties());
|
||||
String testPayload1 = "foo-" + UUID.randomUUID().toString();
|
||||
output.send(new GenericMessage<>(testPayload1.getBytes()));
|
||||
ExtendedConsumerProperties<KafkaConsumerProperties> properties = createConsumerProperties();
|
||||
properties.getExtension().setStartOffset(KafkaMessageChannelBinder.StartOffset.earliest);
|
||||
binder.bindConsumer(testTopicName, "startOffsets", input1, properties);
|
||||
Message<byte[]> receivedMessage1 = (Message<byte[]>) receive(input1);
|
||||
assertThat(receivedMessage1).isNotNull();
|
||||
String testPayload2 = "foo-" + UUID.randomUUID().toString();
|
||||
output.send(new GenericMessage<>(testPayload2.getBytes()));
|
||||
Message<byte[]> receivedMessage2 = (Message<byte[]>) receive(input1);
|
||||
assertThat(receivedMessage2).isNotNull();
|
||||
assertThat(new String(receivedMessage2.getPayload())).isEqualTo(testPayload2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testReset() throws Exception {
|
||||
KafkaTestBinder binder = getBinder();
|
||||
DirectChannel output = new DirectChannel();
|
||||
QueueChannel input1 = new QueueChannel();
|
||||
|
||||
String testTopicName = UUID.randomUUID().toString();
|
||||
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer(testTopicName, output,
|
||||
createProducerProperties());
|
||||
String testPayload1 = "foo-" + UUID.randomUUID().toString();
|
||||
output.send(new GenericMessage<>(testPayload1.getBytes()));
|
||||
ExtendedConsumerProperties<KafkaConsumerProperties> properties = createConsumerProperties();
|
||||
properties.getExtension().setResetOffsets(true);
|
||||
properties.getExtension().setStartOffset(KafkaMessageChannelBinder.StartOffset.earliest);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer(testTopicName, "startOffsets", input1,
|
||||
properties);
|
||||
Message<byte[]> receivedMessage1 = (Message<byte[]>) receive(input1);
|
||||
assertThat(receivedMessage1).isNotNull();
|
||||
String testPayload2 = "foo-" + UUID.randomUUID().toString();
|
||||
output.send(new GenericMessage<>(testPayload2.getBytes()));
|
||||
Message<byte[]> receivedMessage2 = (Message<byte[]>) receive(input1);
|
||||
assertThat(receivedMessage2).isNotNull();
|
||||
assertThat(new String(receivedMessage2.getPayload())).isEqualTo(testPayload2);
|
||||
consumerBinding.unbind();
|
||||
|
||||
String testPayload3 = "foo-" + UUID.randomUUID().toString();
|
||||
output.send(new GenericMessage<>(testPayload3.getBytes()));
|
||||
|
||||
ExtendedConsumerProperties<KafkaConsumerProperties> properties2 = createConsumerProperties();
|
||||
properties2.getExtension().setResetOffsets(true);
|
||||
properties2.getExtension().setStartOffset(KafkaMessageChannelBinder.StartOffset.earliest);
|
||||
consumerBinding = binder.bindConsumer(testTopicName, "startOffsets", input1, properties2);
|
||||
Message<byte[]> receivedMessage4 = (Message<byte[]>) receive(input1);
|
||||
assertThat(receivedMessage4).isNotNull();
|
||||
assertThat(new String(receivedMessage4.getPayload())).isEqualTo(testPayload1);
|
||||
Message<byte[]> receivedMessage5 = (Message<byte[]>) receive(input1);
|
||||
assertThat(receivedMessage5).isNotNull();
|
||||
assertThat(new String(receivedMessage5.getPayload())).isEqualTo(testPayload2);
|
||||
Message<byte[]> receivedMessage6 = (Message<byte[]>) receive(input1);
|
||||
assertThat(receivedMessage6).isNotNull();
|
||||
assertThat(new String(receivedMessage6.getPayload())).isEqualTo(testPayload3);
|
||||
consumerBinding.unbind();
|
||||
producerBinding.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testResume() throws Exception {
|
||||
KafkaBinderConfigurationProperties configurationProperties = createConfigurationProperties();
|
||||
KafkaMessageChannelBinder binder = new KafkaMessageChannelBinder(configurationProperties);
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.refresh();
|
||||
binder.setApplicationContext(context);
|
||||
binder.afterPropertiesSet();
|
||||
DirectChannel output = new DirectChannel();
|
||||
QueueChannel input1 = new QueueChannel();
|
||||
|
||||
String testTopicName = UUID.randomUUID().toString();
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer(testTopicName, output,
|
||||
createProducerProperties());
|
||||
String testPayload1 = "foo-" + UUID.randomUUID().toString();
|
||||
output.send(new GenericMessage<>(testPayload1.getBytes()));
|
||||
ExtendedConsumerProperties<KafkaConsumerProperties> firstConsumerProperties = createConsumerProperties();
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer(testTopicName, "startOffsets", input1,
|
||||
firstConsumerProperties);
|
||||
Message<byte[]> receivedMessage1 = (Message<byte[]>) receive(input1);
|
||||
assertThat(receivedMessage1).isNotNull();
|
||||
String testPayload2 = "foo-" + UUID.randomUUID().toString();
|
||||
output.send(new GenericMessage<>(testPayload2.getBytes()));
|
||||
Message<byte[]> receivedMessage2 = (Message<byte[]>) receive(input1);
|
||||
assertThat(receivedMessage2).isNotNull();
|
||||
assertThat(new String(receivedMessage2.getPayload())).isNotNull();
|
||||
consumerBinding.unbind();
|
||||
|
||||
String testPayload3 = "foo-" + UUID.randomUUID().toString();
|
||||
output.send(new GenericMessage<>(testPayload3.getBytes()));
|
||||
|
||||
consumerBinding = binder.bindConsumer(testTopicName, "startOffsets", input1, createConsumerProperties());
|
||||
Message<byte[]> receivedMessage3 = (Message<byte[]>) receive(input1);
|
||||
assertThat(receivedMessage3).isNotNull();
|
||||
assertThat(new String(receivedMessage3.getPayload())).isEqualTo(testPayload3);
|
||||
consumerBinding.unbind();
|
||||
producerBinding.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSyncProducerMetadata() throws Exception {
|
||||
KafkaMessageChannelBinder binder = new KafkaMessageChannelBinder(createConfigurationProperties());
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.refresh();
|
||||
binder.setApplicationContext(context);
|
||||
binder.afterPropertiesSet();
|
||||
DirectChannel output = new DirectChannel();
|
||||
String testTopicName = UUID.randomUUID().toString();
|
||||
ExtendedProducerProperties<KafkaProducerProperties> properties = createProducerProperties();
|
||||
properties.getExtension().setSync(true);
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer(testTopicName, output, properties);
|
||||
DirectFieldAccessor accessor = new DirectFieldAccessor(extractEndpoint(producerBinding));
|
||||
MessageHandler handler = (MessageHandler) accessor.getPropertyValue("handler");
|
||||
DirectFieldAccessor accessor1 = new DirectFieldAccessor(handler);
|
||||
ProducerConfiguration producerConfiguration = (ProducerConfiguration) accessor1
|
||||
.getPropertyValue("producerConfiguration");
|
||||
assertThat(producerConfiguration.getProducerMetadata().isSync())
|
||||
.withFailMessage("Kafka Sync Producer should have been enabled.");
|
||||
producerBinding.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoCreateTopicsDisabledFailsIfTopicMissing() throws Exception {
|
||||
KafkaBinderConfigurationProperties configurationProperties = createConfigurationProperties();
|
||||
configurationProperties.setAutoCreateTopics(false);
|
||||
KafkaMessageChannelBinder binder = new KafkaMessageChannelBinder(configurationProperties);
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.refresh();
|
||||
binder.setApplicationContext(context);
|
||||
binder.afterPropertiesSet();
|
||||
RetryTemplate metatadataRetrievalRetryOperations = new RetryTemplate();
|
||||
metatadataRetrievalRetryOperations.setRetryPolicy(new SimpleRetryPolicy());
|
||||
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
|
||||
backOffPolicy.setBackOffPeriod(1000);
|
||||
metatadataRetrievalRetryOperations.setBackOffPolicy(backOffPolicy);
|
||||
binder.setMetadataRetryOperations(metatadataRetrievalRetryOperations);
|
||||
DirectChannel output = new DirectChannel();
|
||||
ExtendedConsumerProperties<KafkaConsumerProperties> consumerProperties = createConsumerProperties();
|
||||
String testTopicName = "nonexisting" + System.currentTimeMillis();
|
||||
try {
|
||||
binder.doBindConsumer(testTopicName, "test", output, consumerProperties);
|
||||
fail();
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertThat(e).isInstanceOf(BinderException.class);
|
||||
assertThat(e).hasMessageContaining("Topic " + testTopicName + " does not exist");
|
||||
}
|
||||
|
||||
try {
|
||||
binder.getConnectionFactory().getPartitions(testTopicName);
|
||||
fail();
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertThat(e).isInstanceOf(TopicNotFoundException.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoConfigureTopicsDisabledSucceedsIfTopicExisting() throws Exception {
|
||||
String testTopicName = "existing" + System.currentTimeMillis();
|
||||
AdminUtils.createTopic(kafkaTestSupport.getZkClient(), testTopicName, 5, 1, new Properties());
|
||||
KafkaBinderConfigurationProperties configurationProperties = createConfigurationProperties();
|
||||
configurationProperties.setAutoCreateTopics(false);
|
||||
KafkaMessageChannelBinder binder = new KafkaMessageChannelBinder(configurationProperties);
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.refresh();
|
||||
binder.setApplicationContext(context);
|
||||
binder.afterPropertiesSet();
|
||||
DirectChannel output = new DirectChannel();
|
||||
ExtendedConsumerProperties<KafkaConsumerProperties> consumerProperties = createConsumerProperties();
|
||||
Binding<MessageChannel> binding = binder.doBindConsumer(testTopicName, "test", output, consumerProperties);
|
||||
binding.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoAddPartitionsDisabledFailsIfTopicUnderpartitioned() throws Exception {
|
||||
String testTopicName = "existing" + System.currentTimeMillis();
|
||||
AdminUtils.createTopic(kafkaTestSupport.getZkClient(), testTopicName, 1, 1, new Properties());
|
||||
KafkaBinderConfigurationProperties configurationProperties = createConfigurationProperties();
|
||||
configurationProperties.setAutoAddPartitions(false);
|
||||
KafkaMessageChannelBinder binder = new KafkaMessageChannelBinder(configurationProperties);
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.refresh();
|
||||
binder.setApplicationContext(context);
|
||||
binder.afterPropertiesSet();
|
||||
DirectChannel output = new DirectChannel();
|
||||
ExtendedConsumerProperties<KafkaConsumerProperties> consumerProperties = createConsumerProperties();
|
||||
// this consumer must consume from partition 2
|
||||
consumerProperties.setInstanceCount(3);
|
||||
consumerProperties.setInstanceIndex(2);
|
||||
try {
|
||||
binder.doBindConsumer(testTopicName, "test", output, consumerProperties);
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertThat(e).isInstanceOf(BinderException.class);
|
||||
assertThat(e)
|
||||
.hasMessageContaining("The number of expected partitions was: 3, but 1 has been found instead");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoAddPartitionsDisabledSucceedsIfTopicPartitionedCorrectly() throws Exception {
|
||||
|
||||
String testTopicName = "existing" + System.currentTimeMillis();
|
||||
AdminUtils.createTopic(kafkaTestSupport.getZkClient(), testTopicName, 6, 1, new Properties());
|
||||
KafkaBinderConfigurationProperties configurationProperties = createConfigurationProperties();
|
||||
configurationProperties.setAutoAddPartitions(false);
|
||||
KafkaMessageChannelBinder binder = new KafkaMessageChannelBinder(configurationProperties);
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
RetryTemplate metatadataRetrievalRetryOperations = new RetryTemplate();
|
||||
metatadataRetrievalRetryOperations.setRetryPolicy(new SimpleRetryPolicy());
|
||||
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
|
||||
backOffPolicy.setBackOffPeriod(1000);
|
||||
metatadataRetrievalRetryOperations.setBackOffPolicy(backOffPolicy);
|
||||
binder.setMetadataRetryOperations(metatadataRetrievalRetryOperations);
|
||||
context.refresh();
|
||||
binder.setApplicationContext(context);
|
||||
binder.afterPropertiesSet();
|
||||
DirectChannel output = new DirectChannel();
|
||||
ExtendedConsumerProperties<KafkaConsumerProperties> consumerProperties = createConsumerProperties();
|
||||
// this consumer must consume from partition 2
|
||||
consumerProperties.setInstanceCount(3);
|
||||
consumerProperties.setInstanceIndex(2);
|
||||
|
||||
Binding<?> binding = binder.doBindConsumer(testTopicName, "test", output, consumerProperties);
|
||||
|
||||
Partition[] listenedPartitions = TestUtils.getPropertyValue(binding,
|
||||
"endpoint.val$messageListenerContainer.partitions", Partition[].class);
|
||||
|
||||
assertThat(listenedPartitions).hasSize(2);
|
||||
assertThat(listenedPartitions).contains(new Partition(testTopicName, 2), new Partition(testTopicName, 5));
|
||||
Collection<Partition> partitions = binder.getConnectionFactory().getPartitions(testTopicName);
|
||||
assertThat(partitions).hasSize(6);
|
||||
binding.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoCreateTopicsEnabledSucceeds() throws Exception {
|
||||
KafkaBinderConfigurationProperties configurationProperties = createConfigurationProperties();
|
||||
configurationProperties.setAutoCreateTopics(true);
|
||||
KafkaMessageChannelBinder binder = new KafkaMessageChannelBinder(configurationProperties);
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.refresh();
|
||||
binder.setApplicationContext(context);
|
||||
binder.afterPropertiesSet();
|
||||
RetryTemplate metatadataRetrievalRetryOperations = new RetryTemplate();
|
||||
metatadataRetrievalRetryOperations.setRetryPolicy(new SimpleRetryPolicy());
|
||||
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
|
||||
backOffPolicy.setBackOffPeriod(1000);
|
||||
metatadataRetrievalRetryOperations.setBackOffPolicy(backOffPolicy);
|
||||
binder.setMetadataRetryOperations(metatadataRetrievalRetryOperations);
|
||||
DirectChannel output = new DirectChannel();
|
||||
ExtendedConsumerProperties<KafkaConsumerProperties> consumerProperties = createConsumerProperties();
|
||||
String testTopicName = "nonexisting" + System.currentTimeMillis();
|
||||
Binding<?> binding = binder.doBindConsumer(testTopicName, "test", output, consumerProperties);
|
||||
binding.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPartitionCountNotReduced() throws Exception {
|
||||
String testTopicName = "existing" + System.currentTimeMillis();
|
||||
AdminUtils.createTopic(kafkaTestSupport.getZkClient(), testTopicName, 6, 1, new Properties());
|
||||
KafkaBinderConfigurationProperties configurationProperties = createConfigurationProperties();
|
||||
configurationProperties.setAutoAddPartitions(true);
|
||||
KafkaMessageChannelBinder binder = new KafkaMessageChannelBinder(configurationProperties);
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.refresh();
|
||||
binder.setApplicationContext(context);
|
||||
binder.afterPropertiesSet();
|
||||
RetryTemplate metatadataRetrievalRetryOperations = new RetryTemplate();
|
||||
metatadataRetrievalRetryOperations.setRetryPolicy(new SimpleRetryPolicy());
|
||||
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
|
||||
backOffPolicy.setBackOffPeriod(1000);
|
||||
metatadataRetrievalRetryOperations.setBackOffPolicy(backOffPolicy);
|
||||
binder.setMetadataRetryOperations(metatadataRetrievalRetryOperations);
|
||||
DirectChannel output = new DirectChannel();
|
||||
ExtendedConsumerProperties<KafkaConsumerProperties> consumerProperties = createConsumerProperties();
|
||||
Binding<?> binding = binder.doBindConsumer(testTopicName, "test", output, consumerProperties);
|
||||
binding.unbind();
|
||||
TopicMetadata topicMetadata = AdminUtils.fetchTopicMetadataFromZk(testTopicName,
|
||||
kafkaTestSupport.getZkClient());
|
||||
assertThat(topicMetadata.partitionsMetadata().size()).isEqualTo(6);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPartitionCountIncreasedIfAutoAddPartitionsSet() throws Exception {
|
||||
String testTopicName = "existing" + System.currentTimeMillis();
|
||||
AdminUtils.createTopic(kafkaTestSupport.getZkClient(), testTopicName, 1, 1, new Properties());
|
||||
KafkaBinderConfigurationProperties configurationProperties = createConfigurationProperties();
|
||||
configurationProperties.setMinPartitionCount(6);
|
||||
configurationProperties.setAutoAddPartitions(true);
|
||||
KafkaMessageChannelBinder binder = new KafkaMessageChannelBinder(configurationProperties);
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.refresh();
|
||||
binder.setApplicationContext(context);
|
||||
binder.afterPropertiesSet();
|
||||
RetryTemplate metatadataRetrievalRetryOperations = new RetryTemplate();
|
||||
metatadataRetrievalRetryOperations.setRetryPolicy(new SimpleRetryPolicy());
|
||||
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
|
||||
backOffPolicy.setBackOffPeriod(1000);
|
||||
metatadataRetrievalRetryOperations.setBackOffPolicy(backOffPolicy);
|
||||
binder.setMetadataRetryOperations(metatadataRetrievalRetryOperations);
|
||||
DirectChannel output = new DirectChannel();
|
||||
ExtendedConsumerProperties<KafkaConsumerProperties> consumerProperties = createConsumerProperties();
|
||||
Binding<?> binding = binder.doBindConsumer(testTopicName, "test", output, consumerProperties);
|
||||
binding.unbind();
|
||||
TopicMetadata topicMetadata = AdminUtils.fetchTopicMetadataFromZk(testTopicName,
|
||||
kafkaTestSupport.getZkClient());
|
||||
assertThat(topicMetadata.partitionsMetadata().size()).isEqualTo(6);
|
||||
}
|
||||
|
||||
private static final class FailingInvocationCountingMessageHandler implements MessageHandler {
|
||||
|
||||
private int invocationCount;
|
||||
|
||||
private final LinkedHashMap<Long, Message<?>> receivedMessages = new LinkedHashMap<>();
|
||||
|
||||
private final CountDownLatch latch;
|
||||
|
||||
private FailingInvocationCountingMessageHandler(int latchSize) {
|
||||
latch = new CountDownLatch(latchSize);
|
||||
}
|
||||
|
||||
private FailingInvocationCountingMessageHandler() {
|
||||
this(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
invocationCount++;
|
||||
Long offset = message.getHeaders().get(KafkaHeaders.OFFSET, Long.class);
|
||||
// using the offset as key allows to ensure that we don't store duplicate
|
||||
// messages on retry
|
||||
if (!receivedMessages.containsKey(offset)) {
|
||||
receivedMessages.put(offset, message);
|
||||
latch.countDown();
|
||||
}
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public LinkedHashMap<Long, Message<?>> getReceivedMessages() {
|
||||
return receivedMessages;
|
||||
}
|
||||
|
||||
public int getInvocationCount() {
|
||||
return invocationCount;
|
||||
}
|
||||
|
||||
public CountDownLatch getLatch() {
|
||||
return latch;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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 java.util.List;
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo;
|
||||
import com.esotericsoftware.kryo.Registration;
|
||||
|
||||
import org.springframework.cloud.stream.binder.AbstractTestBinder;
|
||||
import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.ExtendedProducerProperties;
|
||||
import org.springframework.cloud.stream.binder.kafka.config.KafkaBinderConfigurationProperties;
|
||||
import org.springframework.cloud.stream.test.junit.kafka.TestKafkaCluster;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.integration.codec.Codec;
|
||||
import org.springframework.integration.codec.kryo.KryoRegistrar;
|
||||
import org.springframework.integration.codec.kryo.PojoCodec;
|
||||
import org.springframework.integration.kafka.support.LoggingProducerListener;
|
||||
import org.springframework.integration.kafka.support.ProducerListener;
|
||||
import org.springframework.integration.tuple.TupleKryoRegistrar;
|
||||
|
||||
/**
|
||||
* Test support class for {@link KafkaMessageChannelBinder}. Creates a binder that uses a
|
||||
* test {@link TestKafkaCluster kafka cluster}.
|
||||
* @author Eric Bottard
|
||||
* @author Marius Bogoevici
|
||||
* @author David Turanski
|
||||
* @author Gary Russell
|
||||
* @author Soby Chacko
|
||||
*/
|
||||
public class KafkaTestBinder extends
|
||||
AbstractTestBinder<KafkaMessageChannelBinder, ExtendedConsumerProperties<KafkaConsumerProperties>, ExtendedProducerProperties<KafkaProducerProperties>> {
|
||||
|
||||
public KafkaTestBinder(KafkaBinderConfigurationProperties binderConfiguration) {
|
||||
try {
|
||||
KafkaMessageChannelBinder binder = new KafkaMessageChannelBinder(binderConfiguration);
|
||||
binder.setCodec(getCodec());
|
||||
ProducerListener producerListener = new LoggingProducerListener();
|
||||
binder.setProducerListener(producerListener);
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.refresh();
|
||||
binder.setApplicationContext(context);
|
||||
binder.afterPropertiesSet();
|
||||
this.setBinder(binder);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
// do nothing - the rule will take care of that
|
||||
}
|
||||
|
||||
private static Codec getCodec() {
|
||||
return new PojoCodec(new TupleRegistrar());
|
||||
}
|
||||
|
||||
private static class TupleRegistrar implements KryoRegistrar {
|
||||
private final TupleKryoRegistrar delegate = new TupleKryoRegistrar();
|
||||
|
||||
@Override
|
||||
public void registerTypes(Kryo kryo) {
|
||||
this.delegate.registerTypes(kryo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Registration> getRegistrations() {
|
||||
return this.delegate.getRegistrations();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 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.PartitionKeyExtractorStrategy;
|
||||
import org.springframework.cloud.stream.binder.PartitionSelectorStrategy;
|
||||
import org.springframework.messaging.Message;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class RawKafkaPartitionTestSupport implements PartitionKeyExtractorStrategy, PartitionSelectorStrategy {
|
||||
|
||||
@Override
|
||||
public int selectPartition(Object key, int divisor) {
|
||||
return ((byte[]) key)[0] % divisor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object extractKey(Message<?> message) {
|
||||
return message.getPayload();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,262 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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 java.util.Arrays;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.ExtendedProducerProperties;
|
||||
import org.springframework.cloud.stream.binder.HeaderMode;
|
||||
import org.springframework.integration.IntegrationMessageHeaderAccessor;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.endpoint.AbstractEndpoint;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author David Turanski
|
||||
* @author Gary Russell
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class RawModeKafkaBinderTests extends KafkaBinderTests {
|
||||
|
||||
@Test
|
||||
@Override
|
||||
public void testPartitionedModuleJava() throws Exception {
|
||||
KafkaTestBinder binder = getBinder();
|
||||
ExtendedProducerProperties<KafkaProducerProperties> properties = createProducerProperties();
|
||||
properties.setHeaderMode(HeaderMode.raw);
|
||||
properties.setPartitionKeyExtractorClass(RawKafkaPartitionTestSupport.class);
|
||||
properties.setPartitionSelectorClass(RawKafkaPartitionTestSupport.class);
|
||||
properties.setPartitionCount(6);
|
||||
|
||||
DirectChannel output = new DirectChannel();
|
||||
output.setBeanName("test.output");
|
||||
Binding<MessageChannel> outputBinding = binder.bindProducer("partJ.0", output, properties);
|
||||
|
||||
ExtendedConsumerProperties<KafkaConsumerProperties> consumerProperties = createConsumerProperties();
|
||||
consumerProperties.setConcurrency(2);
|
||||
consumerProperties.setInstanceCount(3);
|
||||
consumerProperties.setInstanceIndex(0);
|
||||
consumerProperties.setPartitioned(true);
|
||||
consumerProperties.setHeaderMode(HeaderMode.raw);
|
||||
QueueChannel input0 = new QueueChannel();
|
||||
input0.setBeanName("test.input0J");
|
||||
Binding<MessageChannel> input0Binding = binder.bindConsumer("partJ.0", "test", input0, consumerProperties);
|
||||
consumerProperties.setInstanceIndex(1);
|
||||
QueueChannel input1 = new QueueChannel();
|
||||
input1.setBeanName("test.input1J");
|
||||
Binding<MessageChannel> input1Binding = binder.bindConsumer("partJ.0", "test", input1, consumerProperties);
|
||||
consumerProperties.setInstanceIndex(2);
|
||||
QueueChannel input2 = new QueueChannel();
|
||||
input2.setBeanName("test.input2J");
|
||||
Binding<MessageChannel> input2Binding = binder.bindConsumer("partJ.0", "test", input2, consumerProperties);
|
||||
|
||||
output.send(new GenericMessage<>(new byte[] { (byte) 0 }));
|
||||
output.send(new GenericMessage<>(new byte[] { (byte) 1 }));
|
||||
output.send(new GenericMessage<>(new byte[] { (byte) 2 }));
|
||||
|
||||
Message<?> receive0 = receive(input0);
|
||||
assertThat(receive0).isNotNull();
|
||||
Message<?> receive1 = receive(input1);
|
||||
assertThat(receive1).isNotNull();
|
||||
Message<?> receive2 = receive(input2);
|
||||
assertThat(receive2).isNotNull();
|
||||
|
||||
assertThat(Arrays.asList(((byte[]) receive0.getPayload())[0], ((byte[]) receive1.getPayload())[0],
|
||||
((byte[]) receive2.getPayload())[0])).containsExactlyInAnyOrder((byte) 0, (byte) 1, (byte) 2);
|
||||
|
||||
input0Binding.unbind();
|
||||
input1Binding.unbind();
|
||||
input2Binding.unbind();
|
||||
outputBinding.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Override
|
||||
public void testPartitionedModuleSpEL() throws Exception {
|
||||
KafkaTestBinder binder = getBinder();
|
||||
ExtendedProducerProperties<KafkaProducerProperties> properties = createProducerProperties();
|
||||
properties.setPartitionKeyExpression(spelExpressionParser.parseExpression("payload[0]"));
|
||||
properties.setPartitionSelectorExpression(spelExpressionParser.parseExpression("hashCode()"));
|
||||
properties.setPartitionCount(6);
|
||||
properties.setHeaderMode(HeaderMode.raw);
|
||||
|
||||
DirectChannel output = new DirectChannel();
|
||||
output.setBeanName("test.output");
|
||||
Binding<MessageChannel> outputBinding = binder.bindProducer("part.0", output, properties);
|
||||
try {
|
||||
AbstractEndpoint endpoint = extractEndpoint(outputBinding);
|
||||
assertThat(getEndpointRouting(endpoint)).contains("part.0-' + headers['partition']");
|
||||
}
|
||||
catch (UnsupportedOperationException ignored) {
|
||||
|
||||
}
|
||||
|
||||
ExtendedConsumerProperties<KafkaConsumerProperties> consumerProperties = createConsumerProperties();
|
||||
consumerProperties.setConcurrency(2);
|
||||
consumerProperties.setInstanceIndex(0);
|
||||
consumerProperties.setInstanceCount(3);
|
||||
consumerProperties.setPartitioned(true);
|
||||
consumerProperties.setHeaderMode(HeaderMode.raw);
|
||||
QueueChannel input0 = new QueueChannel();
|
||||
input0.setBeanName("test.input0S");
|
||||
Binding<MessageChannel> input0Binding = binder.bindConsumer("part.0", "test", input0, consumerProperties);
|
||||
consumerProperties.setInstanceIndex(1);
|
||||
QueueChannel input1 = new QueueChannel();
|
||||
input1.setBeanName("test.input1S");
|
||||
Binding<MessageChannel> input1Binding = binder.bindConsumer("part.0", "test", input1, consumerProperties);
|
||||
consumerProperties.setInstanceIndex(2);
|
||||
QueueChannel input2 = new QueueChannel();
|
||||
input2.setBeanName("test.input2S");
|
||||
Binding<MessageChannel> input2Binding = binder.bindConsumer("part.0", "test", input2, consumerProperties);
|
||||
|
||||
Message<byte[]> message2 = MessageBuilder.withPayload(new byte[] { 2 })
|
||||
.setHeader(IntegrationMessageHeaderAccessor.CORRELATION_ID, "foo")
|
||||
.setHeader(IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER, 42)
|
||||
.setHeader(IntegrationMessageHeaderAccessor.SEQUENCE_SIZE, 43).build();
|
||||
output.send(message2);
|
||||
output.send(new GenericMessage<>(new byte[] { 1 }));
|
||||
output.send(new GenericMessage<>(new byte[] { 0 }));
|
||||
Message<?> receive0 = receive(input0);
|
||||
assertThat(receive0).isNotNull();
|
||||
Message<?> receive1 = receive(input1);
|
||||
assertThat(receive1).isNotNull();
|
||||
Message<?> receive2 = receive(input2);
|
||||
assertThat(receive2).isNotNull();
|
||||
assertThat(Arrays.asList(((byte[]) receive0.getPayload())[0], ((byte[]) receive1.getPayload())[0],
|
||||
((byte[]) receive2.getPayload())[0])).containsExactlyInAnyOrder((byte) 0, (byte) 1, (byte) 2);
|
||||
input0Binding.unbind();
|
||||
input1Binding.unbind();
|
||||
input2Binding.unbind();
|
||||
outputBinding.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Override
|
||||
public void testSendAndReceive() throws Exception {
|
||||
KafkaTestBinder binder = getBinder();
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
ExtendedProducerProperties<KafkaProducerProperties> producerProperties = createProducerProperties();
|
||||
producerProperties.setHeaderMode(HeaderMode.raw);
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("foo.0", moduleOutputChannel, producerProperties);
|
||||
ExtendedConsumerProperties<KafkaConsumerProperties> consumerProperties = createConsumerProperties();
|
||||
consumerProperties.setHeaderMode(HeaderMode.raw);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("foo.0", "test", moduleInputChannel,
|
||||
consumerProperties);
|
||||
Message<?> message = MessageBuilder.withPayload("foo".getBytes()).build();
|
||||
// Let the consumer actually bind to the producer before sending a msg
|
||||
binderBindUnbindLatency();
|
||||
moduleOutputChannel.send(message);
|
||||
Message<?> inbound = receive(moduleInputChannel);
|
||||
assertThat(inbound).isNotNull();
|
||||
assertThat(new String((byte[]) inbound.getPayload())).isEqualTo("foo");
|
||||
producerBinding.unbind();
|
||||
consumerBinding.unbind();
|
||||
}
|
||||
|
||||
// Ignored, since raw mode does not support headers
|
||||
@Test
|
||||
@Override
|
||||
@Ignore
|
||||
public void testSendAndReceiveNoOriginalContentType() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendAndReceiveWithExplicitConsumerGroup() {
|
||||
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();
|
||||
ExtendedProducerProperties<KafkaProducerProperties> producerProperties = createProducerProperties();
|
||||
producerProperties.setHeaderMode(HeaderMode.raw);
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("baz.0", moduleOutputChannel, producerProperties);
|
||||
ExtendedConsumerProperties<KafkaConsumerProperties> consumerProperties = createConsumerProperties();
|
||||
consumerProperties.setHeaderMode(HeaderMode.raw);
|
||||
Binding<MessageChannel> input1Binding = binder.bindConsumer("baz.0", "test", module1InputChannel,
|
||||
consumerProperties);
|
||||
// A new module is using the tap as an input channel
|
||||
String fooTapName = "baz.0";
|
||||
Binding<MessageChannel> input2Binding = binder.bindConsumer(fooTapName, "tap1", module2InputChannel,
|
||||
consumerProperties);
|
||||
// Another new module is using tap as an input channel
|
||||
String barTapName = "baz.0";
|
||||
Binding<MessageChannel> input3Binding = binder.bindConsumer(barTapName, "tap2", module3InputChannel,
|
||||
consumerProperties);
|
||||
|
||||
Message<?> message = MessageBuilder.withPayload("foo".getBytes()).build();
|
||||
boolean success = false;
|
||||
boolean retried = false;
|
||||
while (!success) {
|
||||
moduleOutputChannel.send(message);
|
||||
Message<?> inbound = receive(module1InputChannel);
|
||||
assertThat(inbound).isNotNull();
|
||||
assertThat(new String((byte[]) inbound.getPayload())).isEqualTo("foo");
|
||||
|
||||
Message<?> tapped1 = receive(module2InputChannel);
|
||||
Message<?> tapped2 = receive(module3InputChannel);
|
||||
if (tapped1 == null || tapped2 == null) {
|
||||
// listener may not have started
|
||||
assertThat(retried).isFalse().withFailMessage("Failed to receive tap after retry");
|
||||
retried = true;
|
||||
continue;
|
||||
}
|
||||
success = true;
|
||||
assertThat(new String((byte[]) tapped1.getPayload())).isEqualTo("foo");
|
||||
assertThat(new String((byte[]) tapped2.getPayload())).isEqualTo("foo");
|
||||
}
|
||||
// delete one tap stream is deleted
|
||||
input3Binding.unbind();
|
||||
Message<?> message2 = MessageBuilder.withPayload("bar".getBytes()).build();
|
||||
moduleOutputChannel.send(message2);
|
||||
|
||||
// other tap still receives messages
|
||||
Message<?> tapped = receive(module2InputChannel);
|
||||
assertThat(tapped).isNotNull();
|
||||
|
||||
// removed tap does not
|
||||
assertThat(receive(module3InputChannel)).isNull();
|
||||
|
||||
// re-subscribed tap does receive the message
|
||||
input3Binding = binder.bindConsumer(barTapName, "tap2", module3InputChannel, createConsumerProperties());
|
||||
assertThat(receive(module3InputChannel)).isNotNull();
|
||||
|
||||
// clean up
|
||||
input1Binding.unbind();
|
||||
input2Binding.unbind();
|
||||
input3Binding.unbind();
|
||||
producerBinding.unbind();
|
||||
assertThat(extractEndpoint(input1Binding).isRunning()).isFalse();
|
||||
assertThat(extractEndpoint(input2Binding).isRunning()).isFalse();
|
||||
assertThat(extractEndpoint(input3Binding).isRunning()).isFalse();
|
||||
assertThat(extractEndpoint(producerBinding).isRunning()).isFalse();
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>spring-cloud-stream-binder-rabbit</name>
|
||||
<description>RabbitMQ binder implementation</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-binders-parent</artifactId>
|
||||
<version>1.1.0.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-codec</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-core</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-binder-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-test-support-internal</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-amqp</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,36 +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;
|
||||
|
||||
|
||||
/**
|
||||
* Exceptions thrown while interfacing with the RabbitMQ admin plugin.
|
||||
* @author Gary Russell
|
||||
* @since 1.2
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class RabbitAdminException extends RuntimeException {
|
||||
|
||||
public RabbitAdminException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public RabbitAdminException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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 java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cloud.stream.binder.AbstractBinder;
|
||||
import org.springframework.cloud.stream.binder.BindingCleaner;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of {@link org.springframework.cloud.stream.binder.BindingCleaner} for the {@code RabbitBinder}.
|
||||
* @author Gary Russell
|
||||
* @author David Turanski
|
||||
* @since 1.2
|
||||
*/
|
||||
public class RabbitBindingCleaner implements BindingCleaner {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(RabbitBindingCleaner.class);
|
||||
|
||||
private static final String PREFIX_DELIMITER = ".";
|
||||
|
||||
public static final String BINDER_PREFIX = "binder" + PREFIX_DELIMITER;
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> clean(String entity, boolean isJob) {
|
||||
return clean("http://localhost:15672", "guest", "guest", "/", BINDER_PREFIX, entity, isJob);
|
||||
}
|
||||
|
||||
public Map<String, List<String>> clean(String adminUri, String user, String pw, String vhost,
|
||||
String binderPrefix, String entity, boolean isJob) {
|
||||
return doClean(
|
||||
adminUri == null ? "http://localhost:15672" : adminUri,
|
||||
user == null ? "guest" : user,
|
||||
pw == null ? "guest" : pw,
|
||||
vhost == null ? "/" : vhost,
|
||||
binderPrefix == null ? BINDER_PREFIX : binderPrefix,
|
||||
entity, isJob);
|
||||
}
|
||||
|
||||
private Map<String, List<String>> doClean(String adminUri, String user, String pw, String vhost,
|
||||
String binderPrefix, String entity, boolean isJob) {
|
||||
RestTemplate restTemplate = RabbitManagementUtils.buildRestTemplate(adminUri, user, pw);
|
||||
List<String> removedQueues = isJob
|
||||
? null
|
||||
: findStreamQueues(adminUri, vhost, binderPrefix, entity, restTemplate);
|
||||
List<String> removedExchanges = findExchanges(adminUri, vhost, binderPrefix, entity, restTemplate);
|
||||
// Delete the queues in reverse order to enable re-running after a partial success.
|
||||
// The queue search above starts with 0 and terminates on a not found.
|
||||
for (int i = removedQueues.size() - 1; i >= 0; i--) {
|
||||
String queueName = removedQueues.get(i);
|
||||
URI uri = UriComponentsBuilder.fromUriString(adminUri + "/api")
|
||||
.pathSegment("queues", "{vhost}", "{stream}")
|
||||
.buildAndExpand(vhost, queueName).encode().toUri();
|
||||
restTemplate.delete(uri);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("deleted queue: " + queueName);
|
||||
}
|
||||
}
|
||||
Map<String, List<String>> results = new HashMap<>();
|
||||
if (removedQueues.size() > 0) {
|
||||
results.put("queues", removedQueues);
|
||||
}
|
||||
// Fanout exchanges for taps
|
||||
for (String exchange : removedExchanges) {
|
||||
URI uri = UriComponentsBuilder.fromUriString(adminUri + "/api")
|
||||
.pathSegment("exchanges", "{vhost}", "{name}")
|
||||
.buildAndExpand(vhost, exchange).encode().toUri();
|
||||
restTemplate.delete(uri);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("deleted exchange: " + exchange);
|
||||
}
|
||||
}
|
||||
if (removedExchanges.size() > 0) {
|
||||
results.put("exchanges", removedExchanges);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private List<String> findStreamQueues(String adminUri, String vhost, String binderPrefix, String stream,
|
||||
RestTemplate restTemplate) {
|
||||
String queueNamePrefix = adjustPrefix(AbstractBinder.applyPrefix(binderPrefix, stream));
|
||||
List<Map<String, Object>> queues = listAllQueues(adminUri, vhost, restTemplate);
|
||||
List<String> removedQueues = new ArrayList<>();
|
||||
for (Map<String, Object> queue : queues) {
|
||||
String queueName = (String) queue.get("name");
|
||||
if (queueName.startsWith(queueNamePrefix)) {
|
||||
checkNoConsumers(queueName, queue);
|
||||
removedQueues.add(queueName);
|
||||
}
|
||||
}
|
||||
return removedQueues;
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> listAllQueues(String adminUri, String vhost, RestTemplate restTemplate) {
|
||||
URI uri = UriComponentsBuilder.fromUriString(adminUri + "/api")
|
||||
.pathSegment("queues", "{vhost}")
|
||||
.buildAndExpand(vhost).encode().toUri();
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> queues = restTemplate.getForObject(uri, List.class);
|
||||
return queues;
|
||||
}
|
||||
|
||||
private String adjustPrefix(String prefix) {
|
||||
if (prefix.endsWith("*")) {
|
||||
return prefix.substring(0, prefix.length() - 1);
|
||||
}
|
||||
else {
|
||||
return prefix + PREFIX_DELIMITER;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkNoConsumers(String queueName, Map<String, Object> queue) {
|
||||
if (!queue.get("consumers").equals(Integer.valueOf(0))) {
|
||||
throw new RabbitAdminException("Queue " + queueName + " is in use");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<String> findExchanges(String adminUri, String vhost, String binderPrefix, String entity,
|
||||
RestTemplate restTemplate) {
|
||||
List<String> removedExchanges = new ArrayList<>();
|
||||
URI uri = UriComponentsBuilder.fromUriString(adminUri + "/api")
|
||||
.pathSegment("exchanges", "{vhost}")
|
||||
.buildAndExpand(vhost).encode().toUri();
|
||||
List<Map<String, Object>> exchanges = restTemplate.getForObject(uri, List.class);
|
||||
String exchangeNamePrefix = adjustPrefix(AbstractBinder.applyPrefix(binderPrefix, entity));
|
||||
for (Map<String, Object> exchange : exchanges) {
|
||||
String exchangeName = (String) exchange.get("name");
|
||||
if (exchangeName.startsWith(exchangeNamePrefix)) {
|
||||
uri = UriComponentsBuilder.fromUriString(adminUri + "/api")
|
||||
.pathSegment("exchanges", "{vhost}", "{name}", "bindings", "source")
|
||||
.buildAndExpand(vhost, exchangeName).encode().toUri();
|
||||
List<Map<String, Object>> bindings = restTemplate.getForObject(uri, List.class);
|
||||
if (hasNoForeignBindings(bindings, exchangeNamePrefix)) {
|
||||
uri = UriComponentsBuilder.fromUriString(adminUri + "/api")
|
||||
.pathSegment("exchanges", "{vhost}", "{name}", "bindings", "destination")
|
||||
.buildAndExpand(vhost, exchangeName).encode().toUri();
|
||||
bindings = restTemplate.getForObject(uri, List.class);
|
||||
if (bindings.size() == 0) {
|
||||
removedExchanges.add((String) exchange.get("name"));
|
||||
}
|
||||
else {
|
||||
throw new RabbitAdminException("Cannot delete exchange " + exchangeName
|
||||
+ "; it is a destination: " + bindings);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new RabbitAdminException("Cannot delete exchange " + exchangeName + "; it has bindings: "
|
||||
+ bindings);
|
||||
}
|
||||
}
|
||||
}
|
||||
return removedExchanges;
|
||||
}
|
||||
|
||||
private boolean hasNoForeignBindings(List<Map<String, Object>> bindings, String exchangeNamePrefix) {
|
||||
if (bindings.size() == 0) {
|
||||
return true;
|
||||
}
|
||||
boolean noForeign = true;
|
||||
for (Map<String, Object> binding : bindings) {
|
||||
if (!("queue".equals(binding.get("destination_type")))
|
||||
|| !((String) binding.get("destination")).startsWith(exchangeNamePrefix)) {
|
||||
noForeign = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return noForeign;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,43 +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.rabbit;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class RabbitBindingProperties {
|
||||
|
||||
private RabbitConsumerProperties consumer = new RabbitConsumerProperties();
|
||||
|
||||
private RabbitProducerProperties producer = new RabbitProducerProperties();
|
||||
|
||||
public RabbitConsumerProperties getConsumer() {
|
||||
return consumer;
|
||||
}
|
||||
|
||||
public void setConsumer(RabbitConsumerProperties consumer) {
|
||||
this.consumer = consumer;
|
||||
}
|
||||
|
||||
public RabbitProducerProperties getProducer() {
|
||||
return producer;
|
||||
}
|
||||
|
||||
public void setProducer(RabbitProducerProperties producer) {
|
||||
this.producer = producer;
|
||||
}
|
||||
}
|
||||
@@ -1,162 +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.rabbit;
|
||||
|
||||
import javax.validation.constraints.Min;
|
||||
|
||||
import org.springframework.amqp.core.AcknowledgeMode;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class RabbitConsumerProperties {
|
||||
|
||||
private String prefix = "";
|
||||
|
||||
private boolean transacted;
|
||||
|
||||
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;
|
||||
|
||||
private boolean durableSubscription = true;
|
||||
|
||||
private boolean republishToDlq;
|
||||
|
||||
private boolean requeueRejected = true;
|
||||
|
||||
private String[] replyHeaderPatterns = new String[] {"STANDARD_REPLY_HEADERS", "*"};
|
||||
|
||||
private long recoveryInterval = 5000;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@Min(value = 1, message = "Max Concurrency should be greater than zero.")
|
||||
public int getMaxConcurrency() {
|
||||
return maxConcurrency;
|
||||
}
|
||||
|
||||
public void setMaxConcurrency(int maxConcurrency) {
|
||||
this.maxConcurrency = maxConcurrency;
|
||||
}
|
||||
|
||||
@Min(value = 1, message = "Prefetch should be greater than zero.")
|
||||
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;
|
||||
}
|
||||
|
||||
@Min(value = 1, message = "Tx Size should be greater than zero.")
|
||||
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;
|
||||
}
|
||||
|
||||
public long getRecoveryInterval() {
|
||||
return recoveryInterval;
|
||||
}
|
||||
|
||||
public void setRecoveryInterval(long recoveryInterval) {
|
||||
this.recoveryInterval = recoveryInterval;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +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.rabbit;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.stream.binder.ExtendedBindingProperties;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
@ConfigurationProperties("spring.cloud.stream.rabbit")
|
||||
public class RabbitExtendedBindingProperties implements ExtendedBindingProperties<RabbitConsumerProperties, RabbitProducerProperties> {
|
||||
|
||||
private Map<String, RabbitBindingProperties> bindings = new HashMap<>();
|
||||
|
||||
public Map<String, RabbitBindingProperties> getBindings() {
|
||||
return bindings;
|
||||
}
|
||||
|
||||
public void setBindings(Map<String, RabbitBindingProperties> bindings) {
|
||||
this.bindings = bindings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RabbitConsumerProperties getExtendedConsumerProperties(String channelName) {
|
||||
if (bindings.containsKey(channelName) && bindings.get(channelName).getConsumer() != null) {
|
||||
return bindings.get(channelName).getConsumer();
|
||||
}
|
||||
else {
|
||||
return new RabbitConsumerProperties();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RabbitProducerProperties getExtendedProducerProperties(String channelName) {
|
||||
if (bindings.containsKey(channelName) && bindings.get(channelName).getProducer() != null) {
|
||||
return bindings.get(channelName).getProducer();
|
||||
}
|
||||
else {
|
||||
return new RabbitProducerProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,83 +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;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.auth.AuthScope;
|
||||
import org.apache.http.auth.UsernamePasswordCredentials;
|
||||
import org.apache.http.client.AuthCache;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.protocol.HttpClientContext;
|
||||
import org.apache.http.impl.auth.BasicScheme;
|
||||
import org.apache.http.impl.client.BasicAuthCache;
|
||||
import org.apache.http.impl.client.BasicCredentialsProvider;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @since 1.2
|
||||
*/
|
||||
public abstract class RabbitManagementUtils {
|
||||
|
||||
public static RestTemplate buildRestTemplate(String adminUri, String user, String password) {
|
||||
BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
|
||||
credsProvider.setCredentials(
|
||||
new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT),
|
||||
new UsernamePasswordCredentials(user, password));
|
||||
HttpClient httpClient = HttpClients.custom().setDefaultCredentialsProvider(credsProvider).build();
|
||||
// Set up pre-emptive basic Auth because the rabbit plugin doesn't currently support challenge/response for PUT
|
||||
// Create AuthCache instance
|
||||
AuthCache authCache = new BasicAuthCache();
|
||||
// Generate BASIC scheme object and add it to the local; from the apache docs...
|
||||
// auth cache
|
||||
BasicScheme basicAuth = new BasicScheme();
|
||||
URI uri;
|
||||
try {
|
||||
uri = new URI(adminUri);
|
||||
}
|
||||
catch (URISyntaxException e) {
|
||||
throw new RabbitAdminException("Invalid URI", e);
|
||||
}
|
||||
authCache.put(new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme()), basicAuth);
|
||||
// Add AuthCache to the execution context
|
||||
final HttpClientContext localContext = HttpClientContext.create();
|
||||
localContext.setAuthCache(authCache);
|
||||
RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpClient) {
|
||||
|
||||
@Override
|
||||
protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) {
|
||||
return localContext;
|
||||
}
|
||||
|
||||
});
|
||||
restTemplate.setMessageConverters(Collections.<HttpMessageConverter<?>>singletonList(
|
||||
new MappingJackson2HttpMessageConverter()));
|
||||
return restTemplate;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,651 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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 java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
|
||||
import com.rabbitmq.client.AMQP;
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.rabbitmq.client.Envelope;
|
||||
import org.aopalliance.aop.Advice;
|
||||
|
||||
import org.springframework.amqp.AmqpConnectException;
|
||||
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.MessagePostProcessor;
|
||||
import org.springframework.amqp.core.MessageProperties;
|
||||
import org.springframework.amqp.core.Queue;
|
||||
import org.springframework.amqp.core.TopicExchange;
|
||||
import org.springframework.amqp.rabbit.config.RetryInterceptorBuilder;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.connection.LocalizedQueueConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.core.BatchingRabbitTemplate;
|
||||
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.amqp.rabbit.core.support.BatchingStrategy;
|
||||
import org.springframework.amqp.rabbit.core.support.SimpleBatchingStrategy;
|
||||
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||
import org.springframework.amqp.rabbit.retry.MessageRecoverer;
|
||||
import org.springframework.amqp.rabbit.retry.RejectAndDontRequeueRecoverer;
|
||||
import org.springframework.amqp.rabbit.retry.RepublishMessageRecoverer;
|
||||
import org.springframework.amqp.rabbit.support.DefaultMessagePropertiesConverter;
|
||||
import org.springframework.amqp.rabbit.support.MessagePropertiesConverter;
|
||||
import org.springframework.amqp.support.AmqpHeaders;
|
||||
import org.springframework.amqp.support.postprocessor.DelegatingDecompressingPostProcessor;
|
||||
import org.springframework.amqp.support.postprocessor.GZipPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
|
||||
import org.springframework.cloud.stream.binder.AbstractBinder;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.DefaultBinding;
|
||||
import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.ExtendedProducerProperties;
|
||||
import org.springframework.cloud.stream.binder.ExtendedPropertiesBinder;
|
||||
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.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.integration.amqp.inbound.AmqpInboundChannelAdapter;
|
||||
import org.springframework.integration.amqp.outbound.AmqpOutboundEndpoint;
|
||||
import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.context.IntegrationContextUtils;
|
||||
import org.springframework.integration.endpoint.EventDrivenConsumer;
|
||||
import org.springframework.integration.handler.AbstractMessageHandler;
|
||||
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.retry.interceptor.RetryOperationsInterceptor;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.cloud.stream.binder.Binder} implementation backed by RabbitMQ.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
* @author Jennifer Hickey
|
||||
* @author Gunnar Hillert
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author David Turanski
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class RabbitMessageChannelBinder extends AbstractBinder<MessageChannel, ExtendedConsumerProperties<RabbitConsumerProperties>,
|
||||
ExtendedProducerProperties<RabbitProducerProperties>> implements ExtendedPropertiesBinder<MessageChannel, RabbitConsumerProperties, RabbitProducerProperties> {
|
||||
|
||||
public static final AnonymousQueue.Base64UrlNamingStrategy ANONYMOUS_GROUP_NAME_GENERATOR
|
||||
= new AnonymousQueue.Base64UrlNamingStrategy("anonymous.");
|
||||
|
||||
private static final String DEAD_LETTER_EXCHANGE = "DLX";
|
||||
|
||||
private static final MessagePropertiesConverter inboundMessagePropertiesConverter =
|
||||
new DefaultMessagePropertiesConverter() {
|
||||
|
||||
@Override
|
||||
public MessageProperties toMessageProperties(AMQP.BasicProperties source, Envelope envelope,
|
||||
String charset) {
|
||||
MessageProperties properties = super.toMessageProperties(source, envelope, charset);
|
||||
properties.setDeliveryMode(null);
|
||||
return properties;
|
||||
}
|
||||
};
|
||||
|
||||
private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
|
||||
|
||||
private final RabbitAdmin rabbitAdmin;
|
||||
|
||||
private final GenericApplicationContext autoDeclareContext = new GenericApplicationContext();
|
||||
|
||||
private final RabbitProperties rabbitProperties;
|
||||
|
||||
private ConnectionFactory connectionFactory;
|
||||
|
||||
private MessagePostProcessor decompressingPostProcessor = new DelegatingDecompressingPostProcessor();
|
||||
|
||||
private MessagePostProcessor compressingPostProcessor = new GZipPostProcessor();
|
||||
|
||||
private volatile String[] adminAddresses;
|
||||
|
||||
private volatile String[] nodes;
|
||||
|
||||
private volatile boolean clustered;
|
||||
|
||||
private RabbitExtendedBindingProperties extendedBindingProperties = new RabbitExtendedBindingProperties();
|
||||
|
||||
public RabbitMessageChannelBinder(ConnectionFactory connectionFactory, RabbitProperties rabbitProperties) {
|
||||
Assert.notNull(connectionFactory, "connectionFactory must not be null");
|
||||
Assert.notNull(rabbitProperties, "rabbitProperties must not be null");
|
||||
this.connectionFactory = connectionFactory;
|
||||
this.rabbitProperties = rabbitProperties;
|
||||
this.rabbitAdmin = new RabbitAdmin(connectionFactory);
|
||||
this.autoDeclareContext.refresh();
|
||||
this.rabbitAdmin.setApplicationContext(this.autoDeclareContext);
|
||||
this.rabbitAdmin.setIgnoreDeclarationExceptions(true);
|
||||
this.rabbitAdmin.afterPropertiesSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a {@link MessagePostProcessor} to decompress messages. Defaults to a
|
||||
* {@link DelegatingDecompressingPostProcessor} with its default delegates.
|
||||
* @param decompressingPostProcessor the post processor.
|
||||
*/
|
||||
public void setDecompressingPostProcessor(MessagePostProcessor decompressingPostProcessor) {
|
||||
this.decompressingPostProcessor = decompressingPostProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a {@link org.springframework.amqp.core.MessagePostProcessor} to compress messages. Defaults to a
|
||||
* {@link org.springframework.amqp.support.postprocessor.GZipPostProcessor}.
|
||||
* @param compressingPostProcessor the post processor.
|
||||
*/
|
||||
public void setCompressingPostProcessor(MessagePostProcessor compressingPostProcessor) {
|
||||
this.compressingPostProcessor = compressingPostProcessor;
|
||||
}
|
||||
|
||||
public void setAdminAddresses(String[] adminAddresses) {
|
||||
this.adminAddresses = Arrays.copyOf(adminAddresses, adminAddresses.length);
|
||||
}
|
||||
|
||||
public void setNodes(String[] nodes) {
|
||||
this.nodes = Arrays.copyOf(nodes, nodes.length);
|
||||
this.clustered = nodes.length > 1;
|
||||
}
|
||||
|
||||
public void setExtendedBindingProperties(RabbitExtendedBindingProperties extendedBindingProperties) {
|
||||
this.extendedBindingProperties = extendedBindingProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInit() {
|
||||
if (this.clustered) {
|
||||
String[] addresses = StringUtils.commaDelimitedListToStringArray(this.rabbitProperties.getAddresses());
|
||||
Assert.state(addresses.length == this.adminAddresses.length
|
||||
&& addresses.length == this.nodes.length,
|
||||
"'addresses', 'adminAddresses', and 'nodes' properties must have equal length");
|
||||
this.connectionFactory = new LocalizedQueueConnectionFactory(this.connectionFactory, addresses,
|
||||
this.adminAddresses, this.nodes, rabbitProperties.getVirtualHost(),
|
||||
this.rabbitProperties.getUsername(), this.rabbitProperties.getPassword(),
|
||||
this.rabbitProperties.getSsl().isEnabled(), this.rabbitProperties.getSsl().getKeyStore(),
|
||||
this.rabbitProperties.getSsl().getTrustStore(), this.rabbitProperties.getSsl().getKeyStorePassword(),
|
||||
this.rabbitProperties.getSsl().getTrustStorePassword());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RabbitConsumerProperties getExtendedConsumerProperties(String channelName) {
|
||||
return extendedBindingProperties.getExtendedConsumerProperties(channelName);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RabbitProducerProperties getExtendedProducerProperties(String channelName) {
|
||||
return extendedBindingProperties.getExtendedProducerProperties(channelName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Binding<MessageChannel> doBindConsumer(String name, String group, MessageChannel inputChannel,
|
||||
ExtendedConsumerProperties<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);
|
||||
}
|
||||
String prefix = properties.getExtension().getPrefix();
|
||||
String exchangeName = applyPrefix(prefix, name);
|
||||
TopicExchange exchange = new TopicExchange(exchangeName);
|
||||
declareExchange(exchangeName, exchange);
|
||||
|
||||
String queueName = applyPrefix(prefix, baseQueueName);
|
||||
boolean partitioned = !anonymousConsumer && properties.isPartitioned();
|
||||
boolean durable = !anonymousConsumer && properties.getExtension().isDurableSubscription();
|
||||
Queue queue;
|
||||
|
||||
if (anonymousConsumer) {
|
||||
queue = new Queue(queueName, false, true, true);
|
||||
}
|
||||
else {
|
||||
if (partitioned) {
|
||||
String partitionSuffix = "-" + properties.getInstanceIndex();
|
||||
queueName += partitionSuffix;
|
||||
}
|
||||
if (durable) {
|
||||
queue = new Queue(queueName, true, false, false,
|
||||
queueArgs(queueName, properties.getExtension().getPrefix(), properties.getExtension().isAutoBindDlq()));
|
||||
}
|
||||
else {
|
||||
queue = new Queue(queueName, false, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
declareQueue(queueName, queue);
|
||||
|
||||
if (partitioned) {
|
||||
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<MessageChannel> binding = doRegisterConsumer(baseQueueName, group, inputChannel, queue, properties);
|
||||
if (durable) {
|
||||
autoBindDLQ(applyPrefix(prefix, baseQueueName), queueName, properties.getExtension().getPrefix(), properties.getExtension().isAutoBindDlq());
|
||||
}
|
||||
return binding;
|
||||
}
|
||||
|
||||
private Map<String, Object> queueArgs(String queueName, String prefix, boolean bindDlq) {
|
||||
Map<String, Object> args = new HashMap<>();
|
||||
if (bindDlq) {
|
||||
args.put("x-dead-letter-exchange", applyPrefix(prefix, "DLX"));
|
||||
args.put("x-dead-letter-routing-key", queueName);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
private Binding<MessageChannel> doRegisterConsumer(final String name, String group, MessageChannel moduleInputChannel, Queue queue,
|
||||
final ExtendedConsumerProperties<RabbitConsumerProperties> properties) {
|
||||
DefaultBinding<MessageChannel> consumerBinding;
|
||||
SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer(
|
||||
this.connectionFactory);
|
||||
listenerContainer.setAcknowledgeMode(properties.getExtension().getAcknowledgeMode());
|
||||
listenerContainer.setChannelTransacted(properties.getExtension().isTransacted());
|
||||
listenerContainer.setDefaultRequeueRejected(properties.getExtension().isRequeueRejected());
|
||||
int concurrency = properties.getConcurrency();
|
||||
concurrency = concurrency > 0 ? concurrency : 1;
|
||||
listenerContainer.setConcurrentConsumers(concurrency);
|
||||
int maxConcurrency = properties.getExtension().getMaxConcurrency();
|
||||
if (maxConcurrency > concurrency) {
|
||||
listenerContainer.setMaxConcurrentConsumers(maxConcurrency);
|
||||
}
|
||||
listenerContainer.setPrefetchCount(properties.getExtension().getPrefetch());
|
||||
listenerContainer.setRecoveryInterval(properties.getExtension().getRecoveryInterval());
|
||||
listenerContainer.setTxSize(properties.getExtension().getTxSize());
|
||||
listenerContainer.setTaskExecutor(new SimpleAsyncTaskExecutor(queue.getName() + "-"));
|
||||
listenerContainer.setQueues(queue);
|
||||
int maxAttempts = properties.getMaxAttempts();
|
||||
if (maxAttempts > 1 || properties.getExtension().isRepublishToDlq()) {
|
||||
RetryOperationsInterceptor retryInterceptor = RetryInterceptorBuilder.stateless()
|
||||
.maxAttempts(maxAttempts)
|
||||
.backOffOptions(properties.getBackOffInitialInterval(),
|
||||
properties.getBackOffMultiplier(),
|
||||
properties.getBackOffMaxInterval())
|
||||
.recoverer(determineRecoverer(name, properties.getExtension().getPrefix(), properties.getExtension().isRepublishToDlq()))
|
||||
.build();
|
||||
listenerContainer.setAdviceChain(new Advice[] { retryInterceptor });
|
||||
}
|
||||
listenerContainer.setAfterReceivePostProcessors(this.decompressingPostProcessor);
|
||||
listenerContainer.setMessagePropertiesConverter(RabbitMessageChannelBinder.inboundMessagePropertiesConverter);
|
||||
listenerContainer.afterPropertiesSet();
|
||||
AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(listenerContainer);
|
||||
adapter.setBeanFactory(this.getBeanFactory());
|
||||
DirectChannel bridgeToModuleChannel = new DirectChannel();
|
||||
bridgeToModuleChannel.setBeanFactory(this.getBeanFactory());
|
||||
bridgeToModuleChannel.setBeanName(name + ".bridge");
|
||||
adapter.setOutputChannel(bridgeToModuleChannel);
|
||||
adapter.setBeanName("inbound." + name);
|
||||
DefaultAmqpHeaderMapper mapper = new DefaultAmqpHeaderMapper();
|
||||
mapper.setRequestHeaderNames(properties.getExtension().getRequestHeaderPatterns());
|
||||
mapper.setReplyHeaderNames(properties.getExtension().getReplyHeaderPatterns());
|
||||
adapter.setHeaderMapper(mapper);
|
||||
adapter.afterPropertiesSet();
|
||||
consumerBinding = new DefaultBinding<MessageChannel>(name, group, moduleInputChannel, adapter) {
|
||||
@Override
|
||||
protected void afterUnbind() {
|
||||
cleanAutoDeclareContext(properties.getExtension().getPrefix(), name);
|
||||
}
|
||||
};
|
||||
ReceivingHandler convertingBridge = new ReceivingHandler();
|
||||
convertingBridge.setOutputChannel(moduleInputChannel);
|
||||
convertingBridge.setBeanName(name + ".convert.bridge");
|
||||
convertingBridge.afterPropertiesSet();
|
||||
bridgeToModuleChannel.subscribe(convertingBridge);
|
||||
adapter.start();
|
||||
return consumerBinding;
|
||||
}
|
||||
|
||||
private MessageRecoverer determineRecoverer(String name, String prefix, boolean republish) {
|
||||
if (republish) {
|
||||
RabbitTemplate errorTemplate = new RabbitTemplate(this.connectionFactory);
|
||||
RepublishMessageRecoverer republishMessageRecoverer = new RepublishMessageRecoverer(errorTemplate,
|
||||
deadLetterExchangeName(prefix),
|
||||
applyPrefix(prefix, name));
|
||||
return republishMessageRecoverer;
|
||||
}
|
||||
else {
|
||||
return new RejectAndDontRequeueRecoverer();
|
||||
}
|
||||
}
|
||||
|
||||
private AmqpOutboundEndpoint buildOutboundEndpoint(final String name,
|
||||
ExtendedProducerProperties<RabbitProducerProperties> properties,
|
||||
RabbitTemplate rabbitTemplate) {
|
||||
String prefix = properties.getExtension().getPrefix();
|
||||
String exchangeName = applyPrefix(prefix, name);
|
||||
TopicExchange exchange = new TopicExchange(exchangeName);
|
||||
declareExchange(exchangeName, exchange);
|
||||
AmqpOutboundEndpoint endpoint = new AmqpOutboundEndpoint(rabbitTemplate);
|
||||
endpoint.setExchangeName(exchange.getName());
|
||||
if (!properties.isPartitioned()) {
|
||||
endpoint.setRoutingKey(name);
|
||||
}
|
||||
else {
|
||||
endpoint.setExpressionRoutingKey(EXPRESSION_PARSER.parseExpression(buildPartitionRoutingExpression(name)));
|
||||
}
|
||||
for (String requiredGroupName : properties.getRequiredGroups()) {
|
||||
String baseQueueName = exchangeName + "." + requiredGroupName;
|
||||
if (!properties.isPartitioned()) {
|
||||
Queue queue = new Queue(baseQueueName, true, false, false, queueArgs(baseQueueName, prefix, properties.getExtension().isAutoBindDlq()));
|
||||
declareQueue(baseQueueName, queue);
|
||||
autoBindDLQ(baseQueueName, baseQueueName, properties.getExtension().getPrefix(), properties.getExtension().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.getPartitionCount(); i++) {
|
||||
String partitionSuffix = "-" + i;
|
||||
String partitionQueueName = baseQueueName + partitionSuffix;
|
||||
Queue queue = new Queue(partitionQueueName, true, false, false,
|
||||
queueArgs(partitionQueueName, properties.getExtension().getPrefix(), properties.getExtension().isAutoBindDlq()));
|
||||
declareQueue(queue.getName(), queue);
|
||||
autoBindDLQ(baseQueueName, baseQueueName + partitionSuffix, properties.getExtension().getPrefix(), properties.getExtension().isAutoBindDlq());
|
||||
declareBinding(queue.getName(), BindingBuilder.bind(queue).to(exchange).with(name + partitionSuffix));
|
||||
}
|
||||
}
|
||||
}
|
||||
configureOutboundHandler(endpoint, properties);
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
private void configureOutboundHandler(AmqpOutboundEndpoint handler,
|
||||
ExtendedProducerProperties<RabbitProducerProperties> producerProperties) {
|
||||
DefaultAmqpHeaderMapper mapper = new DefaultAmqpHeaderMapper();
|
||||
mapper.setRequestHeaderNames(producerProperties.getExtension().getRequestHeaderPatterns());
|
||||
mapper.setReplyHeaderNames(producerProperties.getExtension().getReplyHeaderPatterns());
|
||||
handler.setHeaderMapper(mapper);
|
||||
handler.setDefaultDeliveryMode(producerProperties.getExtension().getDeliveryMode());
|
||||
handler.setBeanFactory(this.getBeanFactory());
|
||||
handler.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Binding<MessageChannel> doBindProducer(String name, MessageChannel outputChannel,
|
||||
ExtendedProducerProperties<RabbitProducerProperties> producerProperties) {
|
||||
String exchangeName = applyPrefix(producerProperties.getExtension().getPrefix(), name);
|
||||
TopicExchange exchange = new TopicExchange(exchangeName);
|
||||
declareExchange(exchangeName, exchange);
|
||||
AmqpOutboundEndpoint endpoint = this.buildOutboundEndpoint(name, producerProperties,
|
||||
buildRabbitTemplate(producerProperties.getExtension()));
|
||||
return doRegisterProducer(name, outputChannel, endpoint, producerProperties);
|
||||
}
|
||||
|
||||
private RabbitTemplate buildRabbitTemplate(RabbitProducerProperties properties) {
|
||||
RabbitTemplate rabbitTemplate = null;
|
||||
if (properties.isBatchingEnabled()) {
|
||||
BatchingStrategy batchingStrategy = new SimpleBatchingStrategy(
|
||||
properties.getBatchSize(),
|
||||
properties.getBatchBufferLimit(),
|
||||
properties.getBatchTimeout());
|
||||
rabbitTemplate = new BatchingRabbitTemplate(batchingStrategy,
|
||||
getApplicationContext().getBean(IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME,
|
||||
TaskScheduler.class));
|
||||
}
|
||||
else {
|
||||
rabbitTemplate = new RabbitTemplate();
|
||||
}
|
||||
rabbitTemplate.setConnectionFactory(this.connectionFactory);
|
||||
if (properties.isCompress()) {
|
||||
rabbitTemplate.setBeforePublishPostProcessors(this.compressingPostProcessor);
|
||||
}
|
||||
rabbitTemplate.setChannelTransacted(properties.isTransacted());
|
||||
rabbitTemplate.afterPropertiesSet();
|
||||
return rabbitTemplate;
|
||||
}
|
||||
|
||||
private Binding<MessageChannel> doRegisterProducer(final String name, MessageChannel moduleOutputChannel,
|
||||
AmqpOutboundEndpoint delegate, ExtendedProducerProperties<RabbitProducerProperties> properties) {
|
||||
return this.doRegisterProducer(name, moduleOutputChannel, delegate, null, properties);
|
||||
}
|
||||
|
||||
private Binding<MessageChannel> doRegisterProducer(final String name, MessageChannel moduleOutputChannel,
|
||||
AmqpOutboundEndpoint delegate, String replyTo,
|
||||
ExtendedProducerProperties<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<MessageChannel> producerBinding = new DefaultBinding<>(name, null, moduleOutputChannel, consumer);
|
||||
consumer.start();
|
||||
return producerBinding;
|
||||
}
|
||||
|
||||
/**
|
||||
* If so requested, declare the DLX/DLQ and bind it. The DLQ is bound to the DLX with a routing key of the original
|
||||
* queue name because we use default exchange routing by queue name for the original message.
|
||||
* @param queueName The base name for the queue (including the binder prefix, if any).
|
||||
* @param routingKey The routing key for the queue.
|
||||
* @param autoBindDlq true if the DLQ should be bound.
|
||||
*/
|
||||
private void autoBindDLQ(final String queueName, String routingKey, String prefix, boolean autoBindDlq) {
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug("autoBindDLQ=" + autoBindDlq
|
||||
+ " for: " + queueName);
|
||||
}
|
||||
if (autoBindDlq) {
|
||||
String dlqName = constructDLQName(queueName);
|
||||
Queue dlq = new Queue(dlqName);
|
||||
declareQueue(dlqName, dlq);
|
||||
final String dlxName = deadLetterExchangeName(prefix);
|
||||
final DirectExchange dlx = new DirectExchange(dlxName);
|
||||
declareExchange(dlxName, dlx);
|
||||
declareBinding(dlqName, BindingBuilder.bind(dlq).to(dlx).with(routingKey));
|
||||
}
|
||||
}
|
||||
|
||||
public void declareQueue(String beanName, Queue queue) {
|
||||
try {
|
||||
this.rabbitAdmin.declareQueue(queue);
|
||||
}
|
||||
catch (AmqpConnectException e) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Declaration of queue: " + queue.getName() + " deferred - connection not available");
|
||||
}
|
||||
}
|
||||
addToAutoDeclareContext(beanName, queue);
|
||||
}
|
||||
|
||||
public void declareExchange(final String rootName, final Exchange exchange) {
|
||||
try {
|
||||
this.rabbitAdmin.declareExchange(exchange);
|
||||
}
|
||||
catch (AmqpConnectException e) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Declaration of exchange: " + exchange.getName() + " deferred - connection not available");
|
||||
}
|
||||
}
|
||||
addToAutoDeclareContext(rootName + ".exchange", exchange);
|
||||
}
|
||||
|
||||
public void declareBinding(String rootName, org.springframework.amqp.core.Binding binding) {
|
||||
try {
|
||||
this.rabbitAdmin.declareBinding(binding);
|
||||
}
|
||||
catch (AmqpConnectException e) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Declaration of binding: " + rootName + ".binding deferred - connection not available");
|
||||
}
|
||||
}
|
||||
addToAutoDeclareContext(rootName + ".binding", binding);
|
||||
}
|
||||
|
||||
private String deadLetterExchangeName(String prefix) {
|
||||
return prefix + DEAD_LETTER_EXCHANGE;
|
||||
}
|
||||
|
||||
private void addToAutoDeclareContext(String name, Object bean) {
|
||||
synchronized (this.autoDeclareContext) {
|
||||
if (!this.autoDeclareContext.containsBean(name)) {
|
||||
this.autoDeclareContext.getBeanFactory().registerSingleton(name, bean);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void cleanAutoDeclareContext(String prefix, String name) {
|
||||
synchronized (this.autoDeclareContext) {
|
||||
removeSingleton(applyPrefix(prefix, name) + ".binding");
|
||||
removeSingleton(applyPrefix(prefix, name));
|
||||
String dlq = applyPrefix(prefix, name) + ".dlq";
|
||||
removeSingleton(dlq + ".binding");
|
||||
removeSingleton(dlq);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeSingleton(String name) {
|
||||
if (this.autoDeclareContext.containsBean(name)) {
|
||||
ConfigurableListableBeanFactory beanFactory = this.autoDeclareContext.getBeanFactory();
|
||||
if (beanFactory instanceof DefaultListableBeanFactory) {
|
||||
((DefaultListableBeanFactory) beanFactory).destroySingleton(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doManualAck(LinkedList<MessageHeaders> messageHeadersList) {
|
||||
Iterator<MessageHeaders> iterator = messageHeadersList.iterator();
|
||||
Map<Object, Long> channelsToAck = new HashMap<>();
|
||||
while (iterator.hasNext()) {
|
||||
MessageHeaders messageHeaders = iterator.next();
|
||||
if (messageHeaders.containsKey(AmqpHeaders.CHANNEL)) {
|
||||
Channel channel = (com.rabbitmq.client.Channel) messageHeaders.get(AmqpHeaders.CHANNEL);
|
||||
Long deliveryTag = (Long) messageHeaders.get(AmqpHeaders.DELIVERY_TAG);
|
||||
channelsToAck.put(channel, deliveryTag);
|
||||
}
|
||||
}
|
||||
for (Map.Entry<Object, Long> entry : channelsToAck.entrySet()) {
|
||||
try {
|
||||
((Channel) entry.getKey()).basicAck(entry.getValue(), true);
|
||||
}
|
||||
catch (IOException e) {
|
||||
this.logger.error("Exception while manually acknowledging " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class SendingHandler extends AbstractMessageHandler implements Lifecycle {
|
||||
|
||||
private final MessageHandler delegate;
|
||||
|
||||
private final String replyTo;
|
||||
|
||||
private final ExtendedProducerProperties<RabbitProducerProperties> producerProperties;
|
||||
|
||||
private final PartitionHandler partitionHandler;
|
||||
|
||||
private SendingHandler(MessageHandler delegate, String replyTo,
|
||||
ExtendedProducerProperties<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);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleMessageInternal(Message<?> message) throws Exception {
|
||||
MessageValues messageToSend = serializePayloadIfNecessary(message);
|
||||
|
||||
if (this.replyTo != null) {
|
||||
messageToSend.put(AmqpHeaders.REPLY_TO, this.replyTo);
|
||||
}
|
||||
if (producerProperties.isPartitioned()) {
|
||||
messageToSend.put(PARTITION_HEADER,
|
||||
this.partitionHandler.determinePartition(message));
|
||||
}
|
||||
|
||||
this.delegate.handleMessage(messageToSend.toMessage(getMessageBuilderFactory()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
if (this.delegate instanceof Lifecycle) {
|
||||
((Lifecycle) this.delegate).start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (this.delegate instanceof Lifecycle) {
|
||||
((Lifecycle) this.delegate).stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
if (this.delegate instanceof Lifecycle) {
|
||||
return ((Lifecycle) this.delegate).isRunning();
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final class ReceivingHandler extends AbstractReplyProducingMessageHandler {
|
||||
|
||||
private ReceivingHandler() {
|
||||
super();
|
||||
this.setBeanFactory(RabbitMessageChannelBinder.this.getBeanFactory());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object handleRequestMessage(Message<?> requestMessage) {
|
||||
return deserializePayloadIfNecessary(requestMessage).toMessage(getMessageBuilderFactory());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldCopyRequestHeaders() {
|
||||
/*
|
||||
* we've already copied the headers so no need for the ARPMH to do it, and we don't want the content-type
|
||||
* restored if absent.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,142 +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.rabbit;
|
||||
|
||||
import javax.validation.constraints.Min;
|
||||
|
||||
import org.springframework.amqp.core.MessageDeliveryMode;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public class RabbitProducerProperties {
|
||||
|
||||
private String prefix = "";
|
||||
|
||||
private String[] requestHeaderPatterns = new String[] {"STANDARD_REQUEST_HEADERS", "*"};
|
||||
|
||||
private boolean autoBindDlq;
|
||||
|
||||
private boolean compress;
|
||||
|
||||
private boolean batchingEnabled;
|
||||
|
||||
private int batchSize = 100;
|
||||
|
||||
private int batchBufferLimit = 10000;
|
||||
|
||||
private int batchTimeout = 5000;
|
||||
|
||||
private boolean transacted;
|
||||
|
||||
private MessageDeliveryMode deliveryMode = MessageDeliveryMode.PERSISTENT;
|
||||
|
||||
private String[] replyHeaderPatterns = new String[] {"STANDARD_REPLY_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;
|
||||
}
|
||||
|
||||
@Min(value = 1, message = "Batch Size should be greater than zero.")
|
||||
public int getBatchSize() {
|
||||
return batchSize;
|
||||
}
|
||||
|
||||
public void setBatchSize(int batchSize) {
|
||||
this.batchSize = batchSize;
|
||||
}
|
||||
|
||||
@Min(value = 1, message = "Batch Buffer Limit should be greater than zero.")
|
||||
public int getBatchBufferLimit() {
|
||||
return batchBufferLimit;
|
||||
}
|
||||
|
||||
public void setBatchBufferLimit(int batchBufferLimit) {
|
||||
this.batchBufferLimit = batchBufferLimit;
|
||||
}
|
||||
|
||||
@Min(value = 1, message = "Batch Timeout should be greater than zero.")
|
||||
public int getBatchTimeout() {
|
||||
return batchTimeout;
|
||||
}
|
||||
|
||||
public void setBatchTimeout(int batchTimeout) {
|
||||
this.batchTimeout = batchTimeout;
|
||||
}
|
||||
|
||||
public boolean isTransacted() {
|
||||
return this.transacted;
|
||||
}
|
||||
|
||||
public void setTransacted(boolean transacted) {
|
||||
this.transacted = transacted;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* @author David Turanski
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.cloud.stream.rabbit.binder")
|
||||
class RabbitBinderConfigurationProperties {
|
||||
|
||||
private String[] adminAddresses = new String[0];
|
||||
|
||||
private String[] nodes = new String[0];
|
||||
|
||||
private int compressionLevel;
|
||||
|
||||
public String[] getAdminAddresses() {
|
||||
return adminAddresses;
|
||||
}
|
||||
|
||||
public void setAdminAddresses(String[] adminAddresses) {
|
||||
this.adminAddresses = adminAddresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param adminAddresses A comma-separated list of RabbitMQ management plugin URLs.
|
||||
* @deprecated in favor of {@link #setAdminAddresses(String[])}. Will be removed in a
|
||||
* future release.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setAdminAdresses(String[] adminAddresses) {
|
||||
setAdminAddresses(adminAddresses);
|
||||
}
|
||||
|
||||
public String[] getNodes() {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public void setNodes(String[] nodes) {
|
||||
this.nodes = nodes;
|
||||
}
|
||||
|
||||
public int getCompressionLevel() {
|
||||
return compressionLevel;
|
||||
}
|
||||
|
||||
public void setCompressionLevel(int compressionLevel) {
|
||||
this.compressionLevel = compressionLevel;
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.amqp.core.MessagePostProcessor;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.support.postprocessor.DelegatingDecompressingPostProcessor;
|
||||
import org.springframework.amqp.support.postprocessor.GZipPostProcessor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.stream.binder.rabbit.RabbitExtendedBindingProperties;
|
||||
import org.springframework.cloud.stream.binder.rabbit.RabbitMessageChannelBinder;
|
||||
import org.springframework.cloud.stream.config.codec.kryo.KryoCodecAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.integration.codec.Codec;
|
||||
|
||||
|
||||
/**
|
||||
* Configuration class for RabbitMQ message channel binder.
|
||||
*
|
||||
* @author David Turanski
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
@Import({PropertyPlaceholderAutoConfiguration.class, KryoCodecAutoConfiguration.class})
|
||||
@EnableConfigurationProperties({RabbitBinderConfigurationProperties.class, RabbitExtendedBindingProperties.class})
|
||||
public class RabbitMessageChannelBinderConfiguration {
|
||||
|
||||
@Autowired
|
||||
private Codec codec;
|
||||
|
||||
@Autowired
|
||||
private ConnectionFactory rabbitConnectionFactory;
|
||||
|
||||
@Autowired
|
||||
private RabbitProperties rabbitProperties;
|
||||
|
||||
@Autowired
|
||||
private RabbitBinderConfigurationProperties rabbitBinderConfigurationProperties;
|
||||
|
||||
@Autowired
|
||||
private RabbitExtendedBindingProperties rabbitExtendedBindingProperties;
|
||||
|
||||
@Bean
|
||||
RabbitMessageChannelBinder rabbitMessageChannelBinder() {
|
||||
RabbitMessageChannelBinder binder = new RabbitMessageChannelBinder(rabbitConnectionFactory, rabbitProperties);
|
||||
binder.setCodec(codec);
|
||||
binder.setAdminAddresses(rabbitBinderConfigurationProperties.getAdminAddresses());
|
||||
binder.setCompressingPostProcessor(gZipPostProcessor());
|
||||
binder.setDecompressingPostProcessor(deCompressingPostProcessor());
|
||||
binder.setNodes(rabbitBinderConfigurationProperties.getNodes());
|
||||
binder.setExtendedBindingProperties(rabbitExtendedBindingProperties);
|
||||
return binder;
|
||||
}
|
||||
|
||||
@Bean
|
||||
MessagePostProcessor deCompressingPostProcessor() {
|
||||
return new DelegatingDecompressingPostProcessor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
MessagePostProcessor gZipPostProcessor() {
|
||||
GZipPostProcessor gZipPostProcessor = new GZipPostProcessor();
|
||||
gZipPostProcessor.setLevel(rabbitBinderConfigurationProperties.getCompressionLevel());
|
||||
return gZipPostProcessor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
import org.springframework.boot.actuate.health.RabbitHealthIndicator;
|
||||
import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.Cloud;
|
||||
import org.springframework.cloud.CloudFactory;
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
/**
|
||||
* Bind to services, either locally or in a cloud environment.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Dave Syer
|
||||
* @author Glenn Renfro
|
||||
* @author David Turanski
|
||||
* @author Eric Bottard
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnMissingBean(Binder.class)
|
||||
@Import(RabbitMessageChannelBinderConfiguration.class)
|
||||
public class RabbitServiceAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public HealthIndicator binderHealthIndicator(RabbitTemplate rabbitTemplate) {
|
||||
return new RabbitHealthIndicator(rabbitTemplate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration to be used when the cloud profile is set.
|
||||
*/
|
||||
@Configuration
|
||||
@Profile("cloud")
|
||||
protected static class CloudProfile {
|
||||
|
||||
/**
|
||||
* Configuration to be used when the cloud profile is set, and Cloud Connectors
|
||||
* are found on the classpath.
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(Cloud.class)
|
||||
protected static class CloudConnectors {
|
||||
|
||||
@Bean
|
||||
public Cloud cloud() {
|
||||
return new CloudFactory().getCloud();
|
||||
}
|
||||
|
||||
/**
|
||||
* Active only if {@code spring.cloud.stream.overrideCloudConnectors} is not
|
||||
* set to {@code true}.
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnProperty(value = "spring.cloud.stream.overrideCloudConnectors", havingValue = "false", matchIfMissing = true)
|
||||
// Required to parse Rabbit properties which are passed to the binder for
|
||||
// clustering. We need to enable it here explicitly as the default Rabbit
|
||||
// configuration is not triggered.
|
||||
@EnableConfigurationProperties(RabbitProperties.class)
|
||||
protected static class UseCloudConnectors {
|
||||
|
||||
/**
|
||||
* Creates a {@link ConnectionFactory} using the singleton service
|
||||
* connector.
|
||||
*
|
||||
* @param cloud {@link Cloud} instance to be used for accessing services.
|
||||
* @return the {@link ConnectionFactory} used by the binder.
|
||||
*/
|
||||
@Bean
|
||||
ConnectionFactory rabbitConnectionFactory(Cloud cloud) {
|
||||
return cloud.getSingletonServiceConnector(ConnectionFactory.class, null);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(RabbitTemplate.class)
|
||||
RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
|
||||
return new RabbitTemplate(connectionFactory);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration to be used if
|
||||
* {@code spring.cloud.stream.overrideCloudConnectors} is set to {@code true}.
|
||||
* Defers to Spring Boot Autoconfiguration.
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnProperty("spring.cloud.stream.overrideCloudConnectors")
|
||||
@Import(RabbitAutoConfiguration.class)
|
||||
protected static class OverrideCloudConnectors {
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnMissingClass("org.springframework.cloud.Cloud")
|
||||
@Import(RabbitAutoConfiguration.class)
|
||||
protected static class NoCloudConnectors {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration to be used when the cloud profile is not set. Defer to Spring Boot
|
||||
* autoconfiguration.
|
||||
*/
|
||||
@Configuration
|
||||
@Profile("!cloud")
|
||||
@Import(RabbitAutoConfiguration.class)
|
||||
protected static class NoCloudProfile {
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This package contains an implementation of the {@link org.springframework.cloud.stream.binder.Binder} for RabbitMQ.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.stream.binder.rabbit;
|
||||
@@ -1,2 +0,0 @@
|
||||
rabbit:\
|
||||
org.springframework.cloud.stream.binder.rabbit.config.RabbitServiceAutoConfiguration
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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 java.util.UUID;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.amqp.core.Queue;
|
||||
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.connection.LocalizedQueueConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.cloud.stream.test.junit.rabbit.RabbitTestSupport;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public class LocalizedQueueConnectionFactoryIntegrationTests {
|
||||
|
||||
@ClassRule
|
||||
public static RabbitTestSupport rabbitAvailableRule = new RabbitTestSupport(true);
|
||||
|
||||
private LocalizedQueueConnectionFactory lqcf;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
ConnectionFactory defaultConnectionFactory = rabbitAvailableRule.getResource();
|
||||
String[] addresses = new String[] { "localhost:9999", "localhost:5672" };
|
||||
String[] adminAddresses = new String[] { "http://localhost:15672", "http://localhost:15672" };
|
||||
String[] nodes = new String[] { "foo@bar", "rabbit@localhost" };
|
||||
String vhost = "/";
|
||||
String username = "guest";
|
||||
String password = "guest";
|
||||
this.lqcf = new LocalizedQueueConnectionFactory(defaultConnectionFactory, addresses,
|
||||
adminAddresses, nodes, vhost, username, password, false, null, null, null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnect() {
|
||||
RabbitAdmin admin = new RabbitAdmin(this.lqcf);
|
||||
Queue queue = new Queue(UUID.randomUUID().toString(), false, false, true);
|
||||
admin.declareQueue(queue);
|
||||
ConnectionFactory targetConnectionFactory = this.lqcf.getTargetConnectionFactory("[" + queue.getName() + "]");
|
||||
RabbitTemplate template = new RabbitTemplate(targetConnectionFactory);
|
||||
template.convertAndSend("", queue.getName(), "foo");
|
||||
assertThat(template.receiveAndConvert(queue.getName())).isEqualTo("foo");
|
||||
((CachingConnectionFactory) targetConnectionFactory).destroy();
|
||||
admin.deleteQueue(queue.getName());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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 java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.rabbitmq.client.DefaultConsumer;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.amqp.core.BindingBuilder;
|
||||
import org.springframework.amqp.core.Queue;
|
||||
import org.springframework.amqp.core.TopicExchange;
|
||||
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.core.ChannelCallback;
|
||||
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.cloud.stream.binder.AbstractBinder;
|
||||
import org.springframework.cloud.stream.test.junit.rabbit.RabbitTestSupport;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @since 1.2
|
||||
*/
|
||||
public class RabbitBinderCleanerTests {
|
||||
|
||||
private static final String BINDER_PREFIX = "binder.";
|
||||
|
||||
@Rule
|
||||
public RabbitTestSupport rabbitWithMgmtEnabled = new RabbitTestSupport(true);
|
||||
|
||||
@Test
|
||||
public void testCleanStream() {
|
||||
final RabbitBindingCleaner cleaner = new RabbitBindingCleaner();
|
||||
final RestTemplate template = RabbitManagementUtils.buildRestTemplate("http://localhost:15672", "guest",
|
||||
"guest");
|
||||
final String stream1 = UUID.randomUUID().toString();
|
||||
String stream2 = stream1 + "-1";
|
||||
String firstQueue = null;
|
||||
CachingConnectionFactory connectionFactory = rabbitWithMgmtEnabled.getResource();
|
||||
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
String queue1Name = AbstractBinder.applyPrefix(BINDER_PREFIX, stream1 + ".default." + i);
|
||||
String queue2Name = AbstractBinder.applyPrefix(BINDER_PREFIX, stream2 + ".default." + i);
|
||||
if (firstQueue == null) {
|
||||
firstQueue = queue1Name;
|
||||
}
|
||||
URI uri = UriComponentsBuilder.fromUriString("http://localhost:15672/api/queues")
|
||||
.pathSegment("{vhost}", "{queue}")
|
||||
.buildAndExpand("/", queue1Name)
|
||||
.encode().toUri();
|
||||
template.put(uri, new AmqpQueue(false, true));
|
||||
uri = UriComponentsBuilder.fromUriString("http://localhost:15672/api/queues")
|
||||
.pathSegment("{vhost}", "{queue}")
|
||||
.buildAndExpand("/", queue2Name)
|
||||
.encode().toUri();
|
||||
template.put(uri, new AmqpQueue(false, true));
|
||||
uri = UriComponentsBuilder.fromUriString("http://localhost:15672/api/queues")
|
||||
.pathSegment("{vhost}", "{queue}")
|
||||
.buildAndExpand("/", AbstractBinder.constructDLQName(queue1Name)).encode().toUri();
|
||||
template.put(uri, new AmqpQueue(false, true));
|
||||
TopicExchange exchange = new TopicExchange(queue1Name);
|
||||
rabbitAdmin.declareExchange(exchange);
|
||||
rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue(queue1Name)).to(exchange).with(queue1Name));
|
||||
exchange = new TopicExchange(queue2Name);
|
||||
rabbitAdmin.declareExchange(exchange);
|
||||
rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue(queue2Name)).to(exchange).with(queue2Name));
|
||||
}
|
||||
final TopicExchange topic1 = new TopicExchange(
|
||||
AbstractBinder.applyPrefix(BINDER_PREFIX, stream1 + ".foo.bar"));
|
||||
rabbitAdmin.declareExchange(topic1);
|
||||
rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue(firstQueue)).to(topic1).with("#"));
|
||||
String foreignQueue = UUID.randomUUID().toString();
|
||||
rabbitAdmin.declareQueue(new Queue(foreignQueue));
|
||||
rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue(foreignQueue)).to(topic1).with("#"));
|
||||
final TopicExchange topic2 = new TopicExchange(
|
||||
AbstractBinder.applyPrefix(BINDER_PREFIX, stream2 + ".foo.bar"));
|
||||
rabbitAdmin.declareExchange(topic2);
|
||||
rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue(firstQueue)).to(topic2).with("#"));
|
||||
new RabbitTemplate(connectionFactory).execute(new ChannelCallback<Void>() {
|
||||
|
||||
@Override
|
||||
public Void doInRabbit(Channel channel) throws Exception {
|
||||
String queueName = AbstractBinder.applyPrefix(BINDER_PREFIX, stream1 + ".default." + 4);
|
||||
String consumerTag = channel.basicConsume(queueName, new DefaultConsumer(channel));
|
||||
try {
|
||||
waitForConsumerStateNot(queueName, 0);
|
||||
cleaner.clean(stream1, false);
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (RabbitAdminException e) {
|
||||
assertThat(e).hasMessageContaining("Queue " + queueName + " is in use");
|
||||
}
|
||||
channel.basicCancel(consumerTag);
|
||||
waitForConsumerStateNot(queueName, 1);
|
||||
try {
|
||||
cleaner.clean(stream1, false);
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (RabbitAdminException e) {
|
||||
assertThat(e).hasMessageContaining("Cannot delete exchange ");
|
||||
assertThat(e).hasMessageContaining("; it has bindings:");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void waitForConsumerStateNot(String queueName, int state) throws InterruptedException {
|
||||
int n = 0;
|
||||
URI uri = UriComponentsBuilder.fromUriString("http://localhost:15672/api/queues").pathSegment(
|
||||
"{vhost}", "{queue}")
|
||||
.buildAndExpand("/", queueName).encode().toUri();
|
||||
while (n++ < 100) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> queueInfo = template.getForObject(uri, Map.class);
|
||||
if (!queueInfo.get("consumers").equals(Integer.valueOf(state))) {
|
||||
break;
|
||||
}
|
||||
Thread.sleep(100);
|
||||
}
|
||||
assertThat(n < 100).withFailMessage("Consumer state remained at " + state + " after 10 seconds");
|
||||
}
|
||||
|
||||
});
|
||||
rabbitAdmin.deleteExchange(topic1.getName()); // easier than deleting the binding
|
||||
rabbitAdmin.declareExchange(topic1);
|
||||
rabbitAdmin.deleteQueue(foreignQueue);
|
||||
connectionFactory.destroy();
|
||||
Map<String, List<String>> cleanedMap = cleaner.clean(stream1, false);
|
||||
assertThat(cleanedMap).hasSize(2);
|
||||
List<String> cleanedQueues = cleanedMap.get("queues");
|
||||
// should *not* clean stream2
|
||||
assertThat(cleanedQueues).hasSize(10);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
assertThat(cleanedQueues.get(i * 2)).isEqualTo(BINDER_PREFIX + stream1 + ".default." + i);
|
||||
assertThat(cleanedQueues.get(i * 2 + 1)).isEqualTo(BINDER_PREFIX + stream1 + ".default." + i + ".dlq");
|
||||
}
|
||||
List<String> cleanedExchanges = cleanedMap.get("exchanges");
|
||||
assertThat(cleanedExchanges).hasSize(6);
|
||||
|
||||
// wild card *should* clean stream2
|
||||
cleanedMap = cleaner.clean(stream1 + "*", false);
|
||||
assertThat(cleanedMap).hasSize(2);
|
||||
cleanedQueues = cleanedMap.get("queues");
|
||||
assertThat(cleanedQueues).hasSize(5);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
assertThat(cleanedQueues.get(i)).isEqualTo(BINDER_PREFIX + stream2 + ".default." + i);
|
||||
}
|
||||
cleanedExchanges = cleanedMap.get("exchanges");
|
||||
assertThat(cleanedExchanges).hasSize(6);
|
||||
}
|
||||
|
||||
public static class AmqpQueue {
|
||||
|
||||
private boolean autoDelete;
|
||||
|
||||
private boolean durable;
|
||||
|
||||
public AmqpQueue(boolean autoDelete, boolean durable) {
|
||||
this.autoDelete = autoDelete;
|
||||
this.durable = durable;
|
||||
}
|
||||
|
||||
|
||||
@JsonProperty("auto_delete")
|
||||
protected boolean isAutoDelete() {
|
||||
return autoDelete;
|
||||
}
|
||||
|
||||
|
||||
protected void setAutoDelete(boolean autoDelete) {
|
||||
this.autoDelete = autoDelete;
|
||||
}
|
||||
|
||||
|
||||
protected boolean isDurable() {
|
||||
return durable;
|
||||
}
|
||||
|
||||
|
||||
protected void setDurable(boolean durable) {
|
||||
this.durable = durable;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,843 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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 java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.zip.Deflater;
|
||||
|
||||
import org.aopalliance.aop.Advice;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.amqp.core.AcknowledgeMode;
|
||||
import org.springframework.amqp.core.MessageDeliveryMode;
|
||||
import org.springframework.amqp.core.Queue;
|
||||
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||
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.boot.autoconfigure.amqp.RabbitProperties;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.ExtendedProducerProperties;
|
||||
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;
|
||||
import org.springframework.expression.spel.standard.SpelExpression;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.endpoint.AbstractEndpoint;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
* @author David Turanski
|
||||
*/
|
||||
public class RabbitBinderTests extends
|
||||
PartitionCapableBinderTests<RabbitTestBinder, ExtendedConsumerProperties<RabbitConsumerProperties>, ExtendedProducerProperties<RabbitProducerProperties>> {
|
||||
|
||||
private final String CLASS_UNDER_TEST_NAME = RabbitMessageChannelBinder.class.getSimpleName();
|
||||
|
||||
public static final String TEST_PREFIX = "bindertest.";
|
||||
|
||||
@Rule
|
||||
public RabbitTestSupport rabbitAvailableRule = new RabbitTestSupport();
|
||||
|
||||
@Override
|
||||
protected RabbitTestBinder getBinder() {
|
||||
if (testBinder == null) {
|
||||
testBinder = new RabbitTestBinder(rabbitAvailableRule.getResource(), new RabbitProperties());
|
||||
}
|
||||
return testBinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ExtendedConsumerProperties<RabbitConsumerProperties> createConsumerProperties() {
|
||||
return new ExtendedConsumerProperties<>(new RabbitConsumerProperties());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ExtendedProducerProperties<RabbitProducerProperties> createProducerProperties() {
|
||||
return new ExtendedProducerProperties<>(new RabbitProducerProperties());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean usesExplicitRouting() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendAndReceiveBad() throws Exception {
|
||||
RabbitTestBinder binder = getBinder();
|
||||
DirectChannel moduleOutputChannel = new DirectChannel();
|
||||
DirectChannel moduleInputChannel = new DirectChannel();
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("bad.0", moduleOutputChannel,
|
||||
createProducerProperties());
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("bad.0", "test", moduleInputChannel,
|
||||
createConsumerProperties());
|
||||
Message<?> message = MessageBuilder.withPayload("bad").setHeader(MessageHeaders.CONTENT_TYPE, "foo/bar")
|
||||
.build();
|
||||
final CountDownLatch latch = new CountDownLatch(3);
|
||||
moduleInputChannel.subscribe(new MessageHandler() {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
latch.countDown();
|
||||
throw new RuntimeException("bad");
|
||||
}
|
||||
});
|
||||
moduleOutputChannel.send(message);
|
||||
assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
|
||||
producerBinding.unbind();
|
||||
consumerBinding.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConsumerProperties() throws Exception {
|
||||
RabbitTestBinder binder = getBinder();
|
||||
ExtendedConsumerProperties<RabbitConsumerProperties> properties = createConsumerProperties();
|
||||
properties.getExtension().setTransacted(true);
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("props.0", null, new DirectChannel(), properties);
|
||||
AbstractEndpoint endpoint = extractEndpoint(consumerBinding);
|
||||
SimpleMessageListenerContainer container = TestUtils.getPropertyValue(endpoint, "messageListenerContainer",
|
||||
SimpleMessageListenerContainer.class);
|
||||
assertThat(container.getAcknowledgeMode()).isEqualTo(AcknowledgeMode.AUTO);
|
||||
assertThat(container.getQueueNames()[0]).startsWith(properties.getExtension().getPrefix());
|
||||
assertThat(TestUtils.getPropertyValue(container, "transactional", Boolean.class)).isTrue();
|
||||
assertThat(TestUtils.getPropertyValue(container, "concurrentConsumers")).isEqualTo(1);
|
||||
assertThat(TestUtils.getPropertyValue(container, "maxConcurrentConsumers")).isNull();
|
||||
assertThat(TestUtils.getPropertyValue(container, "defaultRequeueRejected", Boolean.class)).isTrue();
|
||||
assertThat(TestUtils.getPropertyValue(container, "prefetchCount")).isEqualTo(1);
|
||||
assertThat(TestUtils.getPropertyValue(container, "txSize")).isEqualTo(1);
|
||||
Advice retry = TestUtils.getPropertyValue(container, "adviceChain", Advice[].class)[0];
|
||||
assertThat(TestUtils.getPropertyValue(retry, "retryOperations.retryPolicy.maxAttempts")).isEqualTo(3);
|
||||
assertThat(TestUtils.getPropertyValue(retry, "retryOperations.backOffPolicy.initialInterval")).isEqualTo(1000L);
|
||||
assertThat(TestUtils.getPropertyValue(retry, "retryOperations.backOffPolicy.maxInterval")).isEqualTo(10000L);
|
||||
assertThat(TestUtils.getPropertyValue(retry, "retryOperations.backOffPolicy.multiplier")).isEqualTo(2.0);
|
||||
consumerBinding.unbind();
|
||||
assertThat(endpoint.isRunning()).isFalse();
|
||||
|
||||
properties = createConsumerProperties();
|
||||
properties.getExtension().setAcknowledgeMode(AcknowledgeMode.NONE);
|
||||
properties.setBackOffInitialInterval(2000);
|
||||
properties.setBackOffMaxInterval(20000);
|
||||
properties.setBackOffMultiplier(5.0);
|
||||
properties.setConcurrency(2);
|
||||
properties.setMaxAttempts(23);
|
||||
properties.getExtension().setMaxConcurrency(3);
|
||||
properties.getExtension().setPrefix("foo.");
|
||||
properties.getExtension().setPrefetch(20);
|
||||
properties.getExtension().setRequestHeaderPatterns(new String[] { "foo" });
|
||||
properties.getExtension().setRequeueRejected(false);
|
||||
properties.getExtension().setTxSize(10);
|
||||
properties.setInstanceIndex(0);
|
||||
consumerBinding = binder.bindConsumer("props.0", "test", new DirectChannel(), properties);
|
||||
|
||||
endpoint = extractEndpoint(consumerBinding);
|
||||
container = verifyContainer(endpoint);
|
||||
|
||||
assertThat(container.getQueueNames()[0]).isEqualTo("foo.props.0.test");
|
||||
|
||||
consumerBinding.unbind();
|
||||
assertThat(endpoint.isRunning()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProducerProperties() throws Exception {
|
||||
RabbitTestBinder binder = getBinder();
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("props.0", new DirectChannel(),
|
||||
createProducerProperties());
|
||||
@SuppressWarnings("unchecked")
|
||||
AbstractEndpoint endpoint = extractEndpoint(producerBinding);
|
||||
MessageDeliveryMode mode = TestUtils.getPropertyValue(endpoint, "handler.delegate.defaultDeliveryMode",
|
||||
MessageDeliveryMode.class);
|
||||
assertThat(mode).isEqualTo(MessageDeliveryMode.PERSISTENT);
|
||||
List<?> requestHeaders = TestUtils.getPropertyValue(endpoint,
|
||||
"handler.delegate.headerMapper.requestHeaderMatcher.matchers", List.class);
|
||||
assertThat(requestHeaders).hasSize(2);
|
||||
producerBinding.unbind();
|
||||
assertThat(endpoint.isRunning()).isFalse();
|
||||
assertThat(TestUtils.getPropertyValue(endpoint, "handler.delegate.amqpTemplate.transactional", Boolean.class))
|
||||
.isFalse();
|
||||
|
||||
ExtendedProducerProperties<RabbitProducerProperties> properties = createProducerProperties();
|
||||
properties.getExtension().setPrefix("foo.");
|
||||
properties.getExtension().setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);
|
||||
properties.getExtension().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);
|
||||
properties.getExtension().setTransacted(true);
|
||||
|
||||
producerBinding = binder.bindProducer("props.0", new DirectChannel(), properties);
|
||||
endpoint = extractEndpoint(producerBinding);
|
||||
assertThat(TestUtils.getPropertyValue(endpoint, "handler.delegate.routingKeyExpression", SpelExpression.class)
|
||||
.getExpressionString()).isEqualTo("'props.0-' + headers['partition']");
|
||||
mode = TestUtils.getPropertyValue(endpoint, "handler.delegate.defaultDeliveryMode", MessageDeliveryMode.class);
|
||||
assertThat(mode).isEqualTo(MessageDeliveryMode.NON_PERSISTENT);
|
||||
assertThat(TestUtils.getPropertyValue(endpoint, "handler.delegate.amqpTemplate.transactional", Boolean.class))
|
||||
.isTrue();
|
||||
verifyFooRequestProducer(endpoint);
|
||||
|
||||
producerBinding.unbind();
|
||||
assertThat(endpoint.isRunning()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDurablePubSubWithAutoBindDLQ() throws Exception {
|
||||
RabbitAdmin admin = new RabbitAdmin(this.rabbitAvailableRule.getResource());
|
||||
|
||||
RabbitTestBinder binder = getBinder();
|
||||
|
||||
ExtendedConsumerProperties<RabbitConsumerProperties> properties = createConsumerProperties();
|
||||
properties.getExtension().setPrefix(TEST_PREFIX);
|
||||
properties.getExtension().setAutoBindDlq(true);
|
||||
properties.getExtension().setDurableSubscription(true);
|
||||
properties.setMaxAttempts(1); // disable retry
|
||||
properties.getExtension().setRequeueRejected(false);
|
||||
DirectChannel moduleInputChannel = new DirectChannel();
|
||||
moduleInputChannel.setBeanName("durableTest");
|
||||
moduleInputChannel.subscribe(new MessageHandler() {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
throw new RuntimeException("foo");
|
||||
}
|
||||
|
||||
});
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("durabletest.0", "tgroup", moduleInputChannel,
|
||||
properties);
|
||||
|
||||
RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
|
||||
template.convertAndSend(TEST_PREFIX + "durabletest.0", "", "foo");
|
||||
|
||||
int n = 0;
|
||||
while (n++ < 100) {
|
||||
Object deadLetter = template.receiveAndConvert(TEST_PREFIX + "durabletest.0.tgroup.dlq");
|
||||
if (deadLetter != null) {
|
||||
assertThat(deadLetter).isEqualTo("foo");
|
||||
break;
|
||||
}
|
||||
Thread.sleep(100);
|
||||
}
|
||||
assertThat(n).isLessThan(100);
|
||||
|
||||
consumerBinding.unbind();
|
||||
assertThat(admin.getQueueProperties(TEST_PREFIX + "durabletest.0.tgroup.dlq")).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonDurablePubSubWithAutoBindDLQ() throws Exception {
|
||||
RabbitAdmin admin = new RabbitAdmin(this.rabbitAvailableRule.getResource());
|
||||
|
||||
RabbitTestBinder binder = getBinder();
|
||||
ExtendedConsumerProperties<RabbitConsumerProperties> properties = createConsumerProperties();
|
||||
properties.getExtension().setPrefix(TEST_PREFIX);
|
||||
properties.getExtension().setAutoBindDlq(true);
|
||||
properties.getExtension().setDurableSubscription(false);
|
||||
properties.setMaxAttempts(1); // disable retry
|
||||
properties.getExtension().setRequeueRejected(false);
|
||||
DirectChannel moduleInputChannel = new DirectChannel();
|
||||
moduleInputChannel.setBeanName("nondurabletest");
|
||||
moduleInputChannel.subscribe(new MessageHandler() {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
throw new RuntimeException("foo");
|
||||
}
|
||||
|
||||
});
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("nondurabletest.0", "tgroup", moduleInputChannel,
|
||||
properties);
|
||||
|
||||
consumerBinding.unbind();
|
||||
assertThat(admin.getQueueProperties(TEST_PREFIX + "nondurabletest.0.dlq")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoBindDLQ() throws Exception {
|
||||
RabbitTestBinder binder = getBinder();
|
||||
ExtendedConsumerProperties<RabbitConsumerProperties> properties = createConsumerProperties();
|
||||
properties.getExtension().setPrefix(TEST_PREFIX);
|
||||
properties.getExtension().setAutoBindDlq(true);
|
||||
properties.setMaxAttempts(1); // disable retry
|
||||
properties.getExtension().setRequeueRejected(false);
|
||||
properties.getExtension().setDurableSubscription(true);
|
||||
DirectChannel moduleInputChannel = new DirectChannel();
|
||||
moduleInputChannel.setBeanName("dlqTest");
|
||||
moduleInputChannel.subscribe(new MessageHandler() {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
throw new RuntimeException("foo");
|
||||
}
|
||||
|
||||
});
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("dlqtest", "default", moduleInputChannel,
|
||||
properties);
|
||||
|
||||
RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
|
||||
template.convertAndSend("", TEST_PREFIX + "dlqtest.default", "foo");
|
||||
|
||||
int n = 0;
|
||||
while (n++ < 100) {
|
||||
Object deadLetter = template.receiveAndConvert(TEST_PREFIX + "dlqtest.default.dlq");
|
||||
if (deadLetter != null) {
|
||||
assertThat(deadLetter).isEqualTo("foo");
|
||||
break;
|
||||
}
|
||||
Thread.sleep(100);
|
||||
}
|
||||
assertThat(n).isLessThan(100);
|
||||
|
||||
consumerBinding.unbind();
|
||||
|
||||
ApplicationContext context = TestUtils.getPropertyValue(binder, "binder.autoDeclareContext",
|
||||
ApplicationContext.class);
|
||||
assertThat(context.containsBean(TEST_PREFIX + "dlqtest.default.binding")).isFalse();
|
||||
assertThat(context.containsBean(TEST_PREFIX + "dlqtest.default")).isFalse();
|
||||
assertThat(context.containsBean(TEST_PREFIX + "dlqtest.default.dlq.binding")).isFalse();
|
||||
assertThat(context.containsBean(TEST_PREFIX + "dlqtest.default.dlq")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoBindDLQPartionedConsumerFirst() throws Exception {
|
||||
RabbitTestBinder binder = getBinder();
|
||||
ExtendedConsumerProperties<RabbitConsumerProperties> properties = createConsumerProperties();
|
||||
properties.getExtension().setPrefix("bindertest.");
|
||||
properties.getExtension().setAutoBindDlq(true);
|
||||
properties.setMaxAttempts(1); // disable retry
|
||||
properties.getExtension().setRequeueRejected(false);
|
||||
properties.setPartitioned(true);
|
||||
properties.setInstanceIndex(0);
|
||||
DirectChannel input0 = new DirectChannel();
|
||||
input0.setBeanName("test.input0DLQ");
|
||||
Binding<MessageChannel> input0Binding = binder.bindConsumer("partDLQ.0", "dlqPartGrp", input0, properties);
|
||||
Binding<MessageChannel> defaultConsumerBinding1 = binder.bindConsumer("partDLQ.0", "default",
|
||||
new QueueChannel(), properties);
|
||||
properties.setInstanceIndex(1);
|
||||
DirectChannel input1 = new DirectChannel();
|
||||
input1.setBeanName("test.input1DLQ");
|
||||
Binding<MessageChannel> input1Binding = binder.bindConsumer("partDLQ.0", "dlqPartGrp", input1, properties);
|
||||
Binding<MessageChannel> defaultConsumerBinding2 = binder.bindConsumer("partDLQ.0", "default",
|
||||
new QueueChannel(), properties);
|
||||
|
||||
ExtendedProducerProperties<RabbitProducerProperties> producerProperties = createProducerProperties();
|
||||
producerProperties.getExtension().setPrefix("bindertest.");
|
||||
producerProperties.getExtension().setAutoBindDlq(true);
|
||||
producerProperties.setPartitionKeyExtractorClass(PartitionTestSupport.class);
|
||||
producerProperties.setPartitionSelectorClass(PartitionTestSupport.class);
|
||||
producerProperties.setPartitionCount(2);
|
||||
DirectChannel output = new DirectChannel();
|
||||
output.setBeanName("test.output");
|
||||
Binding<MessageChannel> outputBinding = binder.bindProducer("partDLQ.0", output, producerProperties);
|
||||
|
||||
final CountDownLatch latch0 = new CountDownLatch(1);
|
||||
input0.subscribe(new MessageHandler() {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
if (latch0.getCount() <= 0) {
|
||||
throw new RuntimeException("dlq");
|
||||
}
|
||||
latch0.countDown();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
final CountDownLatch latch1 = new CountDownLatch(1);
|
||||
input1.subscribe(new MessageHandler() {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
if (latch1.getCount() <= 0) {
|
||||
throw new RuntimeException("dlq");
|
||||
}
|
||||
latch1.countDown();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
output.send(new GenericMessage<>(1));
|
||||
assertThat(latch1.await(10, TimeUnit.SECONDS)).isTrue();
|
||||
|
||||
output.send(new GenericMessage<>(0));
|
||||
assertThat(latch0.await(10, TimeUnit.SECONDS)).isTrue();
|
||||
|
||||
output.send(new GenericMessage<>(1));
|
||||
|
||||
RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
|
||||
template.setReceiveTimeout(10000);
|
||||
|
||||
String streamDLQName = "bindertest.partDLQ.0.dlqPartGrp.dlq";
|
||||
|
||||
org.springframework.amqp.core.Message received = template.receive(streamDLQName);
|
||||
assertThat(received).isNotNull();
|
||||
assertThat(received.getMessageProperties().getHeaders()).containsEntry("partition", 1);
|
||||
|
||||
output.send(new GenericMessage<>(0));
|
||||
received = template.receive(streamDLQName);
|
||||
assertThat(received).isNotNull();
|
||||
assertThat(received.getMessageProperties().getHeaders()).containsEntry("partition", 0);
|
||||
|
||||
input0Binding.unbind();
|
||||
input1Binding.unbind();
|
||||
defaultConsumerBinding1.unbind();
|
||||
defaultConsumerBinding2.unbind();
|
||||
outputBinding.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoBindDLQPartitionedProducerFirst() throws Exception {
|
||||
RabbitTestBinder binder = getBinder();
|
||||
ExtendedProducerProperties<RabbitProducerProperties> properties = createProducerProperties();
|
||||
|
||||
properties.getExtension().setPrefix("bindertest.");
|
||||
properties.getExtension().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<MessageChannel> outputBinding = binder.bindProducer("partDLQ.1", output, properties);
|
||||
|
||||
ExtendedConsumerProperties<RabbitConsumerProperties> consumerProperties = createConsumerProperties();
|
||||
consumerProperties.getExtension().setPrefix("bindertest.");
|
||||
consumerProperties.getExtension().setAutoBindDlq(true);
|
||||
consumerProperties.setMaxAttempts(1); // disable retry
|
||||
consumerProperties.getExtension().setRequeueRejected(false);
|
||||
consumerProperties.setPartitioned(true);
|
||||
consumerProperties.setInstanceIndex(0);
|
||||
DirectChannel input0 = new DirectChannel();
|
||||
input0.setBeanName("test.input0DLQ");
|
||||
Binding<MessageChannel> input0Binding = binder.bindConsumer("partDLQ.1", "dlqPartGrp", input0,
|
||||
consumerProperties);
|
||||
Binding<MessageChannel> defaultConsumerBinding1 = binder.bindConsumer("partDLQ.1", "defaultConsumer",
|
||||
new QueueChannel(), consumerProperties);
|
||||
consumerProperties.setInstanceIndex(1);
|
||||
DirectChannel input1 = new DirectChannel();
|
||||
input1.setBeanName("test.input1DLQ");
|
||||
Binding<MessageChannel> input1Binding = binder.bindConsumer("partDLQ.1", "dlqPartGrp", input1,
|
||||
consumerProperties);
|
||||
Binding<MessageChannel> defaultConsumerBinding2 = binder.bindConsumer("partDLQ.1", "defaultConsumer",
|
||||
new QueueChannel(), consumerProperties);
|
||||
|
||||
final CountDownLatch latch0 = new CountDownLatch(1);
|
||||
input0.subscribe(new MessageHandler() {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
if (latch0.getCount() <= 0) {
|
||||
throw new RuntimeException("dlq");
|
||||
}
|
||||
latch0.countDown();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
final CountDownLatch latch1 = new CountDownLatch(1);
|
||||
input1.subscribe(new MessageHandler() {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
if (latch1.getCount() <= 0) {
|
||||
throw new RuntimeException("dlq");
|
||||
}
|
||||
latch1.countDown();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
output.send(new GenericMessage<Integer>(1));
|
||||
assertThat(latch1.await(10, TimeUnit.SECONDS)).isTrue();
|
||||
|
||||
output.send(new GenericMessage<Integer>(0));
|
||||
assertThat(latch0.await(10, TimeUnit.SECONDS)).isTrue();
|
||||
|
||||
output.send(new GenericMessage<Integer>(1));
|
||||
|
||||
RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
|
||||
template.setReceiveTimeout(10000);
|
||||
|
||||
String streamDLQName = "bindertest.partDLQ.1.dlqPartGrp.dlq";
|
||||
|
||||
org.springframework.amqp.core.Message received = template.receive(streamDLQName);
|
||||
assertThat(received).isNotNull();
|
||||
assertThat(received.getMessageProperties().getHeaders()).containsEntry("partition", 1);
|
||||
|
||||
output.send(new GenericMessage<Integer>(0));
|
||||
received = template.receive(streamDLQName);
|
||||
assertThat(received).isNotNull();
|
||||
assertThat(received.getMessageProperties().getHeaders()).containsEntry("partition", 0);
|
||||
|
||||
input0Binding.unbind();
|
||||
input1Binding.unbind();
|
||||
defaultConsumerBinding1.unbind();
|
||||
defaultConsumerBinding2.unbind();
|
||||
outputBinding.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoBindDLQwithRepublish() throws Exception {
|
||||
// pre-declare the queue with dead-lettering, users can also use a policy
|
||||
RabbitAdmin admin = new RabbitAdmin(this.rabbitAvailableRule.getResource());
|
||||
Map<String, Object> args = new HashMap<String, Object>();
|
||||
args.put("x-dead-letter-exchange", TEST_PREFIX + "DLX");
|
||||
args.put("x-dead-letter-routing-key", TEST_PREFIX + "dlqpubtest.default");
|
||||
Queue queue = new Queue(TEST_PREFIX + "dlqpubtest.default", true, false, false, args);
|
||||
admin.declareQueue(queue);
|
||||
|
||||
RabbitTestBinder binder = getBinder();
|
||||
ExtendedConsumerProperties<RabbitConsumerProperties> properties = createConsumerProperties();
|
||||
properties.getExtension().setPrefix(TEST_PREFIX);
|
||||
properties.getExtension().setAutoBindDlq(true);
|
||||
properties.getExtension().setRepublishToDlq(true);
|
||||
properties.setMaxAttempts(1); // disable retry
|
||||
properties.getExtension().setRequeueRejected(false);
|
||||
properties.getExtension().setDurableSubscription(true);
|
||||
DirectChannel moduleInputChannel = new DirectChannel();
|
||||
moduleInputChannel.setBeanName("dlqPubTest");
|
||||
moduleInputChannel.subscribe(new MessageHandler() {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
throw new RuntimeException("foo");
|
||||
}
|
||||
|
||||
});
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("dlqpubtest", "default", moduleInputChannel,
|
||||
properties);
|
||||
|
||||
RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
|
||||
template.convertAndSend("", TEST_PREFIX + "dlqpubtest.default", "foo");
|
||||
|
||||
int n = 0;
|
||||
while (n++ < 100) {
|
||||
org.springframework.amqp.core.Message deadLetter = template.receive(TEST_PREFIX + "dlqpubtest.default.dlq");
|
||||
if (deadLetter != null) {
|
||||
assertThat(new String(deadLetter.getBody())).isEqualTo("foo");
|
||||
assertThat(deadLetter.getMessageProperties().getHeaders()).containsKey(("x-exception-stacktrace"));
|
||||
break;
|
||||
}
|
||||
Thread.sleep(100);
|
||||
}
|
||||
assertThat(n).isLessThan(100);
|
||||
|
||||
consumerBinding.unbind();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testBatchingAndCompression() throws Exception {
|
||||
RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
|
||||
RabbitTestBinder binder = getBinder();
|
||||
ExtendedProducerProperties<RabbitProducerProperties> properties = createProducerProperties();
|
||||
properties.getExtension().setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);
|
||||
properties.getExtension().setBatchingEnabled(true);
|
||||
properties.getExtension().setBatchSize(2);
|
||||
properties.getExtension().setBatchBufferLimit(100000);
|
||||
properties.getExtension().setBatchTimeout(30000);
|
||||
properties.getExtension().setCompress(true);
|
||||
properties.setRequiredGroups("default");
|
||||
|
||||
DirectChannel output = new DirectChannel();
|
||||
output.setBeanName("batchingProducer");
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer("batching.0", output, properties);
|
||||
|
||||
Log logger = spy(TestUtils.getPropertyValue(binder, "binder.compressingPostProcessor.logger", Log.class));
|
||||
new DirectFieldAccessor(TestUtils.getPropertyValue(binder, "binder.compressingPostProcessor"))
|
||||
.setPropertyValue("logger", logger);
|
||||
when(logger.isTraceEnabled()).thenReturn(true);
|
||||
|
||||
assertThat(TestUtils.getPropertyValue(binder, "binder.compressingPostProcessor.level"))
|
||||
.isEqualTo(Deflater.BEST_SPEED);
|
||||
|
||||
output.send(new GenericMessage<>("foo".getBytes()));
|
||||
output.send(new GenericMessage<>("bar".getBytes()));
|
||||
|
||||
Object out = spyOn("batching.0.default").receive(false);
|
||||
assertThat(out).isInstanceOf(byte[].class);
|
||||
assertThat(new String((byte[]) out)).isEqualTo("\u0000\u0000\u0000\u0003foo\u0000\u0000\u0000\u0003bar");
|
||||
|
||||
ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class);
|
||||
verify(logger).trace(captor.capture());
|
||||
assertThat(captor.getValue().toString()).contains(("Compressed 14 to "));
|
||||
|
||||
QueueChannel input = new QueueChannel();
|
||||
input.setBeanName("batchingConsumer");
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer("batching.0", "test", input,
|
||||
createConsumerProperties());
|
||||
|
||||
output.send(new GenericMessage<>("foo".getBytes()));
|
||||
output.send(new GenericMessage<>("bar".getBytes()));
|
||||
|
||||
Message<byte[]> in = (Message<byte[]>) input.receive(10000);
|
||||
assertThat(in).isNotNull();
|
||||
assertThat(new String(in.getPayload())).isEqualTo("foo");
|
||||
in = (Message<byte[]>) input.receive(10000);
|
||||
assertThat(in).isNotNull();
|
||||
assertThat(new String(in.getPayload())).isEqualTo("bar");
|
||||
assertThat(in.getHeaders().get(AmqpHeaders.DELIVERY_MODE)).isNull();
|
||||
|
||||
producerBinding.unbind();
|
||||
consumerBinding.unbind();
|
||||
}
|
||||
|
||||
/*
|
||||
* Test late binding due to broker down; queues with and without DLQs, and partitioned
|
||||
* queues.
|
||||
*/
|
||||
@Test
|
||||
public void testLateBinding() throws Exception {
|
||||
RabbitTestSupport.RabbitProxy proxy = new RabbitTestSupport.RabbitProxy();
|
||||
CachingConnectionFactory cf = new CachingConnectionFactory("localhost", proxy.getPort());
|
||||
RabbitMessageChannelBinder rabbitBinder = new RabbitMessageChannelBinder(cf, new RabbitProperties());
|
||||
RabbitTestBinder binder = new RabbitTestBinder(cf, rabbitBinder);
|
||||
|
||||
ExtendedProducerProperties<RabbitProducerProperties> properties = createProducerProperties();
|
||||
properties.getExtension().setPrefix("latebinder.");
|
||||
properties.getExtension().setAutoBindDlq(true);
|
||||
|
||||
MessageChannel moduleOutputChannel = new DirectChannel();
|
||||
Binding<MessageChannel> late0ProducerBinding = binder.bindProducer("late.0", moduleOutputChannel, properties);
|
||||
|
||||
QueueChannel moduleInputChannel = new QueueChannel();
|
||||
ExtendedConsumerProperties<RabbitConsumerProperties> rabbitConsumerProperties = createConsumerProperties();
|
||||
rabbitConsumerProperties.getExtension().setPrefix("latebinder.");
|
||||
Binding<MessageChannel> late0ConsumerBinding = binder.bindConsumer("late.0", "test", moduleInputChannel,
|
||||
rabbitConsumerProperties);
|
||||
|
||||
properties.setPartitionKeyExpression(spelExpressionParser.parseExpression("payload.equals('0') ? 0 : 1"));
|
||||
properties.setPartitionSelectorExpression(spelExpressionParser.parseExpression("hashCode()"));
|
||||
properties.setPartitionCount(2);
|
||||
|
||||
MessageChannel partOutputChannel = new DirectChannel();
|
||||
Binding<MessageChannel> partlate0ProducerBinding = binder.bindProducer("partlate.0", partOutputChannel,
|
||||
properties);
|
||||
|
||||
QueueChannel partInputChannel0 = new QueueChannel();
|
||||
QueueChannel partInputChannel1 = new QueueChannel();
|
||||
|
||||
ExtendedConsumerProperties<RabbitConsumerProperties> partLateConsumerProperties = createConsumerProperties();
|
||||
partLateConsumerProperties.getExtension().setPrefix("latebinder.");
|
||||
partLateConsumerProperties.setPartitioned(true);
|
||||
partLateConsumerProperties.setInstanceIndex(0);
|
||||
Binding<MessageChannel> partlate0Consumer0Binding = binder.bindConsumer("partlate.0", "test", partInputChannel0,
|
||||
partLateConsumerProperties);
|
||||
partLateConsumerProperties.setInstanceIndex(1);
|
||||
Binding<MessageChannel> partlate0Consumer1Binding = binder.bindConsumer("partlate.0", "test", partInputChannel1,
|
||||
partLateConsumerProperties);
|
||||
|
||||
ExtendedProducerProperties<RabbitProducerProperties> noDlqProducerProperties = createProducerProperties();
|
||||
noDlqProducerProperties.getExtension().setPrefix("latebinder.");
|
||||
MessageChannel noDLQOutputChannel = new DirectChannel();
|
||||
Binding<MessageChannel> noDlqProducerBinding = binder.bindProducer("lateNoDLQ.0", noDLQOutputChannel,
|
||||
noDlqProducerProperties);
|
||||
|
||||
QueueChannel noDLQInputChannel = new QueueChannel();
|
||||
ExtendedConsumerProperties<RabbitConsumerProperties> noDlqConsumerProperties = createConsumerProperties();
|
||||
noDlqConsumerProperties.getExtension().setPrefix("latebinder.");
|
||||
Binding<MessageChannel> noDlqConsumerBinding = binder.bindConsumer("lateNoDLQ.0", "test", noDLQInputChannel,
|
||||
noDlqConsumerProperties);
|
||||
|
||||
MessageChannel outputChannel = new DirectChannel();
|
||||
Binding<MessageChannel> pubSubProducerBinding = binder.bindProducer("latePubSub", outputChannel,
|
||||
noDlqProducerProperties);
|
||||
QueueChannel pubSubInputChannel = new QueueChannel();
|
||||
noDlqConsumerProperties.getExtension().setDurableSubscription(false);
|
||||
Binding<MessageChannel> nonDurableConsumerBinding = binder.bindConsumer("latePubSub", "lategroup",
|
||||
pubSubInputChannel, noDlqConsumerProperties);
|
||||
QueueChannel durablePubSubInputChannel = new QueueChannel();
|
||||
noDlqConsumerProperties.getExtension().setDurableSubscription(true);
|
||||
Binding<MessageChannel> durableConsumerBinding = binder.bindConsumer("latePubSub", "lateDurableGroup",
|
||||
durablePubSubInputChannel, noDlqConsumerProperties);
|
||||
|
||||
proxy.start();
|
||||
|
||||
moduleOutputChannel.send(new GenericMessage<>("foo"));
|
||||
Message<?> message = moduleInputChannel.receive(10000);
|
||||
assertThat(message).isNotNull();
|
||||
assertThat(message.getPayload()).isNotNull();
|
||||
|
||||
noDLQOutputChannel.send(new GenericMessage<>("bar"));
|
||||
message = noDLQInputChannel.receive(10000);
|
||||
assertThat(message);
|
||||
assertThat(message.getPayload()).isEqualTo("bar");
|
||||
|
||||
outputChannel.send(new GenericMessage<>("baz"));
|
||||
message = pubSubInputChannel.receive(10000);
|
||||
assertThat(message);
|
||||
assertThat(message.getPayload()).isEqualTo("baz");
|
||||
message = durablePubSubInputChannel.receive(10000);
|
||||
assertThat(message).isNotNull();
|
||||
assertThat(message.getPayload()).isEqualTo("baz");
|
||||
|
||||
partOutputChannel.send(new GenericMessage<>("0"));
|
||||
partOutputChannel.send(new GenericMessage<>("1"));
|
||||
message = partInputChannel0.receive(10000);
|
||||
assertThat(message).isNotNull();
|
||||
assertThat(message.getPayload()).isEqualTo("0");
|
||||
message = partInputChannel1.receive(10000);
|
||||
assertThat(message).isNotNull();
|
||||
assertThat(message.getPayload()).isEqualTo("1");
|
||||
|
||||
late0ProducerBinding.unbind();
|
||||
late0ConsumerBinding.unbind();
|
||||
partlate0ProducerBinding.unbind();
|
||||
partlate0Consumer0Binding.unbind();
|
||||
partlate0Consumer1Binding.unbind();
|
||||
noDlqProducerBinding.unbind();
|
||||
noDlqConsumerBinding.unbind();
|
||||
pubSubProducerBinding.unbind();
|
||||
nonDurableConsumerBinding.unbind();
|
||||
durableConsumerBinding.unbind();
|
||||
|
||||
binder.cleanup();
|
||||
|
||||
proxy.stop();
|
||||
cf.destroy();
|
||||
|
||||
this.rabbitAvailableRule.getResource().destroy();
|
||||
}
|
||||
|
||||
private SimpleMessageListenerContainer verifyContainer(AbstractEndpoint endpoint) {
|
||||
SimpleMessageListenerContainer container;
|
||||
Advice retry;
|
||||
container = TestUtils.getPropertyValue(endpoint, "messageListenerContainer",
|
||||
SimpleMessageListenerContainer.class);
|
||||
assertThat(container.getAcknowledgeMode()).isEqualTo(AcknowledgeMode.NONE);
|
||||
assertThat(container.getQueueNames()[0]).startsWith("foo.props.0");
|
||||
assertThat(TestUtils.getPropertyValue(container, "transactional", Boolean.class)).isFalse();
|
||||
assertThat(TestUtils.getPropertyValue(container, "concurrentConsumers")).isEqualTo(2);
|
||||
assertThat(TestUtils.getPropertyValue(container, "maxConcurrentConsumers")).isEqualTo(3);
|
||||
assertThat(TestUtils.getPropertyValue(container, "defaultRequeueRejected", Boolean.class)).isFalse();
|
||||
assertThat(TestUtils.getPropertyValue(container, "prefetchCount")).isEqualTo(20);
|
||||
assertThat(TestUtils.getPropertyValue(container, "txSize")).isEqualTo(10);
|
||||
retry = TestUtils.getPropertyValue(container, "adviceChain", Advice[].class)[0];
|
||||
assertThat(TestUtils.getPropertyValue(retry, "retryOperations.retryPolicy.maxAttempts")).isEqualTo(23);
|
||||
assertThat(TestUtils.getPropertyValue(retry, "retryOperations.backOffPolicy.initialInterval")).isEqualTo(2000L);
|
||||
assertThat(TestUtils.getPropertyValue(retry, "retryOperations.backOffPolicy.maxInterval")).isEqualTo(20000L);
|
||||
assertThat(TestUtils.getPropertyValue(retry, "retryOperations.backOffPolicy.multiplier")).isEqualTo(5.0);
|
||||
|
||||
List<?> requestMatchers = TestUtils.getPropertyValue(endpoint, "headerMapper.requestHeaderMatcher.matchers",
|
||||
List.class);
|
||||
assertThat(requestMatchers).hasSize(1);
|
||||
assertThat(TestUtils.getPropertyValue(requestMatchers.get(0), "pattern")).isEqualTo("foo");
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
private void verifyFooRequestProducer(AbstractEndpoint endpoint) {
|
||||
List<?> requestMatchers = TestUtils.getPropertyValue(endpoint,
|
||||
"handler.delegate.headerMapper.requestHeaderMatcher.matchers", List.class);
|
||||
assertThat(requestMatchers).hasSize(1);
|
||||
assertThat(TestUtils.getPropertyValue(requestMatchers.get(0), "pattern")).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getEndpointRouting(AbstractEndpoint endpoint) {
|
||||
return TestUtils.getPropertyValue(endpoint, "handler.delegate.routingKeyExpression", SpelExpression.class)
|
||||
.getExpressionString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getExpectedRoutingBaseDestination(String name, String group) {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPubSubEndpointRouting(AbstractEndpoint endpoint) {
|
||||
return TestUtils.getPropertyValue(endpoint, "handler.delegate.exchangeNameExpression", SpelExpression.class)
|
||||
.getExpressionString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getClassUnderTestName() {
|
||||
return CLASS_UNDER_TEST_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spy spyOn(final String queue) {
|
||||
final RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
|
||||
template.setAfterReceivePostProcessors(new DelegatingDecompressingPostProcessor());
|
||||
return new Spy() {
|
||||
|
||||
@Override
|
||||
public Object receive(boolean expectNull) throws Exception {
|
||||
if (expectNull) {
|
||||
Thread.sleep(50);
|
||||
return template.receiveAndConvert(new RabbitConsumerProperties().getPrefix() + queue);
|
||||
}
|
||||
Object bar = null;
|
||||
int n = 0;
|
||||
while (n++ < 100 && bar == null) {
|
||||
bar = template.receiveAndConvert(new RabbitConsumerProperties().getPrefix() + queue);
|
||||
Thread.sleep(100);
|
||||
}
|
||||
assertThat(n).isLessThan(100).withFailMessage("Message did not arrive in RabbitMQ");
|
||||
return bar;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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 java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
||||
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
|
||||
import org.springframework.cloud.stream.binder.AbstractTestBinder;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.ExtendedProducerProperties;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.integration.codec.kryo.PojoCodec;
|
||||
import org.springframework.integration.context.IntegrationContextUtils;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
|
||||
/**
|
||||
* Test support class for {@link RabbitMessageChannelBinder}.
|
||||
*
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Gary Russell
|
||||
* @author David Turanski
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class RabbitTestBinder extends AbstractTestBinder<RabbitMessageChannelBinder, ExtendedConsumerProperties<RabbitConsumerProperties>, ExtendedProducerProperties<RabbitProducerProperties>> {
|
||||
|
||||
private final RabbitAdmin rabbitAdmin;
|
||||
|
||||
private final Set<String> prefixes = new HashSet<>();
|
||||
|
||||
private final Set<String> queues = new HashSet<String>();
|
||||
|
||||
private final Set<String> exchanges = new HashSet<String>();
|
||||
|
||||
public RabbitTestBinder(ConnectionFactory connectionFactory, RabbitProperties rabbitProperties) {
|
||||
this(connectionFactory, new RabbitMessageChannelBinder(connectionFactory, rabbitProperties));
|
||||
}
|
||||
|
||||
public RabbitTestBinder(ConnectionFactory connectionFactory, RabbitMessageChannelBinder binder) {
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
|
||||
scheduler.setPoolSize(1);
|
||||
scheduler.afterPropertiesSet();
|
||||
context.getBeanFactory().registerSingleton(IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME, scheduler);
|
||||
context.refresh();
|
||||
binder.setApplicationContext(context);
|
||||
binder.setCodec(new PojoCodec());
|
||||
this.setBinder(binder);
|
||||
this.rabbitAdmin = new RabbitAdmin(connectionFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Binding<MessageChannel> bindConsumer(String name, String group, MessageChannel moduleInputChannel,
|
||||
ExtendedConsumerProperties<RabbitConsumerProperties> properties) {
|
||||
if (group != null) {
|
||||
this.queues.add(properties.getExtension().getPrefix() + name + ("." + group));
|
||||
}
|
||||
this.exchanges.add(properties.getExtension().getPrefix() + name);
|
||||
return super.bindConsumer(name, group, moduleInputChannel, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Binding<MessageChannel> bindProducer(String name, MessageChannel moduleOutputChannel,
|
||||
ExtendedProducerProperties<RabbitProducerProperties> properties) {
|
||||
this.queues.add(properties.getExtension().getPrefix() + name + ".default");
|
||||
this.exchanges.add(properties.getExtension().getPrefix() + name);
|
||||
return super.bindProducer(name, moduleOutputChannel, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
for (String queue : this.queues) {
|
||||
this.rabbitAdmin.deleteQueue(queue);
|
||||
this.rabbitAdmin.deleteQueue(queue + ".dlq");
|
||||
// delete any partitioned queues
|
||||
for (int i = 0; i < 10; i++) {
|
||||
this.rabbitAdmin.deleteQueue(queue + "-" + i);
|
||||
this.rabbitAdmin.deleteQueue(queue + "-" + i + ".dlq");
|
||||
}
|
||||
}
|
||||
for (String exchange : this.exchanges) {
|
||||
this.rabbitAdmin.deleteExchange(exchange);
|
||||
}
|
||||
for (String prefix : this.prefixes) {
|
||||
this.rabbitAdmin.deleteExchange(prefix + "DLX");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.integration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
||||
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||
import org.springframework.amqp.utils.test.TestUtils;
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.actuate.health.CompositeHealthIndicator;
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
import org.springframework.boot.actuate.health.Status;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
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.binder.rabbit.RabbitMessageChannelBinder;
|
||||
import org.springframework.cloud.stream.binding.ChannelBindingService;
|
||||
import org.springframework.cloud.stream.messaging.Processor;
|
||||
import org.springframework.cloud.stream.test.junit.rabbit.RabbitTestSupport;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public class RabbitBinderModuleTests {
|
||||
|
||||
@ClassRule
|
||||
public static RabbitTestSupport rabbitTestSupport = new RabbitTestSupport();
|
||||
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
public static final ConnectionFactory MOCK_CONNECTION_FACTORY = Mockito.mock(ConnectionFactory.class,
|
||||
Mockito.RETURNS_MOCKS);
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
if (context != null) {
|
||||
context.close();
|
||||
context = null;
|
||||
}
|
||||
RabbitAdmin admin = new RabbitAdmin(rabbitTestSupport.getResource());
|
||||
admin.deleteQueue("binder.input.default");
|
||||
admin.deleteQueue("binder.output.default");
|
||||
admin.deleteExchange("binder.input");
|
||||
admin.deleteExchange("binder.output");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParentConnectionFactoryInheritedByDefault() {
|
||||
context = SpringApplication.run(SimpleProcessor.class, "--server.port=0");
|
||||
BinderFactory<?> binderFactory = context.getBean(BinderFactory.class);
|
||||
Binder binder = binderFactory.getBinder(null);
|
||||
assertThat(binder).isInstanceOf(RabbitMessageChannelBinder.class);
|
||||
DirectFieldAccessor binderFieldAccessor = new DirectFieldAccessor(binder);
|
||||
ConnectionFactory binderConnectionFactory = (ConnectionFactory) binderFieldAccessor
|
||||
.getPropertyValue("connectionFactory");
|
||||
assertThat(binderConnectionFactory).isInstanceOf(CachingConnectionFactory.class);
|
||||
ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class);
|
||||
assertThat(binderConnectionFactory).isSameAs(connectionFactory);
|
||||
CompositeHealthIndicator bindersHealthIndicator = context.getBean("bindersHealthIndicator",
|
||||
CompositeHealthIndicator.class);
|
||||
DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(bindersHealthIndicator);
|
||||
assertThat(bindersHealthIndicator).isNotNull();
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, HealthIndicator> healthIndicators = (Map<String, HealthIndicator>) directFieldAccessor
|
||||
.getPropertyValue("indicators");
|
||||
assertThat(healthIndicators).containsKey(("rabbit"));
|
||||
assertThat(healthIndicators.get("rabbit").health().getStatus()).isEqualTo((Status.UP));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testParentConnectionFactoryInheritedByDefaultAndRabbitSettingsPropagated() {
|
||||
context = SpringApplication.run(SimpleProcessor.class, "--server.port=0",
|
||||
"--spring.cloud.stream.rabbit.bindings.input.consumer.transacted=true",
|
||||
"--spring.cloud.stream.rabbit.bindings.output.producer.transacted=true");
|
||||
BinderFactory<?> binderFactory = context.getBean(BinderFactory.class);
|
||||
Binder binder = binderFactory.getBinder(null);
|
||||
assertThat(binder).isInstanceOf(RabbitMessageChannelBinder.class);
|
||||
ChannelBindingService channelBindingService = context.getBean(ChannelBindingService.class);
|
||||
DirectFieldAccessor channelBindingServiceAccessor = new DirectFieldAccessor(channelBindingService);
|
||||
Map<String, List<Binding<MessageChannel>>> consumerBindings = (Map<String, List<Binding<MessageChannel>>>) channelBindingServiceAccessor
|
||||
.getPropertyValue("consumerBindings");
|
||||
Binding<MessageChannel> inputBinding = consumerBindings.get("input").get(0);
|
||||
SimpleMessageListenerContainer container = TestUtils.getPropertyValue(inputBinding,
|
||||
"endpoint.messageListenerContainer", SimpleMessageListenerContainer.class);
|
||||
assertThat(TestUtils.getPropertyValue(container, "transactional", Boolean.class)).isTrue();
|
||||
Map<String, Binding<MessageChannel>> producerBindings = (Map<String, Binding<MessageChannel>>) TestUtils
|
||||
.getPropertyValue(channelBindingService, "producerBindings");
|
||||
Binding<MessageChannel> outputBinding = producerBindings.get("output");
|
||||
assertThat(TestUtils.getPropertyValue(outputBinding, "endpoint.handler.delegate.amqpTemplate.transactional",
|
||||
Boolean.class)).isTrue();
|
||||
DirectFieldAccessor binderFieldAccessor = new DirectFieldAccessor(binder);
|
||||
ConnectionFactory binderConnectionFactory = (ConnectionFactory) binderFieldAccessor
|
||||
.getPropertyValue("connectionFactory");
|
||||
assertThat(binderConnectionFactory).isInstanceOf(CachingConnectionFactory.class);
|
||||
ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class);
|
||||
assertThat(binderConnectionFactory).isSameAs(connectionFactory);
|
||||
CompositeHealthIndicator bindersHealthIndicator = context.getBean("bindersHealthIndicator",
|
||||
CompositeHealthIndicator.class);
|
||||
DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(bindersHealthIndicator);
|
||||
assertThat(bindersHealthIndicator).isNotNull();
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, HealthIndicator> healthIndicators = (Map<String, HealthIndicator>) directFieldAccessor
|
||||
.getPropertyValue("indicators");
|
||||
assertThat(healthIndicators).containsKey("rabbit");
|
||||
assertThat(healthIndicators.get("rabbit").health().getStatus()).isEqualTo(Status.UP);
|
||||
}
|
||||
|
||||
@Test
|
||||
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);
|
||||
assertThat(binder).isInstanceOf(RabbitMessageChannelBinder.class);
|
||||
DirectFieldAccessor binderFieldAccessor = new DirectFieldAccessor(binder);
|
||||
ConnectionFactory binderConnectionFactory = (ConnectionFactory) binderFieldAccessor
|
||||
.getPropertyValue("connectionFactory");
|
||||
assertThat(binderConnectionFactory).isSameAs(MOCK_CONNECTION_FACTORY);
|
||||
ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class);
|
||||
assertThat(binderConnectionFactory).isSameAs(connectionFactory);
|
||||
CompositeHealthIndicator bindersHealthIndicator = context.getBean("bindersHealthIndicator",
|
||||
CompositeHealthIndicator.class);
|
||||
assertThat(bindersHealthIndicator).isNotNull();
|
||||
DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(bindersHealthIndicator);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, HealthIndicator> healthIndicators = (Map<String, HealthIndicator>) directFieldAccessor
|
||||
.getPropertyValue("indicators");
|
||||
assertThat(healthIndicators).containsKey("rabbit");
|
||||
// mock connection factory behaves as if down
|
||||
assertThat(healthIndicators.get("rabbit").health().getStatus()).isEqualTo(Status.DOWN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParentConnectionFactoryNotInheritedByCustomizedBinders() {
|
||||
List<String> params = new ArrayList<>();
|
||||
params.add("--spring.cloud.stream.input.binder=custom");
|
||||
params.add("--spring.cloud.stream.output.binder=custom");
|
||||
params.add("--spring.cloud.stream.binders.custom.type=rabbit");
|
||||
params.add("--spring.cloud.stream.binders.custom.environment.foo=bar");
|
||||
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);
|
||||
assertThat(binder).isInstanceOf(RabbitMessageChannelBinder.class);
|
||||
DirectFieldAccessor binderFieldAccessor = new DirectFieldAccessor(binder);
|
||||
ConnectionFactory binderConnectionFactory = (ConnectionFactory) binderFieldAccessor
|
||||
.getPropertyValue("connectionFactory");
|
||||
ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class);
|
||||
assertThat(binderConnectionFactory).isNotSameAs(connectionFactory);
|
||||
CompositeHealthIndicator bindersHealthIndicator = context.getBean("bindersHealthIndicator",
|
||||
CompositeHealthIndicator.class);
|
||||
assertThat(bindersHealthIndicator);
|
||||
DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(bindersHealthIndicator);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, HealthIndicator> healthIndicators = (Map<String, HealthIndicator>) directFieldAccessor
|
||||
.getPropertyValue("indicators");
|
||||
assertThat(healthIndicators).containsKey("custom");
|
||||
assertThat(healthIndicators.get("custom").health().getStatus()).isEqualTo(Status.UP);
|
||||
}
|
||||
|
||||
@EnableBinding(Processor.class)
|
||||
@SpringBootApplication
|
||||
public static class SimpleProcessor {
|
||||
|
||||
}
|
||||
|
||||
public static class ConnectionFactoryConfiguration {
|
||||
|
||||
@Bean
|
||||
public ConnectionFactory connectionFactory() {
|
||||
return MOCK_CONNECTION_FACTORY;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
log4j.rootCategory=DEBUG, stdout
|
||||
|
||||
# standard logging including calling site
|
||||
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %40.40c:%4L - %m%n
|
||||
|
||||
log4j.category.org.springframework.cloud.binder.rabbit=DEBUG
|
||||
Reference in New Issue
Block a user