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.pool.BasePoolableObjectFactory;
import org.apache.commons.pool.impl.StackObjectPool;
import org.mule.api.MuleContext;
import org.mule.api.MuleException;
import org.mule.api.MuleMessage;
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.ReplyToHandler;
import org.mule.config.i18n.MessageFactory;
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;
    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;

    private static abstract class AmqpConnection {
        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.getConnection().createChannel();

                channel.addShutdownListener(new ShutdownListener() {
                    public void shutdownCompleted(final ShutdownSignalException sse) {
                        if (!sse.isInitiatedByApplication()) {
                            // 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);
                        }
                    }
                });

                channel.setReturnListener(amqpConnector.defaultReturnListener);

                channel.basicQos(amqpConnector.getPrefetchSize(), amqpConnector.getPrefetchCount(), false);

                return channel;
            } catch (final IOException ioe) {
                amqpConnector.handleException(new ConnectException(MessageFactory.createStaticMessage(
                        "Impossible to create new channels on connection: " + amqpConnector.getConnection()), ioe,
                        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();
        }
    }

    private 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;
        }

    }

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

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

        @Override
        public Object makeObject() throws Exception {
            return new ConnectorConnection(amqpConnector);
        }

        @Override
        public void destroyObject(final Object obj) throws Exception {
            ((ConnectorConnection) obj).getChannel().close();
        }
    }

    private 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 doDispose() {
        try {
            connectorConnectionPool.close();
        } catch (final Exception e) {
            logger.error("Can't close the connector connection pool", e);
        }
        connection = null;
        connectionFactory = null;
    }

    @Override
    public void doConnect() throws Exception {
        final List<Address> brokerAddresses = new ArrayList<Address>();
        brokerAddresses.add(new Address(host, port));
        addFallbackAddresses(brokerAddresses);
        connection = connectionFactory.newConnection(brokerAddresses.toArray(new Address[0]));

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

    private 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);
            }
        }
    }

    private 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), 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);
        }
    }

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

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

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

    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());
    }

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

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

    private 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) 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);

                    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);
                    }

                    String routingKey = AmqpEndpointUtil.getRoutingKey(outboundEndpoint);

                    // handle dispatching to default exchange
                    if ((StringUtils.isBlank(exchange)) && (StringUtils.isBlank(routingKey))) {
                        final String queueName = AmqpEndpointUtil.getQueueName(outboundEndpoint.getAddress());
                        if (StringUtils.isNotBlank(queueName)) {
                            routingKey = queueName;
                        }
                    }
                    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) throws IOException {
        if (getAckMode() == AckMode.MULE_AUTO) {
            channel.basicAck(amqpMessage.getEnvelope().getDeliveryTag(), false);
            if (logger.isDebugEnabled()) {
                logger.debug("Mule acknowledged message: " + amqpMessage + " on channel: " + channel);
            }
        }
    }

    public void closeChannel(final Channel channel) throws ConnectException {
        // FIXME remove when http://www.mulesoft.org/jira/browse/MULE-5290 is fixed
        if (!channel.isOpen()) {
            logger.warn(
                    "Attempting to close an already closed channel (probably due to http://www.mulesoft.org/jira/browse/MULE-5290)");
            return;
        }

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

            channel.close();

            if (logger.isDebugEnabled()) {
                logger.debug("Closed channel: " + channel);
            }
        } catch (final IOException ioe) {
            throw new ConnectException(MessageFactory.createStaticMessage("Error when closing channel: " + channel),
                    ioe, this);
        }
    }

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

    @Override
    public ReplyToHandler getReplyToHandler(final ImmutableEndpoint endpoint) {
        return new AmqpReplyToHandler(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 void setHost(final String host) {
        this.host = host;
    }

    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(ConnectionFactory connectionFactory) {
        this.connectionFactory = connectionFactory;
    }

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