org.mule.transport.amqp.AmqpConnector.java Source code

Java tutorial

Introduction

Here is the source code for org.mule.transport.amqp.AmqpConnector.java

Source

/*
 * $Id$
 * --------------------------------------------------------------------------------------
 * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 *
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.txt file.
 */

package org.mule.transport.amqp;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.pool.BasePoolableObjectFactory;
import org.apache.commons.pool.impl.StackObjectPool;
import org.mule.api.DefaultMuleException;
import org.mule.api.MuleContext;
import org.mule.api.MuleEvent;
import org.mule.api.MuleException;
import org.mule.api.MuleMessage;
import org.mule.api.construct.FlowConstruct;
import org.mule.api.endpoint.EndpointBuilder;
import org.mule.api.endpoint.EndpointException;
import org.mule.api.endpoint.ImmutableEndpoint;
import org.mule.api.endpoint.InboundEndpoint;
import org.mule.api.endpoint.OutboundEndpoint;
import org.mule.api.lifecycle.InitialisationException;
import org.mule.api.processor.MessageProcessor;
import org.mule.api.transformer.Transformer;
import org.mule.api.transport.Connectable;
import org.mule.api.transport.MessageDispatcher;
import org.mule.api.transport.MessageReceiver;
import org.mule.api.transport.MessageRequester;
import org.mule.api.transport.PropertyScope;
import org.mule.api.transport.ReplyToHandler;
import org.mule.config.i18n.MessageFactory;
import org.mule.construct.AbstractFlowConstruct;
import org.mule.transport.AbstractConnector;
import org.mule.transport.ConnectException;
import org.mule.transport.amqp.AmqpConstants.AckMode;
import org.mule.transport.amqp.AmqpConstants.DeliveryMode;
import org.mule.transport.amqp.transformers.AmqpMessageToObject;
import org.mule.util.NumberUtils;
import org.mule.util.StringUtils;

import com.rabbitmq.client.Address;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.client.QueueingConsumer.Delivery;
import com.rabbitmq.client.ReturnListener;
import com.rabbitmq.client.ShutdownListener;
import com.rabbitmq.client.ShutdownSignalException;

/**
 * Connects to a particular virtual host on a particular AMQP broker.
 */
public class AmqpConnector extends AbstractConnector {
    public static final String AMQP = "amqp";

    private final Transformer receiveTransformer;

    private String host;
    private int port = ConnectionFactory.DEFAULT_AMQP_PORT;
    private String[] fallbackAddresses;
    private String virtualHost;
    private String username;
    private String password;
    private DeliveryMode deliveryMode;
    private byte priority;
    private AckMode ackMode;
    private boolean activeDeclarationsOnly;
    private boolean mandatory;
    private boolean immediate;
    private ReturnListener defaultReturnListener;
    private EndpointBuilder defaultReturnEndpointBuilder;
    private int prefetchSize;
    private int prefetchCount;
    private boolean noLocal;
    private boolean exclusiveConsumers;

    private ConnectionFactory connectionFactory;
    private Connection connection;
    private final StackObjectPool connectorConnectionPool;

    /**
     * A fake {@link FlowConstruct} that is used when the events need to be dispatched on behalf of
     * the connector and out of any actual Flow context.
     */
    protected static class AmqpConnectorFlowConstruct extends AbstractFlowConstruct {
        private final AmqpConnector connector;

        private AmqpConnectorFlowConstruct(final AmqpConnector connector) {
            super(connector.getName(), connector.getMuleContext());
            this.connector = connector;
        }

        @Override
        public String getConstructType() {
            return "Global AMQP Connector Fake Flow";
        }

        public AmqpConnector getConnector() {
            return connector;
        }
    }

    protected static abstract class AmqpConnection {
        private final Log logger = LogFactory.getLog(getClass());
        private final AmqpConnector amqpConnector;
        private final AtomicReference<Channel> channelRef = new AtomicReference<Channel>();

        private AmqpConnection(final AmqpConnector amqpConnector) {
            this.amqpConnector = amqpConnector;
        }

        private Channel newChannel() {
            try {
                final Channel channel = amqpConnector.createChannel();

                channel.addShutdownListener(new ShutdownListener() {
                    public void shutdownCompleted(final ShutdownSignalException sse) {
                        if (sse.isInitiatedByApplication()) {
                            return;
                        }

                        // do not inform the connector of the issue as it can't
                        // decide what to do reset the channel so it would later
                        // be lazily reconnected
                        channelRef.set(null);
                        if (logger.isDebugEnabled()) {
                            logger.debug("Terminated dead channel: " + channel, sse);
                        }
                    }
                });

                if (logger.isDebugEnabled()) {
                    logger.debug("Shutdown listener configured on channel: " + channel);
                }

                return channel;
            } catch (final Exception e) {
                if ((!amqpConnector.isStopping()) && (amqpConnector.isStarted())) {
                    amqpConnector.getMuleContext().getExceptionListener()
                            .handleException(new ConnectException(MessageFactory
                                    .createStaticMessage("Impossible to create new channels on connection: "
                                            + amqpConnector.getConnection()),
                                    e, amqpConnector));
                }
                return null;
            }
        }

        public AmqpConnector getAmqpConnector() {
            return amqpConnector;
        }

        public Channel getChannel() {
            Channel channel = channelRef.get();

            if (channel != null) {
                return channel;
            }

            channel = newChannel();

            if (channelRef.compareAndSet(null, channel)) {
                return channel;
            }

            // race condition: use the channel created by another thread
            return getChannel();
        }

        @Override
        public String toString() {
            return super.toString() + ", Channel: " + getChannel();
        }
    }

    protected static class ConnectorConnection extends AmqpConnection {
        private ConnectorConnection(final AmqpConnector amqpConnector) {
            super(amqpConnector);
        }
    }

    public static class InboundConnection extends AmqpConnection {
        private final String queue;

        private InboundConnection(final AmqpConnector amqpConnector, final String queue) {
            super(amqpConnector);
            this.queue = queue;
        }

        public String getQueue() {
            return queue;
        }
    }

    public static class OutboundConnection extends AmqpConnection {
        private final String exchange;
        private final String routingKey;

        private OutboundConnection(final AmqpConnector amqpConnector, final String exchange,
                final String routingKey) {
            super(amqpConnector);
            this.exchange = exchange;
            this.routingKey = routingKey;
        }

        public String getExchange() {
            return exchange;
        }

        public String getRoutingKey() {
            return routingKey;
        }
    }

    protected static class ConnectorConnectionPoolableObjectFactory extends BasePoolableObjectFactory {
        private final AmqpConnector amqpConnector;

        private ConnectorConnectionPoolableObjectFactory(final AmqpConnector amqpConnector) {
            this.amqpConnector = amqpConnector;
        }

        @Override
        public Object makeObject() throws Exception {
            final ConnectorConnection connectorConnection = new ConnectorConnection(amqpConnector);
            if (amqpConnector.logger.isDebugEnabled()) {
                amqpConnector.logger.debug("Created new: " + connectorConnection);
            }
            return connectorConnection;
        }

        @Override
        public boolean validateObject(final Object obj) {
            final Channel channel = ((ConnectorConnection) obj).getChannel();
            return channel != null && channel.isOpen();
        }

        @Override
        public void destroyObject(final Object obj) throws Exception {
            if (amqpConnector.logger.isDebugEnabled()) {
                amqpConnector.logger.debug("Destroying " + obj);
            }

            try {
                final Channel channel = ((ConnectorConnection) obj).getChannel();
                if (channel != null) {
                    channel.close();
                }
            } catch (final Exception e) {
                amqpConnector.logger.info("Ignored exception when destroying ConnectorConnection:", e);
            }
        }
    }

    protected interface ConnectorConnectionAction<T> {
        public T run(ConnectorConnection connectorConnection) throws Exception;
    }

    public AmqpConnector(final MuleContext context) {
        super(context);

        receiveTransformer = new AmqpMessageToObject();
        receiveTransformer.setMuleContext(context);

        final int maxIdle = 1;
        final int initIdleCapacity = 0;
        connectorConnectionPool = new StackObjectPool(new ConnectorConnectionPoolableObjectFactory(this), maxIdle,
                initIdleCapacity);
    }

    @Override
    public void doInitialise() throws InitialisationException {
        if (connectionFactory == null) {
            connectionFactory = new ConnectionFactory();
            connectionFactory.setVirtualHost(virtualHost);
            connectionFactory.setUsername(username);
            connectionFactory.setPassword(password);
        } else {
            if (connectionFactory.getVirtualHost() != null) {
                setVirtualHost(connectionFactory.getVirtualHost());
            } else {
                connectionFactory.setVirtualHost(virtualHost);
            }
            setUsername(connectionFactory.getUsername());
            setPassword(connectionFactory.getPassword());
            setHost(connectionFactory.getHost());
            setPort(connectionFactory.getPort());
        }
    }

    @Override
    public void doConnect() throws Exception {
        final List<Address> brokerAddresses = new ArrayList<Address>();
        brokerAddresses.add(new Address(host, port));
        addFallbackAddresses(brokerAddresses);

        connectToFirstResponsiveBroker(brokerAddresses);

        configureDefaultReturnListener();
        // clear any connector connections that could have been created in a previous
        // connect() operation
        connectorConnectionPool.clear();
    }

    @Override
    public void doStart() throws MuleException {
        // NOOP
    }

    @Override
    public void doStop() throws MuleException {
        // NOOP
    }

    @Override
    public void doDisconnect() throws Exception {
        connectorConnectionPool.clear();
        connection.close();
    }

    @Override
    public void doDispose() {
        try {
            connectorConnectionPool.close();
        } catch (final Exception e) {
            logger.error("Can't close the connector connection pool", e);
        }
        connection = null;
        connectionFactory = null;
    }

    protected void addFallbackAddresses(final List<Address> brokerAddresses) {
        if (fallbackAddresses == null)
            return;

        for (final String fallbackAddress : fallbackAddresses) {
            final String[] fallbackAddressElements = StringUtils.splitAndTrim(fallbackAddress, ":");

            if (fallbackAddressElements.length == 2) {
                brokerAddresses.add(
                        new Address(fallbackAddressElements[0], NumberUtils.toInt(fallbackAddressElements[1])));
            } else if (fallbackAddressElements.length == 1) {
                brokerAddresses.add(new Address(fallbackAddressElements[0]));
            } else {
                logger.warn("Ignoring unparseable fallback address: " + fallbackAddress);
            }
        }
    }

    protected void connectToFirstResponsiveBroker(final List<Address> brokerAddresses) throws Exception {
        Exception lastException = null;

        for (final Address brokerAddress : brokerAddresses) {
            lastException = null;

            try {
                connectionFactory.setHost(brokerAddress.getHost());
                connectionFactory.setPort(brokerAddress.getPort());
                connection = connectionFactory.newConnection();

                connection.addShutdownListener(new ShutdownListener() {
                    public void shutdownCompleted(final ShutdownSignalException sse) {
                        if (sse.isInitiatedByApplication()) {
                            return;
                        }

                        getMuleContext().getExceptionListener()
                                .handleException(new ConnectException(
                                        MessageFactory.createStaticMessage(
                                                "Connection shutdown detected for: " + getName()),
                                        sse, AmqpConnector.this));
                    }
                });

            } catch (final Exception e) {
                lastException = e;
            }
        }

        if (lastException != null) {
            throw lastException;
        }
    }

    protected void configureDefaultReturnListener() throws InitialisationException {
        if (defaultReturnEndpointBuilder == null) {
            defaultReturnListener = AmqpReturnHandler.DEFAULT_RETURN_LISTENER;
            return;
        }

        try {
            final MessageProcessor defaultReturnEndpoint = defaultReturnEndpointBuilder.buildOutboundEndpoint();
            defaultReturnListener = new AmqpReturnHandler.DispatchingReturnListener(
                    Collections.singletonList(defaultReturnEndpoint), new AmqpConnectorFlowConstruct(this));
            logger.info(String.format("Configured default return endpoint: %s", defaultReturnListener));
        } catch (final EndpointException ee) {
            throw new InitialisationException(
                    MessageFactory.createStaticMessage("Failed to configure default return endpoint"), ee, this);
        }
    }

    public static Long getDeliveryTagFromMessage(final MuleMessage message) {
        return message.getInvocationProperty(AmqpConstants.AMQP_DELIVERY_TAG,
                message.<Long>getInboundProperty(AmqpConstants.DELIVERY_TAG));
    }

    public static Channel getChannelFromMessage(final MuleMessage message) {
        return getChannelFromMessage(message, null);
    }

    public static Channel getChannelFromMessage(final MuleMessage message, final Channel defaultValue) {
        return message.getInvocationProperty(AmqpConstants.CHANNEL, defaultValue);
    }

    public InboundConnection connect(final MessageReceiver messageReceiver) throws ConnectException {
        return connect(messageReceiver, messageReceiver.getEndpoint());
    }

    public InboundConnection connect(final MessageRequester messageRequester) throws ConnectException {
        return connect(messageRequester, messageRequester.getEndpoint());
    }

    protected <T> T runConnectorConnectionAction(final ConnectorConnectionAction<T> action) throws Exception {
        ConnectorConnection connectorConnection = null;

        try {
            connectorConnection = (ConnectorConnection) connectorConnectionPool.borrowObject();
            return action.run(connectorConnection);
        } catch (final Exception e) {
            // whenever an exception occurs with a ConnectorConnection, consider the
            // object failed and invalidate it
            if (connectorConnection != null) {
                try {
                    connectorConnectionPool.invalidateObject(connectorConnection);
                } catch (final Exception e2) {
                    logger.error("Can't invalidate a borrowed connector connection", e2);
                }
            }

            throw e;
        } finally {
            if (connectorConnection != null) {
                try {
                    connectorConnectionPool.returnObject(connectorConnection);
                } catch (final Exception e) {
                    logger.error("Can't return a borrowed connector connection", e);
                }
            }
        }
    }

    protected InboundConnection connect(final Connectable connectable, final InboundEndpoint inboundEndpoint)
            throws ConnectException {
        try {
            return runConnectorConnectionAction(new ConnectorConnectionAction<InboundConnection>() {
                public InboundConnection run(final ConnectorConnection connectorConnection) throws Exception {
                    final String queueName = AmqpEndpointUtil.getOrCreateQueue(connectorConnection.getChannel(),
                            inboundEndpoint, activeDeclarationsOnly);
                    return new InboundConnection(connectorConnection.getAmqpConnector(), queueName);
                }
            });
        } catch (final Exception e) {
            throw new ConnectException(MessageFactory.createStaticMessage(
                    "Error when connecting inbound endpoint: " + inboundEndpoint), e, connectable);
        }
    }

    public OutboundConnection connect(final MessageDispatcher messageDispatcher, final MuleEvent muleEvent)
            throws ConnectException {
        final OutboundEndpoint outboundEndpoint = messageDispatcher.getEndpoint();

        try {
            return runConnectorConnectionAction(new ConnectorConnectionAction<OutboundConnection>() {
                public OutboundConnection run(final ConnectorConnection connectorConnection) throws Exception {

                    final String exchange = AmqpEndpointUtil.getOrCreateExchange(connectorConnection.getChannel(),
                            outboundEndpoint, activeDeclarationsOnly);

                    String routingKey = AmqpEndpointUtil.getRoutingKey(outboundEndpoint, muleEvent);

                    // if dispatching to default exchange and routing key has been omitted use the
                    // queueName as routing key
                    if ((AmqpEndpointUtil.isDefaultExchange(exchange)) && (StringUtils.isBlank(routingKey))) {
                        final String queueName = AmqpEndpointUtil.getQueueName(outboundEndpoint.getAddress());
                        if (StringUtils.isNotBlank(queueName)) {
                            routingKey = queueName;
                        }
                    }

                    if (StringUtils.isNotEmpty(AmqpEndpointUtil.getQueueName(outboundEndpoint.getAddress()))
                            || outboundEndpoint.getProperties().containsKey(AmqpEndpointUtil.QUEUE_DURABLE)
                            || outboundEndpoint.getProperties().containsKey(AmqpEndpointUtil.QUEUE_AUTO_DELETE)
                            || outboundEndpoint.getProperties().containsKey(AmqpEndpointUtil.QUEUE_EXCLUSIVE)) {
                        AmqpEndpointUtil.getOrCreateQueue(connectorConnection.getChannel(), outboundEndpoint,
                                activeDeclarationsOnly, exchange, routingKey);
                    }

                    final OutboundConnection oc = new OutboundConnection(connectorConnection.getAmqpConnector(),
                            exchange, routingKey);

                    return oc;
                }
            });
        } catch (final Exception e) {
            throw new ConnectException(MessageFactory.createStaticMessage(
                    "Error when connecting outbound endpoint: " + outboundEndpoint), e, messageDispatcher);
        }
    }

    public AmqpMessage consume(final Channel channel, final String queue, final boolean autoAck, final long timeout)
            throws IOException, InterruptedException {
        final QueueingConsumer consumer = new QueueingConsumer(channel);
        final String consumerTag = channel.basicConsume(queue, autoAck, consumer);
        final Delivery delivery = consumer.nextDelivery(timeout);
        channel.basicCancel(consumerTag);

        if (delivery == null)
            return null;

        return new AmqpMessage(consumerTag, delivery.getEnvelope(), delivery.getProperties(), delivery.getBody());
    }

    public void ackMessageIfNecessary(final Channel channel, final AmqpMessage amqpMessage,
            final ImmutableEndpoint endpoint) throws IOException {
        if ((endpoint.getTransactionConfig().isTransacted()) || (getAckMode() != AckMode.MULE_AUTO)) {
            return;
        }

        channel.basicAck(amqpMessage.getEnvelope().getDeliveryTag(), false);

        if (logger.isDebugEnabled()) {
            logger.debug("Mule acknowledged message: " + amqpMessage + " on channel: " + channel);
        }
    }

    public void addInvocationPropertiesIfNecessary(final Channel channel, final AmqpMessage amqpMessage,
            final MuleMessage muleMessage) {
        if (getAckMode() == AckMode.MANUAL) {
            // in manual AckMode, the channel will be needed to ack the message
            muleMessage.setProperty(AmqpConstants.CHANNEL, channel, PropertyScope.INVOCATION);
            // so will the consumer tag (which is already added in the inbound properties
            // for the end user but that we also add here in the invocation scope for
            // internal needs)
            muleMessage.setProperty(AmqpConstants.AMQP_DELIVERY_TAG, amqpMessage.getEnvelope().getDeliveryTag(),
                    PropertyScope.INVOCATION);
        }
    }

    public Channel createChannel() throws IOException {
        final Channel channel = getConnection().createChannel();
        channel.addReturnListener(defaultReturnListener);
        channel.basicQos(getPrefetchSize(), getPrefetchCount(), false);

        if (logger.isDebugEnabled()) {
            logger.debug("Created and configured new channel: " + channel);
        }

        return channel;
    }

    public void closeChannel(final Channel channel) throws ConnectException {
        if (channel == null) {
            return;
        }

        try {
            if (logger.isDebugEnabled()) {
                logger.debug("Closing channel: " + channel);
            }

            channel.close();

            if (logger.isDebugEnabled()) {
                logger.debug("Closed channel: " + channel);
            }
        } catch (final Exception e) {
            logger.warn(MessageFactory.createStaticMessage("Failed to close channel: " + channel), e);
        }
    }

    public void setDefaultReturnEndpoint(final EndpointBuilder defaultReturnEndpointBuilder) {
        this.defaultReturnEndpointBuilder = defaultReturnEndpointBuilder;
    }

    @Override
    public ReplyToHandler getReplyToHandler(final ImmutableEndpoint endpoint) {
        return new AmqpReplyToHandler(this);
    }

    @SuppressWarnings("unchecked")
    @Override
    protected Channel createOperationResource(final ImmutableEndpoint endpoint) throws MuleException {
        try {
            return createChannel();
        } catch (final IOException ioe) {
            throw new DefaultMuleException(ioe);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    protected Object getOperationResourceFactory() {
        return this;
    }

    public Connection getConnection() {
        return connection;
    }

    public Transformer getReceiveTransformer() {
        return receiveTransformer;
    }

    public String getProtocol() {
        return AMQP;
    }

    public Byte getPriority() {
        return priority;
    }

    public void setPriority(final Byte priority) {
        this.priority = priority;
    }

    public AckMode getAckMode() {
        return ackMode;
    }

    public void setAckMode(final AckMode ackMode) {
        this.ackMode = ackMode;
    }

    public void setActiveDeclarationsOnly(final boolean activeDeclarationsOnly) {
        this.activeDeclarationsOnly = activeDeclarationsOnly;
    }

    public DeliveryMode getDeliveryMode() {
        return deliveryMode;
    }

    public void setDeliveryMode(final DeliveryMode deliveryMode) {
        this.deliveryMode = deliveryMode;
    }

    public String getHost() {
        return host;
    }

    public void setHost(final String host) {
        this.host = host;
    }

    public int getPort() {
        return port;
    }

    public void setPort(final int port) {
        this.port = port;
    }

    public void setFallbackAddresses(final String[] fallbackAddresses) {
        this.fallbackAddresses = fallbackAddresses;
    }

    public void setVirtualHost(final String virtualHost) {
        this.virtualHost = virtualHost;
    }

    public void setUsername(final String username) {
        this.username = username;
    }

    public void setPassword(final String password) {
        this.password = password;
    }

    public boolean isImmediate() {
        return immediate;
    }

    public void setImmediate(final boolean immediate) {
        this.immediate = immediate;
    }

    public boolean isMandatory() {
        return mandatory;
    }

    public void setMandatory(final boolean mandatory) {
        this.mandatory = mandatory;
    }

    public int getPrefetchSize() {
        return prefetchSize;
    }

    public void setPrefetchSize(final int prefetchSize) {
        this.prefetchSize = prefetchSize;
    }

    public int getPrefetchCount() {
        return prefetchCount;
    }

    public void setPrefetchCount(final int prefetchCount) {
        this.prefetchCount = prefetchCount;
    }

    public boolean isNoLocal() {
        return noLocal;
    }

    public void setNoLocal(final boolean noLocal) {
        this.noLocal = noLocal;
    }

    public boolean isExclusiveConsumers() {
        return exclusiveConsumers;
    }

    public void setExclusiveConsumers(final boolean exclusiveConsumers) {
        this.exclusiveConsumers = exclusiveConsumers;
    }

    public void setConnectionFactory(final ConnectionFactory connectionFactory) {
        this.connectionFactory = connectionFactory;
    }

    public ConnectionFactory getConnectionFactory() {
        return this.connectionFactory;
    }
}