/*
* 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;
import javax.jms.MessageListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.SmartLifecycle;
import org.springframework.integration.MessageDispatchingException;
import org.springframework.integration.context.IntegrationProperties;
import org.springframework.integration.dispatcher.AbstractDispatcher;
import org.springframework.integration.dispatcher.BroadcastingDispatcher;
import org.springframework.integration.dispatcher.MessageDispatcher;
import org.springframework.integration.dispatcher.RoundRobinLoadBalancingStrategy;
import org.springframework.integration.dispatcher.UnicastingDispatcher;
import org.springframework.integration.support.MessageBuilderFactory;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.listener.AbstractMessageListenerContainer;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageDeliveryException;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.util.Assert;
/**
* @author Mark Fisher
* @author Gary Russell
* @since 2.0
*/
public class SubscribableJmsChannel extends AbstractJmsChannel implements SubscribableChannel, SmartLifecycle, DisposableBean {
private final AbstractMessageListenerContainer container;
private volatile AbstractDispatcher dispatcher;
private volatile boolean initialized;
private volatile Integer maxSubscribers;
public SubscribableJmsChannel(AbstractMessageListenerContainer container, JmsTemplate jmsTemplate) {
super(jmsTemplate);
Assert.notNull(container, "container must not be null");
this.container = container;
}
/**
* 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;
}
@Override
public boolean subscribe(MessageHandler handler) {
Assert.state(this.dispatcher != null, "'MessageDispatcher' must not be null. This channel might not have been initialized");
return this.dispatcher.addHandler(handler);
}
@Override
public boolean unsubscribe(MessageHandler handler) {
Assert.state(this.dispatcher != null, "'MessageDispatcher' must not be null. This channel might not have been initialized");
return this.dispatcher.removeHandler(handler);
}
@Override
public void onInit() throws Exception {
if (this.initialized) {
return;
}
super.onInit();
boolean isPubSub = this.container.isPubSubDomain();
this.configureDispatcher(isPubSub);
MessageListener listener = new DispatchingMessageListener(
this.getJmsTemplate(), this.dispatcher,
this, isPubSub, this.getMessageBuilderFactory());
this.container.setMessageListener(listener);
if (!this.container.isActive()) {
this.container.afterPropertiesSet();
}
this.initialized = true;
}
private void configureDispatcher(boolean isPubSub) {
if (isPubSub) {
BroadcastingDispatcher broadcastingDispatcher = new BroadcastingDispatcher(true);
broadcastingDispatcher.setBeanFactory(this.getBeanFactory());
this.dispatcher = broadcastingDispatcher;
}
else {
UnicastingDispatcher unicastingDispatcher = new UnicastingDispatcher();
unicastingDispatcher.setLoadBalancingStrategy(new RoundRobinLoadBalancingStrategy());
this.dispatcher = unicastingDispatcher;
}
if (this.maxSubscribers == null) {
this.maxSubscribers = this.getIntegrationProperty(isPubSub ?
IntegrationProperties.CHANNELS_MAX_BROADCAST_SUBSCRIBERS :
IntegrationProperties.CHANNELS_MAX_UNICAST_SUBSCRIBERS,
Integer.class);
}
this.dispatcher.setMaxSubscribers(this.maxSubscribers);
}
/*
* SmartLifecycle implementation (delegates to the MessageListener container)
*/
@Override
public boolean isAutoStartup() {
return (this.container != null) ? this.container.isAutoStartup() : false;
}
@Override
public int getPhase() {
return (this.container != null) ? this.container.getPhase() : 0;
}
@Override
public boolean isRunning() {
return (this.container != null) ? this.container.isRunning() : false;
}
@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 static final class DispatchingMessageListener implements MessageListener {
private final Log logger = LogFactory.getLog(this.getClass());
private final JmsTemplate jmsTemplate;
private final MessageDispatcher dispatcher;
private final SubscribableJmsChannel channel;
private final boolean isPubSub;
private final MessageBuilderFactory messageBuilderFactory;
DispatchingMessageListener(JmsTemplate jmsTemplate,
MessageDispatcher dispatcher, SubscribableJmsChannel channel, boolean isPubSub,
MessageBuilderFactory messageBuilderFactory) {
this.jmsTemplate = jmsTemplate;
this.dispatcher = dispatcher;
this.channel = channel;
this.isPubSub = isPubSub;
this.messageBuilderFactory = messageBuilderFactory;
}
@Override
public void onMessage(javax.jms.Message message) {
Message<?> messageToSend = null;
try {
Object converted = this.jmsTemplate.getMessageConverter().fromMessage(message);
if (converted != null) {
messageToSend = (converted instanceof Message<?>) ? (Message<?>) converted
: this.messageBuilderFactory.withPayload(converted).build();
this.dispatcher.dispatch(messageToSend);
}
else if (this.logger.isWarnEnabled()) {
this.logger.warn("MessageConverter returned null, no Message to dispatch");
}
}
catch (MessageDispatchingException e) {
String exceptionMessage = e.getMessage() + " for jms-channel '"
+ this.channel.getFullChannelName() + "'.";
if (this.isPubSub) {
// log only for backwards compatibility with pub/sub
if (this.logger.isWarnEnabled()) {
this.logger.warn(exceptionMessage, e);
}
}
else {
throw new MessageDeliveryException(
messageToSend, exceptionMessage, e);
}
}
catch (Exception e) {
throw new MessagingException("failed to handle incoming JMS Message", e);
}
}
}
}