/*
* 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.jms.config;
import java.util.List;
import java.util.concurrent.Executor;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.ExceptionListener;
import javax.jms.Session;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.context.SmartLifecycle;
import org.springframework.integration.jms.AbstractJmsChannel;
import org.springframework.integration.jms.DynamicJmsTemplate;
import org.springframework.integration.jms.PollableJmsChannel;
import org.springframework.integration.jms.SubscribableJmsChannel;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.listener.AbstractMessageListenerContainer;
import org.springframework.jms.listener.DefaultMessageListenerContainer;
import org.springframework.jms.listener.SimpleMessageListenerContainer;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.jms.support.destination.DestinationResolver;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ErrorHandler;
import org.springframework.util.StringUtils;
/**
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
* @since 2.0
*/
public class JmsChannelFactoryBean extends AbstractFactoryBean<AbstractJmsChannel>
implements SmartLifecycle, DisposableBean, BeanNameAware {
private volatile AbstractJmsChannel channel;
private volatile List<ChannelInterceptor> interceptors;
private final boolean messageDriven;
private final JmsTemplate jmsTemplate = new DynamicJmsTemplate();
private volatile AbstractMessageListenerContainer container;
private volatile Class<? extends AbstractMessageListenerContainer> containerType;
private volatile boolean acceptMessagesWhileStopping;
private volatile boolean autoStartup = true;
private volatile String cacheLevelName;
private volatile Integer cacheLevel;
private volatile String clientId;
private volatile String concurrency;
private volatile Integer concurrentConsumers;
private volatile ConnectionFactory connectionFactory;
private volatile Destination destination;
private volatile String destinationName;
private volatile DestinationResolver destinationResolver;
private volatile String durableSubscriptionName;
private volatile ErrorHandler errorHandler;
private volatile ExceptionListener exceptionListener;
private volatile Boolean exposeListenerSession;
private volatile Integer idleTaskExecutionLimit;
private volatile Integer maxConcurrentConsumers;
private volatile Integer maxMessagesPerTask;
private volatile String messageSelector;
private volatile Integer phase;
private volatile Boolean pubSubDomain;
private volatile boolean pubSubNoLocal;
private volatile Long receiveTimeout;
private volatile Long recoveryInterval;
private volatile String beanName;
private volatile boolean subscriptionShared;
/**
* This value differs from the container implementations' default (which is AUTO_ACKNOWLEDGE)
*/
private volatile int sessionAcknowledgeMode = Session.SESSION_TRANSACTED;
/**
* This value differs from the container implementations' default (which is false).
*/
private volatile boolean sessionTransacted = true;
private volatile boolean subscriptionDurable;
private volatile Executor taskExecutor;
private volatile PlatformTransactionManager transactionManager;
private volatile String transactionName;
private volatile Integer transactionTimeout;
private volatile int maxSubscribers = Integer.MAX_VALUE;
public JmsChannelFactoryBean() {
this(true);
}
public JmsChannelFactoryBean(boolean messageDriven) {
this.messageDriven = messageDriven;
}
public void setInterceptors(List<ChannelInterceptor> interceptors) {
this.interceptors = interceptors;
}
/*
* Template properties
*/
public void setDeliveryPersistent(boolean deliveryPersistent) {
this.jmsTemplate.setDeliveryPersistent(deliveryPersistent);
}
public void setExplicitQosEnabled(boolean explicitQosEnabled) {
this.jmsTemplate.setExplicitQosEnabled(explicitQosEnabled);
}
public void setMessageConverter(MessageConverter messageConverter) {
this.jmsTemplate.setMessageConverter(messageConverter);
}
public void setMessageIdEnabled(boolean messageIdEnabled) {
this.jmsTemplate.setMessageIdEnabled(messageIdEnabled);
}
public void setMessageTimestampEnabled(boolean messageTimestampEnabled) {
this.jmsTemplate.setMessageTimestampEnabled(messageTimestampEnabled);
}
public void setPriority(int priority) {
this.jmsTemplate.setPriority(priority);
}
public void setTimeToLive(long timeToLive) {
this.jmsTemplate.setTimeToLive(timeToLive);
}
/*
* Container properties
*/
public void setAcceptMessagesWhileStopping(boolean acceptMessagesWhileStopping) {
Assert.isTrue(this.messageDriven,
"'acceptMessagesWhileStopping' is allowed only in case of 'messageDriven = true'");
this.acceptMessagesWhileStopping = acceptMessagesWhileStopping;
}
public void setAutoStartup(boolean autoStartup) {
this.autoStartup = autoStartup;
}
public void setCacheLevelName(String cacheLevelName) {
Assert.isTrue(this.messageDriven, "'cacheLevelName' is allowed only in case of 'messageDriven = true'");
Assert.state(this.cacheLevel == null,
"'cacheLevelName' and 'cacheLevel' are mutually exclusive");
this.cacheLevelName = cacheLevelName;
}
public void setCacheLevel(Integer cacheLevel) {
Assert.isTrue(this.messageDriven, "'cacheLevel' is allowed only in case of 'messageDriven = true'");
Assert.state(!StringUtils.hasText(this.cacheLevelName),
"'cacheLevelName' and 'cacheLevel' are mutually exclusive");
this.cacheLevel = cacheLevel;
}
public void setClientId(String clientId) {
Assert.isTrue(this.messageDriven, "'clientId' is allowed only in case of 'messageDriven = true'");
this.clientId = clientId;
}
public void setConcurrency(String concurrency) {
Assert.isTrue(this.messageDriven, "'concurrency' is allowed only in case of 'messageDriven = true'");
this.concurrency = concurrency;
}
public void setConcurrentConsumers(int concurrentConsumers) {
Assert.isTrue(this.messageDriven, "'concurrentConsumers' is allowed only in case of 'messageDriven = true'");
this.concurrentConsumers = concurrentConsumers;
}
public void setConnectionFactory(ConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
this.jmsTemplate.setConnectionFactory(this.connectionFactory);
}
public void setContainerType(Class<? extends AbstractMessageListenerContainer> containerType) {
Assert.isTrue(this.messageDriven, "'containerType' is allowed only in case of 'messageDriven = true'");
this.containerType = containerType;
}
public void setDestination(Destination destination) {
this.destination = destination;
}
public void setDestinationName(String destinationName) {
this.destinationName = destinationName;
}
public void setDestinationResolver(DestinationResolver destinationResolver) {
this.destinationResolver = destinationResolver;
this.jmsTemplate.setDestinationResolver(destinationResolver);
}
public void setDurableSubscriptionName(String durableSubscriptionName) {
Assert.isTrue(this.messageDriven, "'durableSubscriptionName' is allowed only in case of 'messageDriven = true'");
this.durableSubscriptionName = durableSubscriptionName;
}
public void setErrorHandler(ErrorHandler errorHandler) {
Assert.isTrue(this.messageDriven, "'errorHandler' is allowed only in case of 'messageDriven = true'");
this.errorHandler = errorHandler;
}
public void setExceptionListener(ExceptionListener exceptionListener) {
Assert.isTrue(this.messageDriven, "'exceptionListener' is allowed only in case of 'messageDriven = true'");
this.exceptionListener = exceptionListener;
}
public void setExposeListenerSession(boolean exposeListenerSession) {
Assert.isTrue(this.messageDriven, "'exposeListenerSession' is allowed only in case of 'messageDriven = true'");
this.exposeListenerSession = exposeListenerSession;
}
public void setIdleTaskExecutionLimit(int idleTaskExecutionLimit) {
Assert.isTrue(this.messageDriven, "'idleTaskExecutionLimit' is allowed only in case of 'messageDriven = true'");
this.idleTaskExecutionLimit = idleTaskExecutionLimit;
}
public void setMaxConcurrentConsumers(int maxConcurrentConsumers) {
Assert.isTrue(this.messageDriven, "'maxConcurrentConsumers' is allowed only in case of 'messageDriven = true'");
this.maxConcurrentConsumers = maxConcurrentConsumers;
}
public void setMaxMessagesPerTask(int maxMessagesPerTask) {
Assert.isTrue(this.messageDriven, "'maxMessagesPerTask' is allowed only in case of 'messageDriven = true'");
this.maxMessagesPerTask = maxMessagesPerTask;
}
public void setMessageSelector(String messageSelector) {
this.messageSelector = messageSelector;
}
public void setPhase(int phase) {
this.phase = phase;
}
public void setPubSubDomain(boolean pubSubDomain) {
this.pubSubDomain = pubSubDomain;
this.jmsTemplate.setPubSubDomain(pubSubDomain);
}
public void setPubSubNoLocal(boolean pubSubNoLocal) {
this.pubSubNoLocal = pubSubNoLocal;
this.jmsTemplate.setPubSubNoLocal(pubSubNoLocal);
}
public void setReceiveTimeout(long receiveTimeout) {
this.receiveTimeout = receiveTimeout;
this.jmsTemplate.setReceiveTimeout(receiveTimeout);
}
public void setRecoveryInterval(long recoveryInterval) {
Assert.isTrue(this.messageDriven, "'recoveryInterval' is allowed only in case of 'messageDriven = true'");
this.recoveryInterval = recoveryInterval;
}
public void setSessionAcknowledgeMode(int sessionAcknowledgeMode) {
this.sessionAcknowledgeMode = sessionAcknowledgeMode;
this.jmsTemplate.setSessionAcknowledgeMode(sessionAcknowledgeMode);
}
public void setSessionTransacted(boolean sessionTransacted) {
this.sessionTransacted = sessionTransacted;
this.jmsTemplate.setSessionTransacted(sessionTransacted);
}
public void setSubscriptionDurable(boolean subscriptionDurable) {
Assert.isTrue(this.messageDriven, "'subscriptionDurable' is allowed only in case of 'messageDriven = true'");
this.subscriptionDurable = subscriptionDurable;
}
public void setTaskExecutor(Executor taskExecutor) {
Assert.isTrue(this.messageDriven, "'taskExecutor' is allowed only in case of 'messageDriven = true'");
this.taskExecutor = taskExecutor;
}
public void setTransactionManager(PlatformTransactionManager transactionManager) {
Assert.isTrue(this.messageDriven, "'transactionManager' is allowed only in case of 'messageDriven = true'");
this.transactionManager = transactionManager;
}
public void setTransactionName(String transactionName) {
Assert.isTrue(this.messageDriven, "'transactionName' is allowed only in case of 'messageDriven = true'");
this.transactionName = transactionName;
}
public void setTransactionTimeout(int transactionTimeout) {
Assert.isTrue(this.messageDriven, "'transactionTimeout' is allowed only in case of 'messageDriven = true'");
this.transactionTimeout = transactionTimeout;
}
public void setMaxSubscribers(int maxSubscribers) {
Assert.isTrue(this.messageDriven, "'maxSubscribers' is allowed only in case of 'messageDriven = true'");
this.maxSubscribers = maxSubscribers;
}
public void setSubscriptionShared(boolean subscriptionShared) {
this.subscriptionShared = subscriptionShared;
}
@Override
public void setBeanName(String name) {
this.beanName = name;
}
@Override
public Class<?> getObjectType() {
return (this.channel != null) ? this.channel.getClass() : AbstractJmsChannel.class;
}
@Override
protected AbstractJmsChannel createInstance() throws Exception {
this.initializeJmsTemplate();
if (this.messageDriven) {
this.container = createContainer();
SubscribableJmsChannel subscribableJmsChannel = new SubscribableJmsChannel(this.container, this.jmsTemplate);
subscribableJmsChannel.setMaxSubscribers(this.maxSubscribers);
this.channel = subscribableJmsChannel;
}
else {
Assert.isTrue(!Boolean.TRUE.equals(this.pubSubDomain),
"A JMS Topic-backed 'publish-subscribe-channel' must be message-driven.");
PollableJmsChannel pollableJmschannel = new PollableJmsChannel(this.jmsTemplate);
if (this.messageSelector != null) {
pollableJmschannel.setMessageSelector(this.messageSelector);
}
this.channel = pollableJmschannel;
}
if (!CollectionUtils.isEmpty(this.interceptors)) {
this.channel.setInterceptors(this.interceptors);
}
this.channel.setBeanName(this.beanName);
if (this.getBeanFactory() != null) {
this.channel.setBeanFactory(this.getBeanFactory());
}
this.channel.afterPropertiesSet();
return this.channel;
}
private void initializeJmsTemplate() {
Assert.isTrue(this.destination != null ^ this.destinationName != null,
"Exactly one of destination or destinationName is required.");
if (this.destination != null) {
this.jmsTemplate.setDefaultDestination(this.destination);
}
if (this.destinationName != null) {
this.jmsTemplate.setDefaultDestinationName(this.destinationName);
}
}
private AbstractMessageListenerContainer createContainer() throws Exception {
if (this.containerType == null) {
this.containerType = DefaultMessageListenerContainer.class;
}
AbstractMessageListenerContainer container = this.containerType.newInstance();
container.setAcceptMessagesWhileStopping(this.acceptMessagesWhileStopping);
container.setAutoStartup(this.autoStartup);
container.setClientId(this.clientId);
container.setConnectionFactory(this.connectionFactory);
if (this.destination != null) {
container.setDestination(this.destination);
}
if (this.destinationName != null) {
container.setDestinationName(this.destinationName);
}
if (this.destinationResolver != null) {
container.setDestinationResolver(this.destinationResolver);
}
container.setDurableSubscriptionName(this.durableSubscriptionName);
container.setErrorHandler(this.errorHandler);
container.setExceptionListener(this.exceptionListener);
if (this.exposeListenerSession != null) {
container.setExposeListenerSession(this.exposeListenerSession);
}
container.setMessageSelector(this.messageSelector);
if (this.phase != null) {
container.setPhase(this.phase);
}
if (this.pubSubDomain != null) {
container.setPubSubDomain(this.pubSubDomain);
}
container.setSessionAcknowledgeMode(this.sessionAcknowledgeMode);
container.setSessionTransacted(this.sessionTransacted);
container.setSubscriptionDurable(this.subscriptionDurable);
container.setSubscriptionShared(this.subscriptionShared);
if (container instanceof DefaultMessageListenerContainer) {
DefaultMessageListenerContainer dmlc = (DefaultMessageListenerContainer) container;
if (this.cacheLevelName != null) {
dmlc.setCacheLevelName(this.cacheLevelName);
}
if (this.cacheLevel != null) {
dmlc.setCacheLevel(this.cacheLevel);
}
if (StringUtils.hasText(this.concurrency)) {
dmlc.setConcurrency(this.concurrency);
}
if (this.concurrentConsumers != null) {
dmlc.setConcurrentConsumers(this.concurrentConsumers);
}
if (this.maxConcurrentConsumers != null) {
dmlc.setMaxConcurrentConsumers(this.maxConcurrentConsumers);
}
if (this.idleTaskExecutionLimit != null) {
dmlc.setIdleTaskExecutionLimit(this.idleTaskExecutionLimit);
}
if (this.maxMessagesPerTask != null) {
dmlc.setMaxMessagesPerTask(this.maxMessagesPerTask);
}
dmlc.setPubSubNoLocal(this.pubSubNoLocal);
if (this.receiveTimeout != null) {
dmlc.setReceiveTimeout(this.receiveTimeout);
}
if (this.recoveryInterval != null) {
dmlc.setRecoveryInterval(this.recoveryInterval);
}
dmlc.setTaskExecutor(this.taskExecutor);
dmlc.setTransactionManager(this.transactionManager);
if (this.transactionName != null) {
dmlc.setTransactionName(this.transactionName);
}
if (this.transactionTimeout != null) {
dmlc.setTransactionTimeout(this.transactionTimeout);
}
}
else if (container instanceof SimpleMessageListenerContainer) {
SimpleMessageListenerContainer smlc = (SimpleMessageListenerContainer) container;
if (StringUtils.hasText(this.concurrency)) {
smlc.setConcurrency(this.concurrency);
}
if (this.concurrentConsumers != null) {
smlc.setConcurrentConsumers(this.concurrentConsumers);
}
smlc.setPubSubNoLocal(this.pubSubNoLocal);
smlc.setTaskExecutor(this.taskExecutor);
}
return container;
}
/*
* SmartLifecycle implementation (delegates to the created channel if message-driven)
*/
@Override
public boolean isAutoStartup() {
return this.channel instanceof SubscribableJmsChannel && ((SubscribableJmsChannel) this.channel).isAutoStartup();
}
@Override
public int getPhase() {
return (this.channel instanceof SubscribableJmsChannel) ?
((SubscribableJmsChannel) this.channel).getPhase() : 0;
}
@Override
public boolean isRunning() {
return this.channel instanceof SubscribableJmsChannel && ((SubscribableJmsChannel) this.channel).isRunning();
}
@Override
public void start() {
if (this.channel instanceof SubscribableJmsChannel) {
((SubscribableJmsChannel) this.channel).start();
}
}
@Override
public void stop() {
if (this.channel instanceof SubscribableJmsChannel) {
((SubscribableJmsChannel) this.channel).stop();
}
}
@Override
public void stop(Runnable callback) {
if (this.channel instanceof SubscribableJmsChannel) {
((SubscribableJmsChannel) this.channel).stop(callback);
}
}
@Override
protected void destroyInstance(AbstractJmsChannel instance) throws Exception {
if (instance instanceof SubscribableJmsChannel) {
((SubscribableJmsChannel) this.channel).destroy();
}
}
}