net.lshift.accent.AccentChannel.java Source code

Java tutorial

Introduction

Here is the source code for net.lshift.accent.AccentChannel.java

Source

/**
 * Copyright (C) 2011 LShift Ltd.
 *
 * 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 net.lshift.accent;

import com.rabbitmq.client.*;

import java.io.Closeable;
import java.io.IOException;
import java.util.*;

/**
 * Wrapper for a RabbitMQ channel providing the auto-reattempt logic.
 */
public class AccentChannel implements ConnectionListener, Closeable {
    private final AccentConnection connection;
    private final List<ChannelListener> setupListeners = new ArrayList<ChannelListener>();
    private Channel channel = null;
    private final Object channelStateLock = new Object();

    public AccentChannel(AccentConnection connection) {
        this.connection = connection;
        this.connection.addConnectionListener(this);
    }

    /**
     * Closes the given accent channel. Any persist consumers will be terminated.
     * @throws IOException
     */
    @Override
    public void close() throws IOException {
        this.connection.removeConnectionListener(this);

        // Close our underlying connection
        executeIfChannelValid(new ChannelCallback() {
            public void runWithChannel(Channel c) throws IOException {
                c.close();

                // Invalidate the stored channel
                channel = null;

                // Inform all listeners that the channel is lost
                for (ChannelListener cb : setupListeners) {
                    cb.channelLost();
                }
            }
        });
    }

    /**
     * Acquire a channel, and perform the given operation on it. If a channel is lost and reconnected, this operation will
     * not be repeated.
     *
     * @param cb the callback that will be provided the channel.
     */
    public void doOnceWithChannel(final ChannelCallback cb) {
        // Attempt the operation once with the current channel. We need to run this in the context of attemptOnce to ensure
        // the channel is recreated if the user operation fails.
        executeWhenChannelValid(cb);
    }

    /**
     * Add a listener that will be invoked each time the underlying channel is created or destroyed. Used mainly by
     * the various messaging components that need to do work on channel setup, and cleanup on channel loss.
     *
     * @param listener the callback that will be provided the channel.
     */
    public void addChannelSetupListener(final ChannelListener listener) {
        synchronized (channelStateLock) {
            setupListeners.add(listener);
        }

        executeIfChannelValid(new ChannelCallback() {
            public void runWithChannel(Channel c) throws IOException {
                listener.channelCreated(c);
            }
        });
    }

    /**
     * Removes a listener from this list of listeners informed of channel creation. For completeness, also notifies the
     * provided listener that the channel has been lost (to ensure that it performs any cleanup operations).
     * @param listener the listener to remove.
     */
    public void removeChannelSetupListener(ChannelListener listener) {
        synchronized (channelStateLock) {
            setupListeners.remove(listener);
        }

        listener.channelLost();
    }

    /**
     * Callback method from ConnectionListener that handles a connection becoming available.
     * @param connection the connection that is available.
     * @throws IOException if an exception occurs setting up the channel within the connection.
     */
    public void onConnected(final Connection connection) throws IOException {
        ShutdownListener shutdownListener = new ShutdownListener() {
            @Override
            public void shutdownCompleted(ShutdownSignalException cause) {
                synchronized (channelStateLock) {
                    // Release the channel
                    channel = null;

                    // Inform all listeners that the channel is lost
                    for (ChannelListener cb : setupListeners) {
                        cb.channelLost();
                    }

                    // Request that the connection thread rebuild us when it is ready
                    AccentChannel.this.connection.rebuild(AccentChannel.this);
                }
            }
        };

        // Attach a shutdown listener to the connection so if it goes completely, we'll be able to tear down
        // our channel.
        connection.addShutdownListener(shutdownListener);

        synchronized (channelStateLock) {
            channel = connection.createChannel();
            channel.addShutdownListener(shutdownListener);

            // Invoke the setup listeners
            for (ChannelListener cb : setupListeners) {
                cb.channelCreated(channel);
            }

            channelStateLock.notifyAll();
        }
    }

    /**
     * Executes a given operation only if the channel is currently valid.
     * @param t  the operation to execute.
     * @return an exception that was thrown, or null if the operation completed successfully.
     */
    public Exception executeIfChannelValid(final ChannelCallback t) {
        synchronized (channelStateLock) {
            if (channel != null) {
                return ExceptionUtils.attempt(new Thunk() {
                    @Override
                    public void run() throws IOException {
                        t.runWithChannel(channel);
                    }
                });
            } else {
                return null;
            }
        }
    }

    /**
     * Blocks until the channel becomes available, and then execute the given operation.
     * @param t the operation to execute.
     * @return any exception generated.
     */
    public Exception executeWhenChannelValid(final ChannelCallback t) {
        synchronized (channelStateLock) {
            while (channel == null) {
                try {
                    channelStateLock.wait();
                } catch (InterruptedException e) {
                    /* Ignore */ }
            }

            return ExceptionUtils.attempt(new Thunk() {
                @Override
                public void run() throws IOException {
                    t.runWithChannel(channel);
                }
            });
        }
    }
}