Moving the Redis Binder out to its own repo

removed redis starter
This commit is contained in:
Mark Fisher
2016-03-17 08:53:14 -04:00
committed by Marius Bogoevici
parent f9f92ae184
commit 2b397b45a7
18 changed files with 5 additions and 1844 deletions

View File

@@ -1,7 +1,6 @@
<?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-binders-parent</artifactId>
<packaging>pom</packaging>
<url>https://github.com/spring-cloud/spring-cloud-stream</url>
@@ -9,6 +8,7 @@
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-parent</artifactId>
@@ -16,12 +16,11 @@
</parent>
<modules>
<module>spring-cloud-stream-binder-test</module>
<module>spring-cloud-stream-binder-rabbit</module>
<module>spring-cloud-stream-binder-redis</module>
<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>

View File

@@ -1,64 +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-redis</artifactId>
<packaging>jar</packaging>
<name>spring-cloud-stream-binder-redis</name>
<description>Redis binder implementation</description>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binders-parent</artifactId>
<version>1.0.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-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-redis</artifactId>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -1,408 +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.redis;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.cloud.stream.binder.AbstractBinder;
import org.springframework.cloud.stream.binder.BinderHeaders;
import org.springframework.cloud.stream.binder.Binding;
import org.springframework.cloud.stream.binder.ConsumerProperties;
import org.springframework.cloud.stream.binder.DefaultBinding;
import org.springframework.cloud.stream.binder.EmbeddedHeadersMessageConverter;
import org.springframework.cloud.stream.binder.MessageValues;
import org.springframework.cloud.stream.binder.PartitionHandler;
import org.springframework.cloud.stream.binder.ProducerProperties;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.endpoint.MessageProducerSupport;
import org.springframework.integration.handler.AbstractMessageHandler;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.redis.inbound.RedisQueueMessageDrivenEndpoint;
import org.springframework.integration.redis.outbound.RedisQueueOutboundChannelAdapter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.retry.RecoveryCallback;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* A {@link org.springframework.cloud.stream.binder.Binder} implementation backed by Redis.
*
* @author Mark Fisher
* @author Gary Russell
* @author David Turanski
* @author Jennifer Hickey
*/
public class RedisMessageChannelBinder extends AbstractBinder<MessageChannel, ConsumerProperties, ProducerProperties> {
private static final String ERROR_HEADER = "errorKey";
static final String CONSUMER_GROUPS_KEY_PREFIX = "groups.";
private static final SpelExpressionParser parser = new SpelExpressionParser();
private final String[] headersToMap;
private final RedisOperations<String, String> redisOperations;
private final RedisConnectionFactory connectionFactory;
private final EmbeddedHeadersMessageConverter embeddedHeadersMessageConverter = new
EmbeddedHeadersMessageConverter();
private final RedisQueueOutboundChannelAdapter errorAdapter;
public RedisMessageChannelBinder(RedisConnectionFactory connectionFactory) {
this(connectionFactory, new String[0]);
}
public RedisMessageChannelBinder(RedisConnectionFactory connectionFactory, String... headersToMap) {
Assert.notNull(connectionFactory, "connectionFactory must not be null");
this.connectionFactory = connectionFactory;
StringRedisTemplate template = new StringRedisTemplate(connectionFactory);
template.afterPropertiesSet();
this.redisOperations = template;
if (headersToMap != null && headersToMap.length > 0) {
String[] combinedHeadersToMap =
Arrays.copyOfRange(BinderHeaders.STANDARD_HEADERS, 0, BinderHeaders.STANDARD_HEADERS.length
+ headersToMap.length);
System.arraycopy(headersToMap, 0, combinedHeadersToMap, BinderHeaders.STANDARD_HEADERS.length,
headersToMap.length);
this.headersToMap = combinedHeadersToMap;
}
else {
this.headersToMap = BinderHeaders.STANDARD_HEADERS;
}
this.errorAdapter = new RedisQueueOutboundChannelAdapter(
parser.parseExpression("headers['" + ERROR_HEADER + "']"), connectionFactory);
}
@Override
public void onInit() {
this.errorAdapter.setIntegrationEvaluationContext(this.evaluationContext);
this.errorAdapter.setBeanFactory(getBeanFactory());
this.errorAdapter.afterPropertiesSet();
}
@Override
protected Binding<MessageChannel> doBindConsumer(final String name, String group, MessageChannel moduleInputChannel, ConsumerProperties properties) {
if (!StringUtils.hasText(group)) {
group = "anonymous." + UUID.randomUUID().toString();
}
String queueName = groupedName(name, group);
if (properties.isPartitioned()) {
queueName += "-" + properties.getInstanceIndex();
}
MessageProducerSupport adapter = createInboundAdapter(properties, queueName);
return doRegisterConsumer(name, group, queueName, moduleInputChannel, adapter, properties);
}
private MessageProducerSupport createInboundAdapter(ConsumerProperties accessor, String queueName) {
MessageProducerSupport adapter;
int concurrency = accessor.getConcurrency();
concurrency = concurrency > 0 ? concurrency : 1;
if (concurrency == 1) {
RedisQueueMessageDrivenEndpoint single = new RedisQueueMessageDrivenEndpoint(queueName,
this.connectionFactory);
single.setBeanFactory(getBeanFactory());
single.setSerializer(null);
adapter = single;
}
else {
adapter = new CompositeRedisQueueMessageDrivenEndpoint(queueName, concurrency);
}
return adapter;
}
private Binding<MessageChannel> doRegisterConsumer(String bindingName, String group, String channelName, MessageChannel moduleInputChannel,
MessageProducerSupport adapter, final ConsumerProperties properties) {
DirectChannel bridgeToModuleChannel = new DirectChannel();
bridgeToModuleChannel.setBeanFactory(this.getBeanFactory());
bridgeToModuleChannel.setBeanName(channelName + ".bridge");
MessageChannel bridgeInputChannel = addRetryIfNeeded(channelName, bridgeToModuleChannel, properties);
adapter.setOutputChannel(bridgeInputChannel);
adapter.setBeanName("inbound." + channelName);
adapter.afterPropertiesSet();
DefaultBinding<MessageChannel> consumerBinding = new DefaultBinding<MessageChannel>(bindingName, group, moduleInputChannel, adapter) {
@Override
protected void afterUnbind() {
String key = RedisMessageChannelBinder.CONSUMER_GROUPS_KEY_PREFIX + getName();
RedisMessageChannelBinder.this.redisOperations.boundZSetOps(key).incrementScore(getGroup(), -1);
}
};
ReceivingHandler convertingBridge = new ReceivingHandler();
convertingBridge.setOutputChannel(moduleInputChannel);
convertingBridge.setBeanName(channelName + ".bridge.handler");
convertingBridge.afterPropertiesSet();
bridgeToModuleChannel.subscribe(convertingBridge);
this.redisOperations.boundZSetOps(CONSUMER_GROUPS_KEY_PREFIX + bindingName).incrementScore(group, 1);
adapter.start();
return consumerBinding;
}
/**
* If retry is enabled, wrap the bridge channel in another that will invoke send() within the scope of a retry
* template.
* @param name The name.
* @param bridgeToModuleChannel The channel.
* @param properties The properties.
* @return The channel, or a wrapper.
*/
private MessageChannel addRetryIfNeeded(final String name, final DirectChannel bridgeToModuleChannel,
ConsumerProperties properties) {
final RetryTemplate retryTemplate = buildRetryTemplateIfRetryEnabled(properties);
if (retryTemplate == null) {
return bridgeToModuleChannel;
}
else {
DirectChannel channel = new DirectChannel() {
@Override
protected boolean doSend(final Message<?> message, final long timeout) {
try {
return retryTemplate.execute(new RetryCallback<Boolean, Exception>() {
@Override
public Boolean doWithRetry(RetryContext context) throws Exception {
return bridgeToModuleChannel.send(message, timeout);
}
}, new RecoveryCallback<Boolean>() {
/**
* Send the failed message to 'ERRORS:[name]'.
*/
@Override
public Boolean recover(RetryContext context) throws Exception {
logger.error(
"Failed to deliver message; retries exhausted; message sent to queue 'ERRORS:"
+ name + "' ", context.getLastThrowable());
errorAdapter.handleMessage(getMessageBuilderFactory().fromMessage(message)
.setHeader(ERROR_HEADER, "ERRORS:" + name)
.build());
return true;
}
});
}
catch (Exception e) {
logger.error("Failed to deliver message", e);
return false;
}
}
};
channel.setBeanName(name + ".bridge");
return channel;
}
}
@Override
protected Binding<MessageChannel> doBindProducer(final String name, MessageChannel moduleOutputChannel, ProducerProperties properties) {
Assert.isInstanceOf(SubscribableChannel.class, moduleOutputChannel);
return doRegisterProducer(name, moduleOutputChannel, properties);
}
private RedisQueueOutboundChannelAdapter createProducerEndpoint(String name, ProducerProperties properties) {
RedisQueueOutboundChannelAdapter queue;
if (!properties.isPartitioned()) {
queue = new RedisQueueOutboundChannelAdapter(name, this.connectionFactory);
}
else {
queue = new RedisQueueOutboundChannelAdapter(
parser.parseExpression(buildPartitionRoutingExpression(name)), this.connectionFactory);
}
queue.setIntegrationEvaluationContext(this.evaluationContext);
queue.setBeanFactory(this.getBeanFactory());
queue.afterPropertiesSet();
return queue;
}
private Binding<MessageChannel> doRegisterProducer(final String name, MessageChannel moduleOutputChannel,
ProducerProperties properties) {
Assert.isInstanceOf(SubscribableChannel.class, moduleOutputChannel);
MessageHandler handler = new SendingHandler(name, properties);
EventDrivenConsumer consumer = new EventDrivenConsumer((SubscribableChannel) moduleOutputChannel, handler);
consumer.setBeanFactory(this.getBeanFactory());
consumer.setBeanName("outbound." + name);
consumer.afterPropertiesSet();
DefaultBinding<MessageChannel> producerBinding =
new DefaultBinding<>(name, null, moduleOutputChannel, consumer);
String[] requiredGroups = properties.getRequiredGroups();
if (!ObjectUtils.isEmpty(requiredGroups)) {
for (String group : requiredGroups) {
this.redisOperations.boundZSetOps(CONSUMER_GROUPS_KEY_PREFIX + name).incrementScore(group, 1);
}
}
consumer.start();
return producerBinding;
}
private class SendingHandler extends AbstractMessageHandler {
private final String bindingName;
private final ProducerProperties producerProperties;
private final Map<String, RedisQueueOutboundChannelAdapter> adapters = new HashMap<>();
private final PartitionHandler partitionHandler;
private SendingHandler(String bindingName, ProducerProperties producerProperties) {
this.bindingName = bindingName;
this.producerProperties = producerProperties;
ConfigurableListableBeanFactory beanFactory = RedisMessageChannelBinder.this.getBeanFactory();
this.setBeanFactory(beanFactory);
this.partitionHandler = new PartitionHandler(beanFactory, evaluationContext, partitionSelector, producerProperties);
refreshChannelAdapters();
}
@Override
protected void handleMessageInternal(Message<?> message) throws Exception {
MessageValues transformed = serializePayloadIfNecessary(message);
if (producerProperties.isPartitioned()) {
transformed.put(PARTITION_HEADER, this.partitionHandler.determinePartition(message));
}
byte[] messageToSend = embeddedHeadersMessageConverter.embedHeaders(transformed,
RedisMessageChannelBinder.this.headersToMap);
refreshChannelAdapters();
for (RedisQueueOutboundChannelAdapter adapter : adapters.values()) {
adapter.handleMessage((MessageBuilder.withPayload(messageToSend).copyHeaders(transformed).build()));
}
}
private void refreshChannelAdapters() {
Set<String> groups = redisOperations.boundZSetOps(CONSUMER_GROUPS_KEY_PREFIX + bindingName).rangeByScore(1, Double.MAX_VALUE);
for (String group : groups) {
if (!adapters.containsKey(group)) {
String channel = String.format("%s.%s", this.bindingName, group);
adapters.put(group, createProducerEndpoint(channel, producerProperties));
}
}
}
}
private class ReceivingHandler extends AbstractReplyProducingMessageHandler {
public ReceivingHandler() {
super();
this.setBeanFactory(RedisMessageChannelBinder.this.getBeanFactory());
}
@SuppressWarnings("unchecked")
@Override
protected Object handleRequestMessage(Message<?> requestMessage) {
MessageValues theRequestMessage;
try {
theRequestMessage = embeddedHeadersMessageConverter.extractHeaders((Message<byte[]>) requestMessage, true);
}
catch (Exception e) {
logger.error(EmbeddedHeadersMessageConverter.decodeExceptionMessage(requestMessage), e);
theRequestMessage = new MessageValues(requestMessage);
}
return deserializePayloadIfNecessary(theRequestMessage).toMessage(getMessageBuilderFactory());
}
@Override
protected boolean shouldCopyRequestHeaders() {
// prevent returned message from being copied in superclass
return false;
}
}
/**
* Provides concurrency by creating a list of message-driven endpoints.
*/
private class CompositeRedisQueueMessageDrivenEndpoint extends MessageProducerSupport {
private final List<RedisQueueMessageDrivenEndpoint> consumers = new
ArrayList<RedisQueueMessageDrivenEndpoint>();
public CompositeRedisQueueMessageDrivenEndpoint(String queueName, int concurrency) {
for (int i = 0; i < concurrency; i++) {
RedisQueueMessageDrivenEndpoint adapter = new RedisQueueMessageDrivenEndpoint(queueName,
connectionFactory);
adapter.setBeanFactory(RedisMessageChannelBinder.this.getBeanFactory());
adapter.setSerializer(null);
adapter.setBeanName("inbound." + queueName + "." + i);
this.consumers.add(adapter);
}
this.setBeanFactory(RedisMessageChannelBinder.this.getBeanFactory());
}
@Override
protected void onInit() {
for (RedisQueueMessageDrivenEndpoint consumer : consumers) {
consumer.afterPropertiesSet();
}
}
@Override
protected void doStart() {
for (RedisQueueMessageDrivenEndpoint consumer : consumers) {
consumer.start();
}
}
@Override
protected void doStop() {
for (RedisQueueMessageDrivenEndpoint consumer : consumers) {
consumer.stop();
}
}
@Override
public void setOutputChannel(MessageChannel outputChannel) {
for (RedisQueueMessageDrivenEndpoint consumer : consumers) {
consumer.setOutputChannel(outputChannel);
}
}
@Override
public void setErrorChannel(MessageChannel errorChannel) {
for (RedisQueueMessageDrivenEndpoint consumer : consumers) {
consumer.setErrorChannel(errorChannel);
}
}
}
}

View File

@@ -1,63 +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.redis.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.stream.binder.redis.RedisMessageChannelBinder;
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.data.redis.connection.RedisConnectionFactory;
import org.springframework.integration.codec.Codec;
/**
* @author David Turanski
*/
@Configuration
@Import({PropertyPlaceholderAutoConfiguration.class, KryoCodecAutoConfiguration.class})
@ConfigurationProperties(prefix = "spring.cloud.stream.binder.redis")
public class RedisMessageChannelBinderConfiguration {
private String[] headers;
@Autowired
private Codec codec;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
public RedisMessageChannelBinder redisMessageChannelBinder() {
RedisMessageChannelBinder redisMessageChannelBinder = new RedisMessageChannelBinder(this.redisConnectionFactory,
this.headers);
redisMessageChannelBinder.setCodec(this.codec);
return redisMessageChannelBinder;
}
public String[] getHeaders() {
return this.headers;
}
public void setHeaders(String[] headers) {
this.headers = headers;
}
}

View File

@@ -1,77 +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.redis.config;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.RedisHealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
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;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
/**
* Bind to services, either locally or in a cloud environment.
*
* @author Mark Fisher
* @author Dave Syer
* @author David Turanski
* @author Eric Bottard
*/
@Configuration
@ConditionalOnMissingBean(Binder.class)
@Import(RedisMessageChannelBinderConfiguration.class)
@PropertySource("classpath:/META-INF/spring-cloud-stream/redis-binder.properties")
@AutoConfigureBefore({CloudAutoConfiguration.class, RedisAutoConfiguration.class})
public class RedisServiceAutoConfiguration {
@Configuration
@ConditionalOnClass(Cloud.class)
@Profile("cloud")
protected static class CloudConfig {
@Bean
public Cloud cloud() {
return new CloudFactory().getCloud();
}
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
RedisConnectionFactory redisConnectionFactory(Cloud cloud) {
return cloud.getSingletonServiceConnector(RedisConnectionFactory.class, null);
}
}
@Profile("!cloud")
@Import(RedisAutoConfiguration.class)
protected static class NoCloudConfig {
}
@Bean
public HealthIndicator binderHealthIndicator(RedisConnectionFactory redisConnectionFactory) {
return new RedisHealthIndicator(redisConnectionFactory);
}
}

View File

@@ -1,5 +0,0 @@
/**
* This package contains an implementation of the {@link org.springframework.cloud.stream.binder.Binder} for Redis.
*/
package org.springframework.cloud.stream.binder.redis;

View File

@@ -1,5 +0,0 @@
spring.cloud.stream.binder.redis.default.backOffInitialInterval: 1000
spring.cloud.stream.binder.redis.default.backOffMaxInterval: 10000
spring.cloud.stream.binder.redis.default.backOffMultiplier: 2.0
spring.cloud.stream.binder.redis.default.concurrency: 1
spring.cloud.stream.binder.redis.default.maxAttempts: 3

View File

@@ -1,2 +0,0 @@
redis:\
org.springframework.cloud.stream.binder.redis.config.RedisServiceAutoConfiguration

View File

@@ -1,156 +0,0 @@
/*
* Copyright 2002-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.redis;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.joda.time.DateTime;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.tuple.Tuple;
import org.springframework.tuple.TupleBuilder;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author David Turanski
*
*/
public abstract class AbstractRedisSerializerTests {
private RedisSerializer<Object> serializer;
protected abstract RedisSerializer<Object> getSerializer();
@Before
public void setUp() {
serializer = getSerializer();
}
@Test
public void testRandomObjectSerialization() {
Foo foo = new Foo("hello");
byte[] bytes = serializer.serialize(foo);
Object obj = serializer.deserialize(bytes);
assertTrue(obj instanceof Foo);
assertEquals("hello", ((Foo) obj).bar);
}
@Test
public void testDateSerialization() {
Date d = new Date();
byte[] bytes = serializer.serialize(d);
Date obj = (Date) serializer.deserialize(bytes);
assertEquals(d, obj);
}
@Test
public void testDateTimeSerialization() {
DateTime d = new DateTime();
byte[] bytes = serializer.serialize(d);
DateTime obj = (DateTime) serializer.deserialize(bytes);
assertEquals(d, obj);
}
@Test
public void testStringSerialization() {
String s = new String("hello");
byte[] bytes = serializer.serialize(s);
Object obj = serializer.deserialize(bytes);
assertEquals(s, obj);
}
@Test
public void testLongSerialization() {
byte[] bytes = serializer.serialize(100L);
long obj = (Long) serializer.deserialize(bytes);
assertEquals(100, obj);
}
@Test
public void testFloatSerialization() {
byte[] bytes = serializer.serialize(99.9f);
double obj = (Double) serializer.deserialize(bytes);
assertEquals(99.9, obj, 0.1);
}
@Test
public void testBooleanSerialization() {
byte[] bytes = serializer.serialize(true);
boolean obj = (Boolean) serializer.deserialize(bytes);
assertTrue(obj);
}
@Test
public void testMapSerialization() {
Map<String, String> map = new HashMap<String, String>();
map.put("foo", "bar");
byte[] bytes = serializer.serialize(map);
Map<?, ?> obj = (Map<?, ?>) serializer.deserialize(bytes);
assertEquals("bar", obj.get("foo"));
}
@Test
public void testListSerialization() {
List<String> list = new LinkedList<String>();
list.add("foo");
byte[] bytes = serializer.serialize(list);
List<?> obj = (List<?>) serializer.deserialize(bytes);
assertEquals("foo", obj.get(0));
}
@Test
public void testSetSerialization() {
Set<String> set = new TreeSet<String>();
set.add("foo");
byte[] bytes = serializer.serialize(set);
Set<?> obj = (Set<?>) serializer.deserialize(bytes);
assertEquals("foo", obj.iterator().next());
}
@Test
public void testTupleSerialization() {
Tuple t = TupleBuilder.tuple().of("foo", "bar");
byte[] bytes = serializer.serialize(t);
Tuple obj = (Tuple) serializer.deserialize(bytes);
assertEquals("bar", obj.getString("foo"));
}
public static class Foo {
@JsonCreator
public Foo(@JsonProperty("bar") String val) {
bar = val;
}
public String bar;
}
}

View File

@@ -1,259 +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.redis;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.cloud.stream.binder.Binding;
import org.springframework.cloud.stream.binder.ConsumerProperties;
import org.springframework.cloud.stream.binder.EmbeddedHeadersMessageConverter;
import org.springframework.cloud.stream.binder.PartitionCapableBinderTests;
import org.springframework.cloud.stream.binder.ProducerProperties;
import org.springframework.cloud.stream.binder.Spy;
import org.springframework.cloud.stream.test.junit.redis.RedisTestSupport;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.expression.Expression;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.redis.inbound.RedisQueueMessageDrivenEndpoint;
import org.springframework.integration.redis.outbound.RedisQueueOutboundChannelAdapter;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.retry.support.RetryTemplate;
/**
* @author Gary Russell
* @author David Turanski
* @author Mark Fisher
*/
public class RedisBinderTests extends PartitionCapableBinderTests<RedisTestBinder, ConsumerProperties, ProducerProperties> {
private final String CLASS_UNDER_TEST_NAME = RedisMessageChannelBinder.class.getSimpleName();
@Rule
public RedisTestSupport redisAvailableRule = new RedisTestSupport();
private RedisTemplate<String, Object> redisTemplate;
private static final EmbeddedHeadersMessageConverter embeddedHeadersMessageConverter =
new EmbeddedHeadersMessageConverter();
@Override
protected RedisTestBinder getBinder() {
if (testBinder == null) {
testBinder = new RedisTestBinder(redisAvailableRule.getResource());
}
return testBinder;
}
@Override
protected ConsumerProperties createConsumerProperties() {
return new ConsumerProperties();
}
@Override
protected ProducerProperties createProducerProperties() {
return new ProducerProperties();
}
@Override
protected boolean usesExplicitRouting() {
return true;
}
@Test
public void testConsumerProperties() throws Exception {
RedisTestBinder binder = getBinder();
ConsumerProperties properties1 = new ConsumerProperties();
properties1.setMaxAttempts(1);
Binding<MessageChannel> binding = binder.bindConsumer("props.0", "test", new DirectChannel(), properties1);
AbstractEndpoint endpoint = extractEndpoint(binding);
assertThat(endpoint, instanceOf(RedisQueueMessageDrivenEndpoint.class));
assertSame(DirectChannel.class, TestUtils.getPropertyValue(endpoint, "outputChannel").getClass());
binding.unbind();
assertFalse(endpoint.isRunning());
ConsumerProperties properties2 = new ConsumerProperties();
properties2.setBackOffInitialInterval(2000);
properties2.setBackOffMaxInterval(20000);
properties2.setBackOffMultiplier(5.0);
properties2.setConcurrency(2);
properties2.setMaxAttempts(23);
properties2.setInstanceIndex(0);
binding = binder.bindConsumer("props.0", "test", new DirectChannel(), properties2);
endpoint = extractEndpoint(binding);
verifyConsumer(endpoint);
binding.unbind();
assertFalse(endpoint.isRunning());
}
@Test
public void testProducerProperties() throws Exception {
RedisTestBinder binder = getBinder();
Binding<MessageChannel> consumerBinding = binder.bindConsumer("props.0", "test", new DirectChannel(), createConsumerProperties());
Binding<MessageChannel> producerBinding = binder.bindProducer("props.0", new DirectChannel(), createProducerProperties());
AbstractEndpoint producerEndpoint = extractEndpoint(producerBinding);
@SuppressWarnings("unchecked")
Map<String, RedisQueueOutboundChannelAdapter> adapters = TestUtils.getPropertyValue(producerEndpoint, "handler.adapters", Map.class);
RedisQueueOutboundChannelAdapter adapter = adapters.get("test");
assertEquals(
"props.0.test",
TestUtils.getPropertyValue(adapter, "queueNameExpression", Expression.class).getExpressionString());
producerBinding.unbind();
assertFalse(producerEndpoint.isRunning());
ProducerProperties producerProperties = new ProducerProperties();
producerProperties.setPartitionKeyExpression(spelExpressionParser.parseExpression("'foo'"));
producerProperties.setPartitionKeyExtractorClass(AbstractRedisSerializerTests.Foo.class);
producerProperties.setPartitionSelectorExpression(spelExpressionParser.parseExpression("0"));
producerProperties.setPartitionSelectorClass(AbstractRedisSerializerTests.Foo.class);
producerProperties.setPartitionCount(1);
producerBinding = binder.bindProducer("props.0", new DirectChannel(), producerProperties);
producerEndpoint = extractEndpoint(producerBinding);
adapter = (RedisQueueOutboundChannelAdapter) TestUtils.getPropertyValue(producerEndpoint, "handler.adapters", Map.class).get("test");
assertEquals(
"'props.0.test-' + headers['partition']",
TestUtils.getPropertyValue(adapter, "queueNameExpression", Expression.class).getExpressionString());
producerBinding.unbind();
consumerBinding.unbind();
assertFalse(producerEndpoint.isRunning());
assertFalse(extractEndpoint(consumerBinding).isRunning());
}
private void verifyConsumer(AbstractEndpoint endpoint) {
assertThat(endpoint.getClass().getName(), containsString("CompositeRedisQueueMessageDrivenEndpoint"));
assertEquals(2, TestUtils.getPropertyValue(endpoint, "consumers", Collection.class).size());
DirectChannel channel = TestUtils.getPropertyValue(
TestUtils.getPropertyValue(endpoint, "consumers", List.class).get(0),
"outputChannel", DirectChannel.class);
assertThat(
channel.getClass().getName(), containsString(getClassUnderTestName() + "$")); // retry wrapper
assertThat(
TestUtils.getPropertyValue(TestUtils.getPropertyValue(endpoint, "consumers", List.class).get(1),
"outputChannel").getClass().getName(), containsString(getClassUnderTestName() + "$")); // retry wrapper
RetryTemplate retry = TestUtils.getPropertyValue(channel, "val$retryTemplate", RetryTemplate.class);
assertEquals(23, TestUtils.getPropertyValue(retry, "retryPolicy.maxAttempts"));
assertEquals(2000L, TestUtils.getPropertyValue(retry, "backOffPolicy.initialInterval"));
assertEquals(20000L, TestUtils.getPropertyValue(retry, "backOffPolicy.maxInterval"));
assertEquals(5.0, TestUtils.getPropertyValue(retry, "backOffPolicy.multiplier"));
}
@Test
public void testRetryFail() {
RedisTestBinder binder = getBinder();
DirectChannel channel = new DirectChannel();
binder.bindProducer("retry.0", channel, createProducerProperties());
ConsumerProperties consumerProperties = new ConsumerProperties();
consumerProperties.setMaxAttempts(2);
consumerProperties.setBackOffInitialInterval(100);
consumerProperties.setBackOffMultiplier(1.0);
Binding<MessageChannel> consumerBinding = binder.bindConsumer("retry.0", "test", new DirectChannel(), consumerProperties); // no subscriber
channel.send(new GenericMessage<>("foo"));
RedisTemplate<String, Object> template = createTemplate();
Object rightPop = template.boundListOps("ERRORS:retry.0.test").rightPop(5, TimeUnit.SECONDS);
assertNotNull(rightPop);
assertThat(new String((byte[]) rightPop), containsString("foo"));
consumerBinding.unbind();
}
@Test
public void testMoreHeaders() {
RedisMessageChannelBinder binder = new RedisMessageChannelBinder(mock(RedisConnectionFactory.class), "foo", "bar");
Collection<String> headers = Arrays.asList(TestUtils.getPropertyValue(binder, "headersToMap", String[].class));
assertEquals(7, headers.size());
assertTrue(headers.contains("foo"));
assertTrue(headers.contains("bar"));
}
private RedisTemplate<String, Object> createTemplate() {
if (this.redisTemplate != null) {
return this.redisTemplate;
}
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(this.redisAvailableRule.getResource());
template.setKeySerializer(new StringRedisSerializer());
template.setEnableDefaultSerializer(false);
template.afterPropertiesSet();
this.redisTemplate = template;
return template;
}
@Override
@SuppressWarnings("unchecked")
protected String getEndpointRouting(AbstractEndpoint endpoint) {
Map<String, RedisQueueOutboundChannelAdapter> adapters = TestUtils.getPropertyValue(endpoint, "handler.adapters", Map.class);
return TestUtils.getPropertyValue(adapters.values().iterator().next(), "queueNameExpression", Expression.class).getExpressionString();
}
@Override
protected String getExpectedRoutingBaseDestination(String name, String group) {
return name + "." + group;
}
@Override
protected String getClassUnderTestName() {
return CLASS_UNDER_TEST_NAME;
}
@Override
public Spy spyOn(final String queue) {
final RedisTemplate<String, Object> template = createTemplate();
return new Spy() {
@Override
public Object receive(boolean expectNull) throws Exception {
byte[] bytes = (byte[]) template.boundListOps(queue).rightPop(50, TimeUnit.MILLISECONDS);
if (bytes == null) {
return null;
}
bytes = (byte[]) embeddedHeadersMessageConverter.extractHeaders(new GenericMessage<byte[]>(bytes), false).getPayload();
return new String(bytes, "UTF-8");
}
};
}
@Override
protected void binderBindUnbindLatency() throws InterruptedException {
Thread.sleep(3000); // needed for Redis see INT-3442
}
}

View File

@@ -1,145 +0,0 @@
/*
* Copyright 2013 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.redis;
import static org.junit.Assert.assertEquals;
import java.util.Collections;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.cloud.stream.binder.BinderTestUtils;
import org.springframework.cloud.stream.test.junit.redis.RedisTestSupport;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.Topic;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.data.redis.serializer.GenericToStringSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.integration.redis.outbound.RedisPublishingMessageHandler;
import org.springframework.integration.support.MessageBuilder;
/**
* Temporary copy of SI RedisPublishingMessageHandlerTests that adds tests that publish messages with data types other
* than String
*
* @author Mark Fisher
* @author Jennifer Hickey
* @author Gary Russell
*/
public class RedisPublishingMessageHandlerTests {
private static final String TOPIC = "si.test.channel";
private static final int NUM_MESSAGES = 10;
private RedisConnectionFactory connectionFactory;
private RedisMessageListenerContainer container;
private CountDownLatch latch = new CountDownLatch(NUM_MESSAGES);
@Rule
public RedisTestSupport redisAvailableRule = new RedisTestSupport();
@Before
public void setUp() {
this.connectionFactory = redisAvailableRule.getResource();
}
@Test
public void testWithDefaultSerializer() throws Exception {
setupListener(new StringRedisSerializer());
final RedisPublishingMessageHandler handler = new RedisPublishingMessageHandler(connectionFactory);
handler.setBeanFactory(BinderTestUtils.MOCK_BF);
handler.setTopic(TOPIC);
handler.afterPropertiesSet();
for (int i = 0; i < NUM_MESSAGES; i++) {
handler.handleMessage(MessageBuilder.withPayload("test-" + i).build());
}
latch.await(3, TimeUnit.SECONDS);
assertEquals(0, latch.getCount());
container.stop();
}
@Test
public void testWithNoSerializer() throws Exception {
setupListener(null);
final RedisPublishingMessageHandler handler = new RedisPublishingMessageHandler(connectionFactory);
handler.setBeanFactory(BinderTestUtils.MOCK_BF);
handler.setTopic(TOPIC);
handler.afterPropertiesSet();
for (int i = 0; i < NUM_MESSAGES; i++) {
handler.handleMessage(MessageBuilder.withPayload(new String("test-" + i).getBytes()).build());
}
latch.await(3, TimeUnit.SECONDS);
assertEquals(0, latch.getCount());
container.stop();
}
@Test
public void testWithCustomSerializer() throws Exception {
GenericToStringSerializer<Long> serializer = new GenericToStringSerializer<Long>(Long.class);
setupListener(serializer);
final RedisPublishingMessageHandler handler = new RedisPublishingMessageHandler(connectionFactory);
handler.setBeanFactory(BinderTestUtils.MOCK_BF);
handler.setTopic(TOPIC);
handler.setSerializer(serializer);
handler.afterPropertiesSet();
for (long i = 0; i < NUM_MESSAGES; i++) {
handler.handleMessage(MessageBuilder.withPayload(i).build());
}
latch.await(3, TimeUnit.SECONDS);
assertEquals(0, latch.getCount());
container.stop();
}
private void setupListener(RedisSerializer<?> listenerSerializer) throws InterruptedException {
MessageListenerAdapter listener = new MessageListenerAdapter();
listener.setDelegate(new Listener(latch));
listener.setSerializer(listenerSerializer);
listener.afterPropertiesSet();
this.container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.afterPropertiesSet();
container.addMessageListener(listener, Collections.<Topic> singletonList(new ChannelTopic(TOPIC)));
container.start();
Thread.sleep(1000);
}
private static class Listener {
private final CountDownLatch latch;
private Listener(CountDownLatch latch) {
this.latch = latch;
}
@SuppressWarnings("unused")
public void handleMessage(Object s) {
this.latch.countDown();
}
}
}

View File

@@ -1,224 +0,0 @@
/*
* Copyright 2002-2013 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.redis;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.cloud.stream.binder.BinderTestUtils;
import org.springframework.cloud.stream.test.junit.redis.RedisTestSupport;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericToStringSerializer;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.redis.inbound.RedisQueueMessageDrivenEndpoint;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.support.GenericMessage;
/**
* Integration test of {@link RedisQueueInboundChannelAdapter}
*
* @author Jennifer Hickey
*/
public class RedisQueueInboundChannelAdapterTests {
private static final String QUEUE_NAME = "inboundadaptertest";
private RedisConnectionFactory connectionFactory;
private final BlockingDeque<Object> messages = new LinkedBlockingDeque<>(99);
private RedisQueueMessageDrivenEndpoint adapter;
private double timeoutMultiplier = 1.0D;
@Rule
public RedisTestSupport redisAvailableRule = new RedisTestSupport();
private String currentQueueName;
@Before
public void setUp() {
messages.clear();
this.connectionFactory = redisAvailableRule.getResource();
DirectChannel outputChannel = new DirectChannel();
outputChannel.setBeanFactory(BinderTestUtils.MOCK_BF);
outputChannel.subscribe(new TestMessageHandler());
this.currentQueueName = QUEUE_NAME + ":" + System.nanoTime();
adapter = new RedisQueueMessageDrivenEndpoint(currentQueueName, connectionFactory);
adapter.setBeanFactory(BinderTestUtils.MOCK_BF);
adapter.setOutputChannel(outputChannel);
String multiplier = System.getenv("REDIS_TIMEOUT_MULTIPLIER");
if (multiplier != null) {
timeoutMultiplier = Double.parseDouble(multiplier);
}
}
@After
public void tearDown() {
adapter.stop();
connectionFactory.getConnection().del(currentQueueName.getBytes());
}
@Test
public void testDefaultPayloadSerializer() throws Exception {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
adapter.afterPropertiesSet();
adapter.start();
template.boundListOps(currentQueueName).rightPush("message1");
@SuppressWarnings("unchecked")
Message<String> message = (Message<String>) messages.poll((int)(1000 * timeoutMultiplier), TimeUnit.MILLISECONDS);
assertNotNull(message);
assertEquals("message1", message.getPayload());
}
@Test
public void testDefaultMsgSerializer() throws Exception {
RedisTemplate<String, Message<String>> template = new RedisTemplate<String, Message<String>>();
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new JdkSerializationRedisSerializer());
template.setConnectionFactory(connectionFactory);
template.afterPropertiesSet();
adapter.setExpectMessage(true);
adapter.afterPropertiesSet();
adapter.start();
Map<String, Object> headers = new HashMap<>();
headers.put("header1", "foo");
template.boundListOps(currentQueueName).rightPush(new GenericMessage<>("message2", headers));
@SuppressWarnings("unchecked")
Message<String> message = (Message<String>) messages.poll((int)(1000 * timeoutMultiplier), TimeUnit.MILLISECONDS);
assertEquals("message2", message.getPayload());
assertEquals("foo", message.getHeaders().get("header1"));
}
@SuppressWarnings("unchecked")
@Test
public void testNoSerializer() throws Exception {
RedisTemplate<String, byte[]> template = new RedisTemplate<>();
template.setEnableDefaultSerializer(false);
template.setKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(connectionFactory);
template.afterPropertiesSet();
adapter.setSerializer(null);
adapter.afterPropertiesSet();
adapter.start();
template.boundListOps(currentQueueName).rightPush("message3".getBytes());
Message<byte[]> message = (Message<byte[]>) messages.poll((int)(1000 * timeoutMultiplier), TimeUnit.MILLISECONDS);
assertEquals("message3", new String(message.getPayload()));
}
@Test(expected = IllegalArgumentException.class)
public void testNoSerializerNoExtractPayload() throws Exception {
RedisTemplate<String, byte[]> template = new RedisTemplate<>();
template.setEnableDefaultSerializer(false);
template.setKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(connectionFactory);
template.afterPropertiesSet();
adapter.setSerializer(null);
adapter.setExpectMessage(true);
adapter.afterPropertiesSet();
adapter.start();
}
@Test
public void testCustomPayloadSerializer() throws Exception {
RedisTemplate<String, Long> template = new RedisTemplate<>();
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericToStringSerializer<>(Long.class));
template.setConnectionFactory(connectionFactory);
template.afterPropertiesSet();
adapter.setSerializer(new GenericToStringSerializer<Long>(Long.class));
adapter.afterPropertiesSet();
adapter.start();
template.boundListOps(currentQueueName).rightPush(5l);
@SuppressWarnings("unchecked")
Message<Long> message = (Message<Long>) messages.poll((int)(1000 * timeoutMultiplier), TimeUnit.MILLISECONDS);
assertEquals(5L, (long) message.getPayload());
}
@Test
public void testCustomMessageSerializer() throws Exception {
RedisTemplate<String, Message<?>> template = new RedisTemplate<>();
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new TestMessageSerializer());
template.setConnectionFactory(connectionFactory);
template.afterPropertiesSet();
adapter.setSerializer(new TestMessageSerializer());
adapter.setExpectMessage(true);
adapter.afterPropertiesSet();
adapter.start();
template.boundListOps(currentQueueName).rightPush(new GenericMessage<>(10l));
@SuppressWarnings("unchecked")
Message<Long> message = (Message<Long>) messages.poll((int)(1000 * timeoutMultiplier), TimeUnit.MILLISECONDS);
assertEquals(10L, (long) message.getPayload());
}
private class TestMessageHandler implements MessageHandler {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
messages.add(message);
}
}
private class TestMessageSerializer implements RedisSerializer<Message<?>> {
@Override
public byte[] serialize(Message<?> t) throws SerializationException {
return "Foo".getBytes();
}
@Override
public Message<?> deserialize(byte[] bytes) throws SerializationException {
return new GenericMessage<>(10l);
}
}
}

View File

@@ -1,172 +0,0 @@
/*
* Copyright 2002-2013 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.redis;
import static org.junit.Assert.assertEquals;
import java.util.HashMap;
import java.util.Map;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.cloud.stream.binder.BinderTestUtils;
import org.springframework.cloud.stream.test.junit.redis.RedisTestSupport;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.GenericToStringSerializer;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.integration.redis.outbound.RedisQueueOutboundChannelAdapter;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.GenericMessage;
/**
* Integration test of {@link RedisQueueOutboundChannelAdapter}
*
* @author Jennifer Hickey
* @author Gary Russell
*/
public class RedisQueueOutboundChannelAdapterTests {
private static final String QUEUE_NAME = "outboundadaptertest";
private RedisConnectionFactory connectionFactory;
private RedisQueueOutboundChannelAdapter adapter;
@Rule
public RedisTestSupport redisAvailableRule = new RedisTestSupport();
@Before
public void setUp() {
this.connectionFactory = redisAvailableRule.getResource();
adapter = new RedisQueueOutboundChannelAdapter(QUEUE_NAME, connectionFactory);
adapter.setBeanFactory(BinderTestUtils.MOCK_BF);
}
@After
public void tearDown() {
connectionFactory.getConnection().del(QUEUE_NAME.getBytes());
}
@Test
public void testDefaultPayloadSerializer() throws Exception {
StringRedisTemplate template = new StringRedisTemplate(connectionFactory);
template.afterPropertiesSet();
adapter.afterPropertiesSet();
adapter.handleMessage(new GenericMessage<String>("message1"));
assertEquals("message1", template.boundListOps(QUEUE_NAME).rightPop());
}
@Test
public void testDefaultMsgSerializer() throws Exception {
RedisTemplate<String, Message<?>> template = new RedisTemplate<String, Message<?>>();
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new JdkSerializationRedisSerializer());
template.setConnectionFactory(connectionFactory);
template.afterPropertiesSet();
adapter.setExtractPayload(false);
adapter.afterPropertiesSet();
Map<String, Object> headers = new HashMap<String, Object>();
headers.put("header1", "foo");
adapter.handleMessage(new GenericMessage<String>("message2", headers));
Message<?> message = template.boundListOps(QUEUE_NAME).rightPop();
assertEquals("message2", message.getPayload());
assertEquals("foo", message.getHeaders().get("header1"));
}
@Test
public void testNoSerializer() throws Exception {
RedisTemplate<String, byte[]> template = new RedisTemplate<String, byte[]>();
template.setEnableDefaultSerializer(false);
template.setKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(connectionFactory);
template.afterPropertiesSet();
adapter.afterPropertiesSet();
adapter.handleMessage(new GenericMessage<byte[]>("message3".getBytes()));
byte[] value = template.boundListOps(QUEUE_NAME).rightPop();
assertEquals("message3", new String(value));
}
@Test(expected = IllegalArgumentException.class)
public void testNoSerializerNoExtractPayload() throws Exception {
RedisTemplate<String, byte[]> template = new RedisTemplate<String, byte[]>();
template.setEnableDefaultSerializer(false);
template.setKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(connectionFactory);
template.afterPropertiesSet();
adapter.setSerializer(null);
adapter.setExtractPayload(false);
adapter.afterPropertiesSet();
}
@Test
public void testCustomPayloadSerializer() throws Exception {
RedisTemplate<String, Long> template = new RedisTemplate<String, Long>();
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericToStringSerializer<Long>(Long.class));
template.setConnectionFactory(connectionFactory);
template.afterPropertiesSet();
adapter.setSerializer(new GenericToStringSerializer<Long>(Long.class));
adapter.afterPropertiesSet();
adapter.handleMessage(new GenericMessage<Long>(5l));
assertEquals(Long.valueOf(5), template.boundListOps(QUEUE_NAME).rightPop());
}
@Test
public void testCustomMessageSerializer() throws Exception {
RedisTemplate<String, Message<?>> template = new RedisTemplate<String, Message<?>>();
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new TestMessageSerializer());
template.setConnectionFactory(connectionFactory);
template.afterPropertiesSet();
adapter.setSerializer(new TestMessageSerializer());
adapter.setExtractPayload(false);
adapter.afterPropertiesSet();
Message<?> message = template.boundListOps(QUEUE_NAME).rightPop();
assertEquals(10l, message.getPayload());
}
private class TestMessageSerializer implements RedisSerializer<Message<?>> {
@Override
public byte[] serialize(Message<?> t) throws SerializationException {
return "Foo".getBytes();
}
@Override
public Message<?> deserialize(byte[] bytes) throws SerializationException {
return new GenericMessage<Long>(10l);
}
}
}

View File

@@ -1,71 +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.redis;
import org.springframework.cloud.stream.binder.AbstractTestBinder;
import org.springframework.cloud.stream.binder.ConsumerProperties;
import org.springframework.cloud.stream.binder.ProducerProperties;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.integration.channel.DefaultHeaderChannelRegistry;
import org.springframework.integration.codec.kryo.PojoCodec;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.support.DefaultMessageBuilderFactory;
import org.springframework.integration.support.utils.IntegrationUtils;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
/**
* Test support class for {@link RedisMessageChannelBinder}.
*
* @author Ilayaperumal Gopinathan
* @author Gary Russell
* @author David Turanski
*/
public class RedisTestBinder extends AbstractTestBinder<RedisMessageChannelBinder, ConsumerProperties, ProducerProperties> {
private StringRedisTemplate template;
public RedisTestBinder(RedisConnectionFactory connectionFactory) {
RedisMessageChannelBinder binder = new RedisMessageChannelBinder(connectionFactory);
GenericApplicationContext context = new GenericApplicationContext();
context.getBeanFactory().registerSingleton(IntegrationUtils.INTEGRATION_MESSAGE_BUILDER_FACTORY_BEAN_NAME,
new DefaultMessageBuilderFactory());
DefaultHeaderChannelRegistry channelRegistry = new DefaultHeaderChannelRegistry();
channelRegistry.setReaperDelay(Long.MAX_VALUE);
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.afterPropertiesSet();
channelRegistry.setTaskScheduler(taskScheduler);
context.getBeanFactory().registerSingleton(
IntegrationContextUtils.INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME,
channelRegistry);
context.refresh();
binder.setApplicationContext(context);
binder.setCodec(new PojoCodec());
setBinder(binder);
template = new StringRedisTemplate(connectionFactory);
}
@Override
public void cleanup() {
if (!queues.isEmpty()) {
for (String queue : queues) {
template.delete(queue);
}
}
}
}

View File

@@ -1,159 +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.redis.integration;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.collection.IsMapContaining.hasKey;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
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.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.redis.RedisMessageChannelBinder;
import org.springframework.cloud.stream.messaging.Processor;
import org.springframework.cloud.stream.test.junit.redis.RedisTestSupport;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
/**
* @author Marius Bogoevici
*/
public class RedisBinderModuleTests {
@ClassRule
public static RedisTestSupport redisTestSupport = new RedisTestSupport();
private ConfigurableApplicationContext context = null;
public static final RedisConnectionFactory MOCK_CONNECTION_FACTORY = Mockito.mock(RedisConnectionFactory.class,
Mockito.RETURNS_MOCKS);
@After
public void tearDown() {
if (context != null) {
context.close();
context = null;
}
}
@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, instanceOf(RedisMessageChannelBinder.class));
DirectFieldAccessor binderFieldAccessor = new DirectFieldAccessor(binder);
RedisConnectionFactory binderConnectionFactory =
(RedisConnectionFactory) binderFieldAccessor.getPropertyValue("connectionFactory");
assertThat(binderConnectionFactory, instanceOf(RedisConnectionFactory.class));
RedisConnectionFactory connectionFactory = context.getBean(RedisConnectionFactory.class);
assertThat(binderConnectionFactory, is(connectionFactory));
CompositeHealthIndicator bindersHealthIndicator =
context.getBean("bindersHealthIndicator", CompositeHealthIndicator.class);
assertNotNull(bindersHealthIndicator);
DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(bindersHealthIndicator);
@SuppressWarnings("unchecked")
Map<String,HealthIndicator> healthIndicators =
(Map<String, HealthIndicator>) directFieldAccessor.getPropertyValue("indicators");
assertThat(healthIndicators, hasKey("redis"));
assertThat(healthIndicators.get("redis").health().getStatus(), equalTo(Status.UP));
}
@Test
public void testParentConnectionFactoryInheritedIfOverridden() {
context = new SpringApplication(SimpleProcessor.class, ConnectionFactoryConfiguration.class).run();
BinderFactory<?> binderFactory = context.getBean(BinderFactory.class);
Binder binder = binderFactory.getBinder(null);
assertThat(binder, instanceOf(RedisMessageChannelBinder.class));
DirectFieldAccessor binderFieldAccessor = new DirectFieldAccessor(binder);
RedisConnectionFactory binderConnectionFactory =
(RedisConnectionFactory) binderFieldAccessor.getPropertyValue("connectionFactory");
assertThat(binderConnectionFactory, is(MOCK_CONNECTION_FACTORY));
RedisConnectionFactory connectionFactory = context.getBean(RedisConnectionFactory.class);
assertThat(binderConnectionFactory, is(connectionFactory));
CompositeHealthIndicator bindersHealthIndicator =
context.getBean("bindersHealthIndicator", CompositeHealthIndicator.class);
assertNotNull(bindersHealthIndicator);
DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(bindersHealthIndicator);
@SuppressWarnings("unchecked")
Map<String,HealthIndicator> healthIndicators =
(Map<String, HealthIndicator>) directFieldAccessor.getPropertyValue("indicators");
assertThat(healthIndicators, hasKey("redis"));
assertThat(healthIndicators.get("redis").health().getStatus(), equalTo(Status.UP));
}
@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=redis");
params.add("--spring.cloud.stream.binders.custom.environment.foo=bar");
context = SpringApplication.run(SimpleProcessor.class, params.toArray(new String[]{}));
BinderFactory<?> binderFactory = context.getBean(BinderFactory.class);
Binder binder = binderFactory.getBinder(null);
assertThat(binder, instanceOf(RedisMessageChannelBinder.class));
DirectFieldAccessor binderFieldAccessor = new DirectFieldAccessor(binder);
RedisConnectionFactory binderConnectionFactory =
(RedisConnectionFactory) binderFieldAccessor.getPropertyValue("connectionFactory");
RedisConnectionFactory connectionFactory = context.getBean(RedisConnectionFactory.class);
assertThat(binderConnectionFactory, not(is(connectionFactory)));
CompositeHealthIndicator bindersHealthIndicator =
context.getBean("bindersHealthIndicator", CompositeHealthIndicator.class);
assertNotNull(bindersHealthIndicator);
DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(bindersHealthIndicator);
@SuppressWarnings("unchecked")
Map<String,HealthIndicator> healthIndicators =
(Map<String, HealthIndicator>) directFieldAccessor.getPropertyValue("indicators");
assertThat(healthIndicators, hasKey("custom"));
assertThat(healthIndicators.get("custom").health().getStatus(), equalTo(Status.UP));
}
@EnableBinding(Processor.class)
@SpringBootApplication
public static class SimpleProcessor {
}
public static class ConnectionFactoryConfiguration {
@Bean
public RedisConnectionFactory connectionFactory() {
return MOCK_CONNECTION_FACTORY;
}
}
}

View File

@@ -14,9 +14,8 @@
<version>1.0.0.BUILD-SNAPSHOT</version>
</parent>
<modules>
<module>spring-cloud-starter-stream-rabbit</module>
<module>spring-cloud-starter-stream-redis</module>
<module>spring-cloud-starter-stream-kafka</module>
<module>spring-cloud-starter-stream-rabbit</module>
</modules>
<properties>
<spring-cloud-commons.version>1.1.0.RC1</spring-cloud-commons.version>

View File

@@ -1,26 +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>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-starters</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>spring-cloud-starter-stream-redis</artifactId>
<description>Spring Cloud Starter Stream Redis</description>
<url>http://projects.spring.io/spring-cloud</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-redis</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1 +0,0 @@
provides: spring-cloud-stream-binder-redis