org.springframework.amqp.rabbit.connection.CachingConnectionFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.amqp.rabbit.connection.CachingConnectionFactory.java

Source

/*
 * Copyright 2002-2010 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.amqp.rabbit.connection;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.LinkedList;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.util.Assert;

import com.rabbitmq.client.Channel;

/**
 * NOTE: this ConnectionFactory implementation is considered <b>experimental</b> at this stage. There are concerns to be
 * addressed in relation to the statefulness of channels. Therefore, we recommend using {@link SingleConnectionFactory}
 * for now.
 * 
 * A {@link ConnectionFactory} implementation that returns the same Connections from all {@link #createConnection()}
 * calls, and ignores calls to {@link com.rabbitmq.client.Connection#close()} and caches
 * {@link com.rabbitmq.client.Channel}.
 * 
 * <p>
 * By default, only one single Session will be cached, with further requested Channels being created and disposed on
 * demand. Consider raising the {@link #setChannelCacheSize(int) "channelCacheSize" value} in case of a high-concurrency
 * environment.
 * 
 * <p>
 * <b>NOTE: This ConnectionFactory requires explicit closing of all Channels obtained form its shared Connection.</b>
 * This is the usual recommendation for native Rabbit access code anyway. However, with this ConnectionFactory, its use
 * is mandatory in order to actually allow for Channel reuse.
 * 
 * @author Mark Pollack
 * @author Mark Fisher
 * @author Dave Syer
 */
public class CachingConnectionFactory extends SingleConnectionFactory implements DisposableBean {

    private final Log logger = LogFactory.getLog(getClass());

    private int channelCacheSize = 1;

    private final LinkedList<ChannelProxy> cachedChannelsNonTransactional = new LinkedList<ChannelProxy>();

    private final LinkedList<ChannelProxy> cachedChannelsTransactional = new LinkedList<ChannelProxy>();

    private volatile boolean active = true;

    private ChannelCachingConnectionProxy targetConnection;

    /**
     * Create a new CachingConnectionFactory initializing the hostname to be the value returned from
     * InetAddress.getLocalHost(), or "localhost" if getLocalHost() throws an exception.
     */
    public CachingConnectionFactory() {
        super();
    }

    /**
     * Create a new CachingConnectionFactory given a host name.
     * 
     * @param hostName the host name to connect to
     */
    public CachingConnectionFactory(String hostName, int port) {
        super(hostName, port);
    }

    /**
     * Create a new CachingConnectionFactory given a host name.
     * 
     * @param hostName the host name to connect to
     */
    public CachingConnectionFactory(int port) {
        super(port);
    }

    /**
     * Create a new CachingConnectionFactory given a host name.
     * 
     * @param hostName the host name to connect to
     */
    public CachingConnectionFactory(String hostName) {
        super(hostName);
    }

    /**
     * Create a new CachingConnectionFactory for the given target ConnectionFactory.
     * 
     * @param rabbitConnectionFactory the target ConnectionFactory
     */
    public CachingConnectionFactory(com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory) {
        super(rabbitConnectionFactory);
    }

    public void setChannelCacheSize(int sessionCacheSize) {
        Assert.isTrue(sessionCacheSize >= 1, "Channel cache size must be 1 or higher");
        this.channelCacheSize = sessionCacheSize;
    }

    public int getChannelCacheSize() {
        return this.channelCacheSize;
    }

    private Channel getChannel(boolean transactional) {
        LinkedList<ChannelProxy> channelList = transactional ? this.cachedChannelsTransactional
                : this.cachedChannelsNonTransactional;
        Channel channel = null;
        synchronized (channelList) {
            if (!channelList.isEmpty()) {
                channel = channelList.removeFirst();
            }
        }
        if (channel != null) {
            if (logger.isTraceEnabled()) {
                logger.trace("Found cached Rabbit Channel");
            }
        } else {
            channel = getCachedChannelProxy(channelList, transactional);
        }
        return channel;
    }

    private ChannelProxy getCachedChannelProxy(LinkedList<ChannelProxy> channelList, boolean transactional) {
        Channel targetChannel = createBareChannel(transactional);
        if (logger.isDebugEnabled()) {
            logger.debug("Creating cached Rabbit Channel from " + targetChannel);
        }
        return (ChannelProxy) Proxy.newProxyInstance(ChannelProxy.class.getClassLoader(),
                new Class[] { ChannelProxy.class },
                new CachedChannelInvocationHandler(targetChannel, channelList, transactional));
    }

    private Channel createBareChannel(boolean transactional) {
        return this.targetConnection.createBareChannel(transactional);
    }

    @Override
    protected Connection doCreateConnection() {
        targetConnection = new ChannelCachingConnectionProxy(super.doCreateConnection());
        return targetConnection;
    }

    /**
     * Reset the Channel cache and underlying shared Connection, to be reinitialized on next access.
     */
    protected void reset() {
        this.active = false;
        synchronized (this.cachedChannelsNonTransactional) {
            for (ChannelProxy channel : cachedChannelsNonTransactional) {
                try {
                    channel.getTargetChannel().close();
                } catch (Throwable ex) {
                    logger.trace("Could not close cached Rabbit Channel", ex);
                }
            }
            this.cachedChannelsNonTransactional.clear();
        }
        this.active = true;
        super.reset();
        this.targetConnection = null;
    }

    @Override
    public String toString() {
        return "CachingConnectionFactory [channelCacheSize=" + channelCacheSize + ", host=" + this.getHost()
                + ", port=" + this.getPort() + ", active=" + active + "]";
    }

    private class CachedChannelInvocationHandler implements InvocationHandler {

        private volatile Channel target;

        private final LinkedList<ChannelProxy> channelList;

        private final Object targetMonitor = new Object();

        private final boolean transactional;

        public CachedChannelInvocationHandler(Channel target, LinkedList<ChannelProxy> channelList,
                boolean transactional) {
            this.target = target;
            this.channelList = channelList;
            this.transactional = transactional;
        }

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String methodName = method.getName();
            if (methodName.equals("txSelect") && !this.transactional) {
                throw new UnsupportedOperationException("Cannot start transaction on non-transactional channel");
            }
            if (methodName.equals("equals")) {
                // Only consider equal when proxies are identical.
                return (proxy == args[0]);
            } else if (methodName.equals("hashCode")) {
                // Use hashCode of Channel proxy.
                return System.identityHashCode(proxy);
            } else if (methodName.equals("toString")) {
                return "Cached Rabbit Channel: " + this.target;
            } else if (methodName.equals("close")) {
                // Handle close method: don't pass the call on.
                if (active) {
                    synchronized (this.channelList) {
                        if (this.channelList.size() < getChannelCacheSize()) {
                            logicalClose((ChannelProxy) proxy);
                            // Remain open in the channel list.
                            return null;
                        }
                    }
                }

                // If we get here, we're supposed to shut down.
                physicalClose();
                return null;
            } else if (methodName.equals("getTargetChannel")) {
                // Handle getTargetChannel method: return underlying Channel.
                return this.target;
            }
            try {
                synchronized (targetMonitor) {
                    if (this.target == null) {
                        this.target = createBareChannel(transactional);
                    }
                }
                return method.invoke(this.target, args);
            } catch (InvocationTargetException ex) {
                if (!this.target.isOpen()) {
                    // Basic re-connection logic...
                    logger.debug("Detected closed channel on exception.  Re-initializing: " + target);
                    synchronized (targetMonitor) {
                        if (!this.target.isOpen()) {
                            this.target = createBareChannel(transactional);
                        }
                    }
                }
                throw ex.getTargetException();
            }
        }

        /**
         * GUARDED by channelList
         * 
         * @param proxy the channel to close
         */
        private void logicalClose(ChannelProxy proxy) throws Exception {
            if (!this.target.isOpen()) {
                synchronized (targetMonitor) {
                    if (!this.target.isOpen()) {
                        this.target = null;
                        return;
                    }
                }
            }
            // Allow for multiple close calls...
            if (!this.channelList.contains(proxy)) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Returning cached Channel: " + this.target);
                }
                this.channelList.addLast(proxy);
            }
        }

        private void physicalClose() throws Exception {
            if (logger.isDebugEnabled()) {
                logger.debug("Closing cached Channel: " + this.target);
            }
            if (this.target == null) {
                return;
            }
            if (this.target.isOpen()) {
                synchronized (targetMonitor) {
                    if (this.target.isOpen()) {
                        this.target.close();
                    }
                    this.target = null;
                }
            }
        }

    }

    private class ChannelCachingConnectionProxy implements Connection, ConnectionProxy {

        private volatile Connection target;

        public ChannelCachingConnectionProxy(Connection target) {
            this.target = target;
        }

        private Channel createBareChannel(boolean transactional) {
            return target.createChannel(transactional);
        }

        public Channel createChannel(boolean transactional) {
            Channel channel = getChannel(transactional);
            return channel;
        }

        public void close() {
            target.close();
        }

        public boolean isOpen() {
            return target != null && target.isOpen();
        }

        public Connection getTargetConnection() {
            return target;
        }

        @Override
        public int hashCode() {
            return 31 + ((target == null) ? 0 : target.hashCode());
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            ChannelCachingConnectionProxy other = (ChannelCachingConnectionProxy) obj;
            if (target == null) {
                if (other.target != null)
                    return false;
            } else if (!target.equals(other.target))
                return false;
            return true;
        }

        @Override
        public String toString() {
            return "Shared Rabbit Connection: " + this.target;
        }

    }
}