/*
* 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.integration.redis.channel;
import java.util.concurrent.Executor;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.SmartLifecycle;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
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.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.integration.MessageDispatchingException;
import org.springframework.integration.channel.AbstractMessageChannel;
import org.springframework.integration.channel.MessagePublishingErrorHandler;
import org.springframework.integration.context.IntegrationProperties;
import org.springframework.integration.dispatcher.BroadcastingDispatcher;
import org.springframework.integration.support.channel.BeanFactoryChannelResolver;
import org.springframework.integration.support.converter.SimpleMessageConverter;
import org.springframework.integration.util.ErrorHandlingTaskExecutor;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageDeliveryException;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.ErrorHandler;
import org.springframework.util.StringUtils;
/**
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
* @since 2.0
*/
@SuppressWarnings("rawtypes")
public class SubscribableRedisChannel extends AbstractMessageChannel
implements SubscribableChannel, SmartLifecycle, DisposableBean {
private final RedisMessageListenerContainer container = new RedisMessageListenerContainer();
private final RedisConnectionFactory connectionFactory;
private final RedisTemplate redisTemplate;
private final String topicName;
private final BroadcastingDispatcher dispatcher = new BroadcastingDispatcher(true);
private volatile Integer maxSubscribers;
private volatile boolean initialized;
// defaults
private volatile Executor taskExecutor = new SimpleAsyncTaskExecutor();
private volatile RedisSerializer<?> serializer = new StringRedisSerializer();
private volatile MessageConverter messageConverter = new SimpleMessageConverter();
public SubscribableRedisChannel(RedisConnectionFactory connectionFactory, String topicName) {
Assert.notNull(connectionFactory, "'connectionFactory' must not be null");
Assert.hasText(topicName, "'topicName' must not be empty");
this.connectionFactory = connectionFactory;
this.redisTemplate = new StringRedisTemplate(connectionFactory);
this.topicName = topicName;
}
public void setTaskExecutor(Executor taskExecutor) {
Assert.notNull(taskExecutor, "'taskExecutor' must not be null");
this.taskExecutor = taskExecutor;
}
@Override
public void setMessageConverter(MessageConverter messageConverter) {
Assert.notNull(messageConverter, "'messageConverter' must not be null");
this.messageConverter = messageConverter;
}
public void setSerializer(RedisSerializer<?> serializer) {
Assert.notNull(serializer, "'serializer' must not be null");
this.serializer = serializer;
}
/**
* Specify the maximum number of subscribers supported by the
* channel's dispatcher.
*
* @param maxSubscribers The maximum number of subscribers allowed.
*/
public void setMaxSubscribers(int maxSubscribers) {
this.maxSubscribers = maxSubscribers;
this.dispatcher.setMaxSubscribers(maxSubscribers);
}
@Override
public boolean subscribe(MessageHandler handler) {
return this.dispatcher.addHandler(handler);
}
@Override
public boolean unsubscribe(MessageHandler handler) {
return this.dispatcher.removeHandler(handler);
}
@Override
protected boolean doSend(Message<?> message, long arg1) {
this.redisTemplate.convertAndSend(this.topicName, this.messageConverter.fromMessage(message, Object.class));
return true;
}
@Override
public void onInit() throws Exception {
if (this.initialized) {
return;
}
super.onInit();
if (this.maxSubscribers == null) {
Integer maxSubscribers =
getIntegrationProperty(IntegrationProperties.CHANNELS_MAX_BROADCAST_SUBSCRIBERS, Integer.class);
this.setMaxSubscribers(maxSubscribers);
}
if (this.messageConverter == null) {
this.messageConverter = new SimpleMessageConverter();
}
if (this.messageConverter instanceof BeanFactoryAware) {
((BeanFactoryAware) this.messageConverter).setBeanFactory(this.getBeanFactory());
}
this.container.setConnectionFactory(this.connectionFactory);
if (!(this.taskExecutor instanceof ErrorHandlingTaskExecutor)) {
ErrorHandler errorHandler = new MessagePublishingErrorHandler(
new BeanFactoryChannelResolver(this.getBeanFactory()));
this.taskExecutor = new ErrorHandlingTaskExecutor(this.taskExecutor, errorHandler);
}
this.container.setTaskExecutor(this.taskExecutor);
MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageListenerDelegate());
adapter.setSerializer(this.serializer);
adapter.afterPropertiesSet();
this.container.addMessageListener(adapter, new ChannelTopic(this.topicName));
this.container.afterPropertiesSet();
this.dispatcher.setBeanFactory(this.getBeanFactory());
this.initialized = true;
}
/*
* SmartLifecycle implementation (delegates to the MessageListener container)
*/
@Override
public boolean isAutoStartup() {
return (this.container != null) && this.container.isAutoStartup();
}
@Override
public int getPhase() {
return (this.container != null) ? this.container.getPhase() : 0;
}
@Override
public boolean isRunning() {
return (this.container != null) && this.container.isRunning();
}
@Override
public void start() {
if (this.container != null) {
this.container.start();
}
}
@Override
public void stop() {
if (this.container != null) {
this.container.stop();
}
}
@Override
public void stop(Runnable callback) {
if (this.container != null) {
this.container.stop(callback);
}
}
@Override
public void destroy() throws Exception {
if (this.container != null) {
this.container.destroy();
}
}
private class MessageListenerDelegate {
MessageListenerDelegate() {
super();
}
@SuppressWarnings({ "unused" })
public void handleMessage(Object payload) {
Message<?> siMessage = SubscribableRedisChannel.this.messageConverter.toMessage(payload, null);
try {
SubscribableRedisChannel.this.dispatcher.dispatch(siMessage);
}
catch (MessageDispatchingException e) {
String topicName = SubscribableRedisChannel.this.topicName;
topicName = StringUtils.hasText(topicName) ? topicName : "unknown";
throw new MessageDeliveryException(siMessage, e.getMessage()
+ " for redis-channel '"
+ topicName
+ "' (" + SubscribableRedisChannel.this.getFullChannelName() + ").", e);
}
}
}
}