/*
* 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.amqp.channel;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.MessageListener;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.amqp.support.converter.SimpleMessageConverter;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.SmartLifecycle;
import org.springframework.integration.MessageDispatchingException;
import org.springframework.integration.amqp.support.AmqpHeaderMapper;
import org.springframework.integration.context.IntegrationProperties;
import org.springframework.integration.dispatcher.AbstractDispatcher;
import org.springframework.integration.dispatcher.MessageDispatcher;
import org.springframework.integration.support.AbstractIntegrationMessageBuilder;
import org.springframework.integration.support.MessageBuilderFactory;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageDeliveryException;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.util.Assert;
/**
* @author Mark Fisher
* @author Gary Russell
* @author Artem Bilan
* @since 2.1
*/
abstract class AbstractSubscribableAmqpChannel extends AbstractAmqpChannel
implements SubscribableChannel, SmartLifecycle, DisposableBean {
private final String channelName;
private final AbstractMessageListenerContainer container;
private volatile AbstractDispatcher dispatcher;
private final boolean isPubSub;
private volatile Integer maxSubscribers;
private final AmqpAdmin admin;
private final ConnectionFactory connectionFactory;
/**
* Construct an instance with the supplied name, container and template; default header
* mappers will be used if the message is mapped.
* @param channelName the channel name.
* @param container the container.
* @param amqpTemplate the template.
* @see #setExtractPayload(boolean)
*/
protected AbstractSubscribableAmqpChannel(String channelName, AbstractMessageListenerContainer container,
AmqpTemplate amqpTemplate) {
this(channelName, container, amqpTemplate, false);
}
/**
* Construct an instance with the supplied name, container and template; default header
* mappers will be used if the message is mapped.
* @param channelName the channel name.
* @param container the container.
* @param amqpTemplate the template.
* @param outboundMapper the outbound mapper.
* @param inboundMapper the inbound mapper.
* @see #setExtractPayload(boolean)
* @since 4.3
*/
protected AbstractSubscribableAmqpChannel(String channelName, AbstractMessageListenerContainer container,
AmqpTemplate amqpTemplate, AmqpHeaderMapper outboundMapper, AmqpHeaderMapper inboundMapper) {
this(channelName, container, amqpTemplate, false, outboundMapper, inboundMapper);
}
/**
* Construct an instance with the supplied name, container and template; default header
* mappers will be used if the message is mapped.
* @param channelName the channel name.
* @param container the container.
* @param amqpTemplate the template.
* @param isPubSub true for a pub/sub channel.
* @see #setExtractPayload(boolean)
*/
protected AbstractSubscribableAmqpChannel(String channelName,
AbstractMessageListenerContainer container,
AmqpTemplate amqpTemplate, boolean isPubSub) {
super(amqpTemplate);
Assert.notNull(container, "container must not be null");
Assert.hasText(channelName, "channel name must not be empty");
this.channelName = channelName;
this.container = container;
this.isPubSub = isPubSub;
this.connectionFactory = container.getConnectionFactory();
this.admin = new RabbitAdmin(this.connectionFactory);
}
/**
* Construct an instance with the supplied name, container and template; default header
* mappers will be used if the message is mapped.
* @param channelName the channel name.
* @param container the container.
* @param amqpTemplate the template.
* @param isPubSub true for a pub/sub channel.
* @param outboundMapper the outbound mapper.
* @param inboundMapper the inbound mapper.
* @see #setExtractPayload(boolean)
* @since 4.3
*/
protected AbstractSubscribableAmqpChannel(String channelName,
AbstractMessageListenerContainer container,
AmqpTemplate amqpTemplate, boolean isPubSub,
AmqpHeaderMapper outboundMapper, AmqpHeaderMapper inboundMapper) {
super(amqpTemplate, outboundMapper, inboundMapper);
Assert.notNull(container, "container must not be null");
Assert.hasText(channelName, "channel name must not be empty");
this.channelName = channelName;
this.container = container;
this.isPubSub = isPubSub;
this.connectionFactory = container.getConnectionFactory();
this.admin = new RabbitAdmin(this.connectionFactory);
}
/**
* Specify the maximum number of subscribers supported by the
* channel's dispatcher (if it is an {@link AbstractDispatcher}).
* @param maxSubscribers The maximum number of subscribers allowed.
*/
public void setMaxSubscribers(int maxSubscribers) {
this.maxSubscribers = maxSubscribers;
if (this.dispatcher != null) {
this.dispatcher.setMaxSubscribers(this.maxSubscribers);
}
}
protected AmqpAdmin getAdmin() {
return this.admin;
}
protected ConnectionFactory getConnectionFactory() {
return this.connectionFactory;
}
@Override
public boolean subscribe(MessageHandler handler) {
return this.dispatcher.addHandler(handler);
}
@Override
public boolean unsubscribe(MessageHandler handler) {
return this.dispatcher.removeHandler(handler);
}
@Override
public void onInit() throws Exception {
super.onInit();
this.dispatcher = this.createDispatcher();
if (this.maxSubscribers == null) {
this.maxSubscribers = this.getIntegrationProperty(this.isPubSub ?
IntegrationProperties.CHANNELS_MAX_BROADCAST_SUBSCRIBERS :
IntegrationProperties.CHANNELS_MAX_UNICAST_SUBSCRIBERS,
Integer.class);
}
setMaxSubscribers(this.maxSubscribers);
String queue = this.obtainQueueName(this.admin, this.channelName);
this.container.setQueueNames(queue);
MessageConverter converter = (this.getAmqpTemplate() instanceof RabbitTemplate)
? ((RabbitTemplate) this.getAmqpTemplate()).getMessageConverter()
: new SimpleMessageConverter();
MessageListener listener = new DispatchingMessageListener(converter,
this.dispatcher, this, this.isPubSub,
getMessageBuilderFactory(), getInboundHeaderMapper());
this.container.setMessageListener(listener);
if (!this.container.isActive()) {
this.container.afterPropertiesSet();
}
}
/*
* 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();
}
}
protected abstract AbstractDispatcher createDispatcher();
protected abstract String obtainQueueName(AmqpAdmin admin, String channelName);
private static final class DispatchingMessageListener implements MessageListener {
private final Log logger = LogFactory.getLog(this.getClass());
private final MessageDispatcher dispatcher;
private final MessageConverter converter;
private final AbstractSubscribableAmqpChannel channel;
private final boolean isPubSub;
private final MessageBuilderFactory messageBuilderFactory;
private final AmqpHeaderMapper inboundHeaderMapper;
private DispatchingMessageListener(MessageConverter converter,
MessageDispatcher dispatcher, AbstractSubscribableAmqpChannel channel, boolean isPubSub,
MessageBuilderFactory messageBuilderFactory, AmqpHeaderMapper inboundHeaderMapper) {
Assert.notNull(converter, "MessageConverter must not be null");
Assert.notNull(dispatcher, "MessageDispatcher must not be null");
this.converter = converter;
this.dispatcher = dispatcher;
this.channel = channel;
this.isPubSub = isPubSub;
this.messageBuilderFactory = messageBuilderFactory;
this.inboundHeaderMapper = inboundHeaderMapper;
}
@Override
public void onMessage(org.springframework.amqp.core.Message message) {
Message<?> messageToSend = null;
try {
Object converted = this.converter.fromMessage(message);
if (converted != null) {
messageToSend = (converted instanceof Message<?>) ? (Message<?>) converted
: buildMessage(message, converted);
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 amqp-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);
}
}
}
protected Message<Object> buildMessage(org.springframework.amqp.core.Message message, Object converted) {
AbstractIntegrationMessageBuilder<Object> messageBuilder =
this.messageBuilderFactory.withPayload(converted);
if (this.channel.isExtractPayload()) {
Map<String, Object> headers =
this.inboundHeaderMapper.toHeadersFromRequest(message.getMessageProperties());
messageBuilder.copyHeaders(headers);
}
return messageBuilder.build();
}
}
}