/*
* Copyright 2002-2017 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.amqp.config;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executor;
import org.aopalliance.aop.Advice;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.support.MessagePropertiesConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.context.Lifecycle;
import org.springframework.context.SmartLifecycle;
import org.springframework.integration.amqp.channel.AbstractAmqpChannel;
import org.springframework.integration.amqp.channel.PointToPointSubscribableAmqpChannel;
import org.springframework.integration.amqp.channel.PollableAmqpChannel;
import org.springframework.integration.amqp.channel.PublishSubscribeAmqpChannel;
import org.springframework.integration.amqp.support.AmqpHeaderMapper;
import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ErrorHandler;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* If point-to-point, we send to the default exchange with the routing key
* equal to "[beanName]" and we declare that same Queue and register a listener
* if message-driven or poll explicitly otherwise. If publish-subscribe, we declare
* a FanoutExchange named "si.fanout.[beanName]" and we send to that without any
* routing key, and on the receiving side, we create an anonymous Queue that is
* bound to that exchange.
*
* @author Mark Fisher
* @author Gary Russell
* @author Artem Bilan
* @since 2.1
*/
public class AmqpChannelFactoryBean extends AbstractFactoryBean<AbstractAmqpChannel> implements SmartLifecycle,
DisposableBean, BeanNameAware {
private volatile AbstractAmqpChannel channel;
private volatile List<ChannelInterceptor> interceptors;
private final boolean messageDriven;
private final AmqpTemplate amqpTemplate = new RabbitTemplate();
private volatile AmqpAdmin amqpAdmin;
private volatile FanoutExchange exchange;
private volatile String queueName;
private volatile boolean autoStartup = true;
private volatile Advice[] adviceChain;
private volatile Integer concurrentConsumers;
private volatile Integer consumersPerQueue;
private volatile ConnectionFactory connectionFactory;
private volatile MessagePropertiesConverter messagePropertiesConverter;
private volatile ErrorHandler errorHandler;
private volatile Boolean exposeListenerChannel;
private volatile Integer phase;
private volatile Integer prefetchCount;
private volatile boolean isPubSub;
private volatile Long receiveTimeout;
private volatile Long recoveryInterval;
private volatile Long shutdownTimeout;
private volatile String beanName;
private volatile AcknowledgeMode acknowledgeMode;
private volatile boolean channelTransacted;
private volatile Executor taskExecutor;
private volatile PlatformTransactionManager transactionManager;
private volatile TransactionAttribute transactionAttribute;
private volatile Integer txSize;
private volatile Integer maxSubscribers;
private volatile Boolean missingQueuesFatal;
private volatile MessageDeliveryMode defaultDeliveryMode;
private volatile Boolean extractPayload;
private volatile AmqpHeaderMapper outboundHeaderMapper = DefaultAmqpHeaderMapper.outboundMapper();
private volatile AmqpHeaderMapper inboundHeaderMapper = DefaultAmqpHeaderMapper.inboundMapper();
private boolean headersLast;
public AmqpChannelFactoryBean() {
this(true);
}
public AmqpChannelFactoryBean(boolean messageDriven) {
this.messageDriven = messageDriven;
}
@Override
public void setBeanName(String name) {
this.beanName = name;
}
public void setInterceptors(List<ChannelInterceptor> interceptors) {
this.interceptors = interceptors;
}
/**
* This is an optional reference to an AmqpAdmin to use when
* declaring a Queue implicitly for a PollableAmqpChannel. It
* is not needed for the message-driven (Subscribable) channels
* since those are able to create a RabbitAdmin instance using
* the underlying listener container's ConnectionFactory.
*
* @param amqpAdmin The amqp admin.
*/
public void setAmqpAdmin(AmqpAdmin amqpAdmin) {
this.amqpAdmin = amqpAdmin;
}
/**
* Set the FanoutExchange to use. This is only relevant for
* publish-subscribe-channels, and even then if not provided,
* a FanoutExchange will be implicitly created.
*
* @param exchange The fanout exchange.
*/
public void setExchange(FanoutExchange exchange) {
this.exchange = exchange;
}
/**
* Set the Queue name to use. This is only relevant for
* point-to-point channels, even then if not provided,
* a Queue will be implicitly created.
*
* @param queueName The queue name.
*/
public void setQueueName(String queueName) {
this.queueName = queueName;
}
/*
* Template-only properties
*/
public void setEncoding(String encoding) {
if (this.amqpTemplate instanceof RabbitTemplate) {
((RabbitTemplate) this.amqpTemplate).setEncoding(encoding);
}
else if (logger.isInfoEnabled()) {
logger.info("AmqpTemplate is not a RabbitTemplate, so configured 'encoding' value will be ignored.");
}
}
public void setMessageConverter(MessageConverter messageConverter) {
if (this.amqpTemplate instanceof RabbitTemplate) {
((RabbitTemplate) this.amqpTemplate).setMessageConverter(messageConverter);
}
else if (logger.isInfoEnabled()) {
logger.info("AmqpTemplate is not a RabbitTemplate, so configured MessageConverter will be ignored.");
}
}
public void setTemplateChannelTransacted(boolean channelTransacted) {
if (this.amqpTemplate instanceof RabbitTemplate) {
((RabbitTemplate) this.amqpTemplate).setChannelTransacted(channelTransacted);
}
else if (logger.isInfoEnabled()) {
logger.info("AmqpTemplate is not a RabbitTemplate, so configured 'channelTransacted' will be ignored.");
}
}
/*
* Template and Container properties
*/
public void setChannelTransacted(boolean channelTransacted) {
this.channelTransacted = channelTransacted;
}
public void setConnectionFactory(ConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
if (this.amqpTemplate instanceof RabbitTemplate) {
((RabbitTemplate) this.amqpTemplate).setConnectionFactory(this.connectionFactory);
}
}
public void setMessagePropertiesConverter(MessagePropertiesConverter messagePropertiesConverter) {
this.messagePropertiesConverter = messagePropertiesConverter;
if (this.amqpTemplate instanceof RabbitTemplate) {
((RabbitTemplate) this.amqpTemplate).setMessagePropertiesConverter(messagePropertiesConverter);
}
}
/*
* Container-only properties
*/
public void setAcknowledgeMode(AcknowledgeMode acknowledgeMode) {
this.acknowledgeMode = acknowledgeMode;
}
public void setAdviceChain(Advice[] adviceChain) {
this.adviceChain = Arrays.copyOf(adviceChain, adviceChain.length);
}
public void setAutoStartup(boolean autoStartup) {
this.autoStartup = autoStartup;
}
public void setConcurrentConsumers(int concurrentConsumers) {
this.concurrentConsumers = concurrentConsumers;
}
public void setConsumersPerQueue(Integer consumersPerQueue) {
this.consumersPerQueue = consumersPerQueue;
}
public void setErrorHandler(ErrorHandler errorHandler) {
this.errorHandler = errorHandler;
}
public void setExposeListenerChannel(boolean exposeListenerChannel) {
this.exposeListenerChannel = exposeListenerChannel;
}
public void setPhase(int phase) {
this.phase = phase;
}
public void setPrefetchCount(int prefetchCount) {
this.prefetchCount = prefetchCount;
}
public void setPubSub(boolean pubSub) {
this.isPubSub = pubSub;
}
public void setReceiveTimeout(long receiveTimeout) {
this.receiveTimeout = receiveTimeout;
}
public void setRecoveryInterval(long recoveryInterval) {
this.recoveryInterval = recoveryInterval;
}
public void setShutdownTimeout(long shutdownTimeout) {
this.shutdownTimeout = shutdownTimeout;
}
public void setTaskExecutor(Executor taskExecutor) {
this.taskExecutor = taskExecutor;
}
public void setTransactionAttribute(TransactionAttribute transactionAttribute) {
this.transactionAttribute = transactionAttribute;
}
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public void setTxSize(int txSize) {
this.txSize = txSize;
}
public void setMaxSubscribers(int maxSubscribers) {
this.maxSubscribers = maxSubscribers;
}
public void setMissingQueuesFatal(Boolean missingQueuesFatal) {
this.missingQueuesFatal = missingQueuesFatal;
}
public void setDefaultDeliveryMode(MessageDeliveryMode defaultDeliveryMode) {
this.defaultDeliveryMode = defaultDeliveryMode;
}
public void setExtractPayload(Boolean extractPayload) {
this.extractPayload = extractPayload;
}
public void setOutboundHeaderMapper(AmqpHeaderMapper outboundMapper) {
this.outboundHeaderMapper = outboundMapper;
}
public void setInboundHeaderMapper(AmqpHeaderMapper inboundMapper) {
this.inboundHeaderMapper = inboundMapper;
}
public void setHeadersLast(boolean headersLast) {
this.headersLast = headersLast;
}
@Override
public Class<?> getObjectType() {
return (this.channel != null) ? this.channel.getClass() : AbstractAmqpChannel.class;
}
@Override
protected AbstractAmqpChannel createInstance() throws Exception {
if (this.messageDriven) {
AbstractMessageListenerContainer container = this.createContainer();
if (this.amqpTemplate instanceof InitializingBean) {
((InitializingBean) this.amqpTemplate).afterPropertiesSet();
}
if (this.isPubSub) {
PublishSubscribeAmqpChannel pubsub = new PublishSubscribeAmqpChannel(
this.beanName, container, this.amqpTemplate, this.outboundHeaderMapper, this.inboundHeaderMapper);
if (this.exchange != null) {
pubsub.setExchange(this.exchange);
}
if (this.maxSubscribers != null) {
pubsub.setMaxSubscribers(this.maxSubscribers);
}
this.channel = pubsub;
}
else {
PointToPointSubscribableAmqpChannel p2p = new PointToPointSubscribableAmqpChannel(
this.beanName, container, this.amqpTemplate, this.outboundHeaderMapper, this.inboundHeaderMapper);
if (StringUtils.hasText(this.queueName)) {
p2p.setQueueName(this.queueName);
}
if (this.maxSubscribers != null) {
p2p.setMaxSubscribers(this.maxSubscribers);
}
this.channel = p2p;
}
}
else {
Assert.isTrue(!this.isPubSub, "An AMQP 'publish-subscribe-channel' must be message-driven.");
PollableAmqpChannel pollable = new PollableAmqpChannel(this.beanName, this.amqpTemplate,
this.outboundHeaderMapper, this.inboundHeaderMapper);
if (this.amqpAdmin != null) {
pollable.setAmqpAdmin(this.amqpAdmin);
}
if (StringUtils.hasText(this.queueName)) {
pollable.setQueueName(this.queueName);
}
this.channel = pollable;
}
if (!CollectionUtils.isEmpty(this.interceptors)) {
this.channel.setInterceptors(this.interceptors);
}
this.channel.setBeanName(this.beanName);
if (this.getBeanFactory() != null) {
this.channel.setBeanFactory(this.getBeanFactory());
}
if (this.defaultDeliveryMode != null) {
this.channel.setDefaultDeliveryMode(this.defaultDeliveryMode);
}
if (this.extractPayload != null) {
this.channel.setExtractPayload(this.extractPayload);
}
this.channel.setHeadersMappedLast(this.headersLast);
this.channel.afterPropertiesSet();
return this.channel;
}
private AbstractMessageListenerContainer createContainer() throws Exception {
AbstractMessageListenerContainer container;
if (this.consumersPerQueue == null) {
SimpleMessageListenerContainer smlc = new SimpleMessageListenerContainer();
if (this.concurrentConsumers != null) {
smlc.setConcurrentConsumers(this.concurrentConsumers);
}
if (this.receiveTimeout != null) {
smlc.setReceiveTimeout(this.receiveTimeout);
}
if (this.txSize != null) {
smlc.setTxSize(this.txSize);
}
container = smlc;
}
else {
DirectMessageListenerContainer dmlc = new DirectMessageListenerContainer();
dmlc.setConsumersPerQueue(this.consumersPerQueue);
container = dmlc;
}
if (this.acknowledgeMode != null) {
container.setAcknowledgeMode(this.acknowledgeMode);
}
if (!ObjectUtils.isEmpty(this.adviceChain)) {
container.setAdviceChain(this.adviceChain);
}
container.setAutoStartup(this.autoStartup);
container.setChannelTransacted(this.channelTransacted);
container.setConnectionFactory(this.connectionFactory);
if (this.errorHandler != null) {
container.setErrorHandler(this.errorHandler);
}
if (this.exposeListenerChannel != null) {
container.setExposeListenerChannel(this.exposeListenerChannel);
}
if (this.messagePropertiesConverter != null) {
container.setMessagePropertiesConverter(this.messagePropertiesConverter);
}
if (this.phase != null) {
container.setPhase(this.phase);
}
if (this.prefetchCount != null) {
container.setPrefetchCount(this.prefetchCount);
}
if (this.recoveryInterval != null) {
container.setRecoveryInterval(this.recoveryInterval);
}
if (this.shutdownTimeout != null) {
container.setShutdownTimeout(this.shutdownTimeout);
}
if (this.taskExecutor != null) {
container.setTaskExecutor(this.taskExecutor);
}
if (this.transactionAttribute != null) {
container.setTransactionAttribute(this.transactionAttribute);
}
if (this.transactionManager != null) {
container.setTransactionManager(this.transactionManager);
}
if (this.missingQueuesFatal != null) {
container.setMissingQueuesFatal(this.missingQueuesFatal);
}
return container;
}
/*
* SmartLifecycle implementation (delegates to the created channel if message-driven)
*/
@Override
public boolean isAutoStartup() {
return (this.channel instanceof SmartLifecycle) && ((SmartLifecycle) this.channel).isAutoStartup();
}
@Override
public int getPhase() {
return (this.channel instanceof SmartLifecycle) ?
((SmartLifecycle) this.channel).getPhase() : 0;
}
@Override
public boolean isRunning() {
return (this.channel instanceof Lifecycle) && ((Lifecycle) this.channel).isRunning();
}
@Override
public void start() {
if (this.channel instanceof Lifecycle) {
((Lifecycle) this.channel).start();
}
}
@Override
public void stop() {
if (this.channel instanceof Lifecycle) {
((Lifecycle) this.channel).stop();
}
}
@Override
public void stop(Runnable callback) {
if (this.channel instanceof SmartLifecycle) {
((SmartLifecycle) this.channel).stop(callback);
}
}
@Override
protected void destroyInstance(AbstractAmqpChannel instance) throws Exception {
if (instance instanceof DisposableBean) {
((DisposableBean) this.channel).destroy();
}
}
}