org.springframework.amqp.rabbit.support.PublisherCallbackChannelImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.amqp.rabbit.support.PublisherCallbackChannelImpl.java

Source

/*
 * Copyright 2002-2014 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.support;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodCallback;
import org.springframework.util.ReflectionUtils.MethodFilter;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.AMQP.Basic.RecoverOk;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.AMQP.Channel.FlowOk;
import com.rabbitmq.client.AMQP.Exchange.BindOk;
import com.rabbitmq.client.AMQP.Exchange.DeclareOk;
import com.rabbitmq.client.AMQP.Exchange.DeleteOk;
import com.rabbitmq.client.AMQP.Exchange.UnbindOk;
import com.rabbitmq.client.AMQP.Queue.PurgeOk;
import com.rabbitmq.client.AMQP.Tx.CommitOk;
import com.rabbitmq.client.AMQP.Tx.RollbackOk;
import com.rabbitmq.client.AMQP.Tx.SelectOk;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Command;
import com.rabbitmq.client.ConfirmListener;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.FlowListener;
import com.rabbitmq.client.GetResponse;
import com.rabbitmq.client.Method;
import com.rabbitmq.client.ReturnListener;
import com.rabbitmq.client.ShutdownListener;
import com.rabbitmq.client.ShutdownSignalException;

/**
 * Channel wrapper to allow a single listener able to handle
 * confirms from multiple channels.
 *
 * @author Gary Russell
 * @since 1.0.1
 *
 */
public class PublisherCallbackChannelImpl
        implements PublisherCallbackChannel, ConfirmListener, ReturnListener, ShutdownListener {

    private static final String[] METHODS_OF_INTEREST = new String[] { "getFlow", "flow", "flowBlocked",
            "basicConsume", "basicQos" };

    private static final MethodFilter METHOD_FILTER = new MethodFilter() {
        @Override
        public boolean matches(java.lang.reflect.Method method) {
            return ObjectUtils.containsElement(METHODS_OF_INTEREST, method.getName());
        }
    };

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

    private final Channel delegate;

    private final Map<String, Listener> listeners = new ConcurrentHashMap<String, Listener>();

    private final Map<Listener, SortedMap<Long, PendingConfirm>> pendingConfirms = new ConcurrentHashMap<PublisherCallbackChannel.Listener, SortedMap<Long, PendingConfirm>>();

    private final SortedMap<Long, Listener> listenerForSeq = new ConcurrentSkipListMap<Long, Listener>();

    private final java.lang.reflect.Method getFlowMethod;

    private final java.lang.reflect.Method flowMethod;

    private final java.lang.reflect.Method flowBlockedMethod;

    private final java.lang.reflect.Method basicConsumeFourArgsMethod;

    private final java.lang.reflect.Method basicQosTwoArgsMethod;

    public PublisherCallbackChannelImpl(Channel delegate) {
        delegate.addShutdownListener(this);
        this.delegate = delegate;

        // The following reflection is required to maintain comatibility with pre 3.3.x clients.
        final AtomicReference<java.lang.reflect.Method> getFlowMethod = new AtomicReference<java.lang.reflect.Method>();
        final AtomicReference<java.lang.reflect.Method> flowMethod = new AtomicReference<java.lang.reflect.Method>();
        final AtomicReference<java.lang.reflect.Method> flowBlockedMethod = new AtomicReference<java.lang.reflect.Method>();
        final AtomicReference<java.lang.reflect.Method> basicConsumeFourArgsMethod = new AtomicReference<java.lang.reflect.Method>();
        final AtomicReference<java.lang.reflect.Method> basicQosTwoArgsMethod = new AtomicReference<java.lang.reflect.Method>();
        ReflectionUtils.doWithMethods(delegate.getClass(), new MethodCallback() {

            @Override
            public void doWith(java.lang.reflect.Method method)
                    throws IllegalArgumentException, IllegalAccessException {
                if ("getFlow".equals(method.getName()) && method.getParameterTypes().length == 0
                        && FlowOk.class.equals(method.getReturnType())) {
                    getFlowMethod.set(method);
                } else if ("flow".equals(method.getName()) && method.getParameterTypes().length == 1
                        && boolean.class.equals(method.getParameterTypes()[0])
                        && FlowOk.class.equals(method.getReturnType())) {
                    flowMethod.set(method);
                } else if ("flowBlocked".equals(method.getName()) && method.getParameterTypes().length == 0
                        && boolean.class.equals(method.getReturnType())) {
                    flowBlockedMethod.set(method);
                } else if ("basicConsume".equals(method.getName()) && method.getParameterTypes().length == 4
                        && String.class.equals(method.getParameterTypes()[0])
                        && boolean.class.equals(method.getParameterTypes()[1])
                        && Map.class.equals(method.getParameterTypes()[2])
                        && Consumer.class.equals(method.getParameterTypes()[3])
                        && String.class.equals(method.getReturnType())) {
                    basicConsumeFourArgsMethod.set(method);
                } else if ("basicQos".equals(method.getName()) && method.getParameterTypes().length == 2
                        && int.class.equals(method.getParameterTypes()[0])
                        && boolean.class.equals(method.getParameterTypes()[1])
                        && void.class.equals(method.getReturnType())) {
                    basicQosTwoArgsMethod.set(method);
                }
            }

        }, METHOD_FILTER);
        this.getFlowMethod = getFlowMethod.get();
        this.flowMethod = flowMethod.get();
        this.flowBlockedMethod = flowBlockedMethod.get();
        this.basicConsumeFourArgsMethod = basicConsumeFourArgsMethod.get();
        this.basicQosTwoArgsMethod = basicQosTwoArgsMethod.get();
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // BEGIN PURE DELEGATE METHODS
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    public void addShutdownListener(ShutdownListener listener) {
        this.delegate.addShutdownListener(listener);
    }

    public void removeShutdownListener(ShutdownListener listener) {
        this.delegate.removeShutdownListener(listener);
    }

    public ShutdownSignalException getCloseReason() {
        return this.delegate.getCloseReason();
    }

    public void notifyListeners() {
        this.delegate.notifyListeners();
    }

    public boolean isOpen() {
        return this.delegate.isOpen();
    }

    public int getChannelNumber() {
        return this.delegate.getChannelNumber();
    }

    public Connection getConnection() {
        return this.delegate.getConnection();
    }

    public void close(int closeCode, String closeMessage) throws IOException {
        this.delegate.close(closeCode, closeMessage);
    }

    /**
     * @deprecated - removed in the 3.3.x client
     * @param active active.
     * @return FlowOk.
     * @throws IOException IOException.
     */
    @Deprecated
    public FlowOk flow(boolean active) throws IOException {
        if (this.flowMethod != null) {
            return (FlowOk) ReflectionUtils.invokeMethod(this.flowMethod, this.delegate, active);
        }
        throw new UnsupportedOperationException("'flow(boolean)' is not supported by the client library");
    }

    /**
     * @deprecated - removed in the 3.3.x client
     * @return FlowOk.
     */
    @Deprecated
    public FlowOk getFlow() {
        if (this.getFlowMethod != null) {
            return (FlowOk) ReflectionUtils.invokeMethod(this.getFlowMethod, this.delegate);
        }
        throw new UnsupportedOperationException("'getFlow()' is not supported by the client library");
    }

    /**
     * Added to the 3.3.x client
     * @since 1.3.3
     */
    public boolean flowBlocked() {
        if (this.flowBlockedMethod != null) {
            return (Boolean) ReflectionUtils.invokeMethod(this.flowBlockedMethod, this.delegate);
        }
        throw new UnsupportedOperationException("'flowBlocked()' is not supported by the client library");
    }

    public void abort() throws IOException {
        this.delegate.abort();
    }

    public void abort(int closeCode, String closeMessage) throws IOException {
        this.delegate.abort(closeCode, closeMessage);
    }

    public void addFlowListener(FlowListener listener) {
        this.delegate.addFlowListener(listener);
    }

    public boolean removeFlowListener(FlowListener listener) {
        return this.delegate.removeFlowListener(listener);
    }

    public void clearFlowListeners() {
        this.delegate.clearFlowListeners();
    }

    public Consumer getDefaultConsumer() {
        return this.delegate.getDefaultConsumer();
    }

    public void setDefaultConsumer(Consumer consumer) {
        this.delegate.setDefaultConsumer(consumer);
    }

    public void basicQos(int prefetchSize, int prefetchCount, boolean global) throws IOException {
        this.delegate.basicQos(prefetchSize, prefetchCount, global);
    }

    /**
     * Added to the 3.3.x client
     * @since 1.3.3
     */
    public void basicQos(int prefetchCount, boolean global) throws IOException {
        if (this.basicQosTwoArgsMethod != null) {
            ReflectionUtils.invokeMethod(this.basicQosTwoArgsMethod, this.delegate, prefetchCount, global);
            return;
        }
        throw new UnsupportedOperationException("'basicQos(int, boolean)' is not supported by the client library");
    }

    public void basicQos(int prefetchCount) throws IOException {
        this.delegate.basicQos(prefetchCount);
    }

    public void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
            throws IOException {
        this.delegate.basicPublish(exchange, routingKey, props, body);
    }

    public void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate,
            BasicProperties props, byte[] body) throws IOException {
        this.delegate.basicPublish(exchange, routingKey, mandatory, props, body);
    }

    public void basicPublish(String exchange, String routingKey, boolean mandatory, BasicProperties props,
            byte[] body) throws IOException {
        this.delegate.basicPublish(exchange, routingKey, mandatory, props, body);
    }

    public DeclareOk exchangeDeclare(String exchange, String type) throws IOException {
        return this.delegate.exchangeDeclare(exchange, type);
    }

    public DeclareOk exchangeDeclare(String exchange, String type, boolean durable) throws IOException {
        return this.delegate.exchangeDeclare(exchange, type, durable);
    }

    public DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete,
            Map<String, Object> arguments) throws IOException {
        return this.delegate.exchangeDeclare(exchange, type, durable, autoDelete, arguments);
    }

    public DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete,
            boolean internal, Map<String, Object> arguments) throws IOException {
        return this.delegate.exchangeDeclare(exchange, type, durable, autoDelete, internal, arguments);
    }

    public DeclareOk exchangeDeclarePassive(String name) throws IOException {
        return this.delegate.exchangeDeclarePassive(name);
    }

    public DeleteOk exchangeDelete(String exchange, boolean ifUnused) throws IOException {
        return this.delegate.exchangeDelete(exchange, ifUnused);
    }

    public DeleteOk exchangeDelete(String exchange) throws IOException {
        return this.delegate.exchangeDelete(exchange);
    }

    public BindOk exchangeBind(String destination, String source, String routingKey) throws IOException {
        return this.delegate.exchangeBind(destination, source, routingKey);
    }

    public BindOk exchangeBind(String destination, String source, String routingKey, Map<String, Object> arguments)
            throws IOException {
        return this.delegate.exchangeBind(destination, source, routingKey, arguments);
    }

    public UnbindOk exchangeUnbind(String destination, String source, String routingKey) throws IOException {
        return this.delegate.exchangeUnbind(destination, source, routingKey);
    }

    public UnbindOk exchangeUnbind(String destination, String source, String routingKey,
            Map<String, Object> arguments) throws IOException {
        return this.delegate.exchangeUnbind(destination, source, routingKey, arguments);
    }

    public com.rabbitmq.client.AMQP.Queue.DeclareOk queueDeclare() throws IOException {
        return this.delegate.queueDeclare();
    }

    public com.rabbitmq.client.AMQP.Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive,
            boolean autoDelete, Map<String, Object> arguments) throws IOException {
        return this.delegate.queueDeclare(queue, durable, exclusive, autoDelete, arguments);
    }

    public com.rabbitmq.client.AMQP.Queue.DeclareOk queueDeclarePassive(String queue) throws IOException {
        return this.delegate.queueDeclarePassive(queue);
    }

    public com.rabbitmq.client.AMQP.Queue.DeleteOk queueDelete(String queue) throws IOException {
        return this.delegate.queueDelete(queue);
    }

    public com.rabbitmq.client.AMQP.Queue.DeleteOk queueDelete(String queue, boolean ifUnused, boolean ifEmpty)
            throws IOException {
        return this.delegate.queueDelete(queue, ifUnused, ifEmpty);
    }

    public com.rabbitmq.client.AMQP.Queue.BindOk queueBind(String queue, String exchange, String routingKey)
            throws IOException {
        return this.delegate.queueBind(queue, exchange, routingKey);
    }

    public com.rabbitmq.client.AMQP.Queue.BindOk queueBind(String queue, String exchange, String routingKey,
            Map<String, Object> arguments) throws IOException {
        return this.delegate.queueBind(queue, exchange, routingKey, arguments);
    }

    public com.rabbitmq.client.AMQP.Queue.UnbindOk queueUnbind(String queue, String exchange, String routingKey)
            throws IOException {
        return this.delegate.queueUnbind(queue, exchange, routingKey);
    }

    public com.rabbitmq.client.AMQP.Queue.UnbindOk queueUnbind(String queue, String exchange, String routingKey,
            Map<String, Object> arguments) throws IOException {
        return this.delegate.queueUnbind(queue, exchange, routingKey, arguments);
    }

    public PurgeOk queuePurge(String queue) throws IOException {
        return this.delegate.queuePurge(queue);
    }

    public GetResponse basicGet(String queue, boolean autoAck) throws IOException {
        return this.delegate.basicGet(queue, autoAck);
    }

    public void basicAck(long deliveryTag, boolean multiple) throws IOException {
        this.delegate.basicAck(deliveryTag, multiple);
    }

    public void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException {
        this.delegate.basicNack(deliveryTag, multiple, requeue);
    }

    public void basicReject(long deliveryTag, boolean requeue) throws IOException {
        this.delegate.basicReject(deliveryTag, requeue);
    }

    public String basicConsume(String queue, Consumer callback) throws IOException {
        return this.delegate.basicConsume(queue, callback);
    }

    public String basicConsume(String queue, boolean autoAck, Consumer callback) throws IOException {
        return this.delegate.basicConsume(queue, autoAck, callback);
    }

    public String basicConsume(String queue, boolean autoAck, String consumerTag, Consumer callback)
            throws IOException {
        return this.delegate.basicConsume(queue, autoAck, consumerTag, callback);
    }

    /**
     * Added to the 3.3.x client
     * @since 1.3.3
     */
    public String basicConsume(String queue, boolean autoAck, Map<String, Object> arguments, Consumer callback)
            throws IOException {
        if (this.basicConsumeFourArgsMethod != null) {
            return (String) ReflectionUtils.invokeMethod(this.basicConsumeFourArgsMethod, this.delegate, queue,
                    autoAck, arguments, callback);
        }
        throw new UnsupportedOperationException(
                "'basicConsume(String, boolean, Map, Consumer)' is not supported by the client library");
    }

    public String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal,
            boolean exclusive, Map<String, Object> arguments, Consumer callback) throws IOException {
        return this.delegate.basicConsume(queue, autoAck, consumerTag, noLocal, exclusive, arguments, callback);
    }

    public void basicCancel(String consumerTag) throws IOException {
        this.delegate.basicCancel(consumerTag);
    }

    public RecoverOk basicRecover() throws IOException {
        return this.delegate.basicRecover();
    }

    public RecoverOk basicRecover(boolean requeue) throws IOException {
        return this.delegate.basicRecover(requeue);
    }

    @Deprecated
    public void basicRecoverAsync(boolean requeue) throws IOException {
        this.delegate.basicRecover(requeue);
    }

    public SelectOk txSelect() throws IOException {
        return this.delegate.txSelect();
    }

    public CommitOk txCommit() throws IOException {
        return this.delegate.txCommit();
    }

    public RollbackOk txRollback() throws IOException {
        return this.delegate.txRollback();
    }

    public com.rabbitmq.client.AMQP.Confirm.SelectOk confirmSelect() throws IOException {
        return this.delegate.confirmSelect();
    }

    public long getNextPublishSeqNo() {
        return this.delegate.getNextPublishSeqNo();
    }

    public boolean waitForConfirms() throws InterruptedException {
        return this.delegate.waitForConfirms();
    }

    public boolean waitForConfirms(long timeout) throws InterruptedException, TimeoutException {
        return this.delegate.waitForConfirms(timeout);
    }

    public void waitForConfirmsOrDie() throws IOException, InterruptedException {
        this.delegate.waitForConfirmsOrDie();
    }

    public void waitForConfirmsOrDie(long timeout) throws IOException, InterruptedException, TimeoutException {
        this.delegate.waitForConfirmsOrDie(timeout);
    }

    public void asyncRpc(Method method) throws IOException {
        this.delegate.asyncRpc(method);
    }

    public Command rpc(Method method) throws IOException {
        return this.delegate.rpc(method);
    }

    public void addConfirmListener(ConfirmListener listener) {
        this.delegate.addConfirmListener(listener);
    }

    public boolean removeConfirmListener(ConfirmListener listener) {
        return this.delegate.removeConfirmListener(listener);
    }

    public void clearConfirmListeners() {
        this.delegate.clearConfirmListeners();
    }

    public void addReturnListener(ReturnListener listener) {
        this.delegate.addReturnListener(listener);
    }

    public boolean removeReturnListener(ReturnListener listener) {
        return this.delegate.removeReturnListener(listener);
    }

    public synchronized void clearReturnListeners() {
        this.delegate.clearReturnListeners();
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // END PURE DELEGATE METHODS
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    public void close() throws IOException {
        if (this.delegate.isOpen()) {
            this.delegate.close();
        }
        generateNacksForPendingAcks("Channel closed by application");
    }

    private void generateNacksForPendingAcks(String cause) {
        synchronized (this.pendingConfirms) {
            for (Entry<Listener, SortedMap<Long, PendingConfirm>> entry : this.pendingConfirms.entrySet()) {
                Listener listener = entry.getKey();
                for (Entry<Long, PendingConfirm> confirmEntry : entry.getValue().entrySet()) {
                    try {
                        confirmEntry.getValue().setCause(cause);
                        handleNack(confirmEntry.getKey(), false);
                    } catch (IOException e) {
                        logger.error("Error delivering Nack afterShutdown", e);
                    }
                }
                listener.removePendingConfirmsReference(this, entry.getValue());
            }
            this.pendingConfirms.clear();
            this.listenerForSeq.clear();
        }
    }

    public synchronized SortedMap<Long, PendingConfirm> addListener(Listener listener) {
        Assert.notNull(listener, "Listener cannot be null");
        if (this.listeners.size() == 0) {
            this.delegate.addConfirmListener(this);
            this.delegate.addReturnListener(this);
        }
        if (!this.listeners.values().contains(listener)) {
            this.listeners.put(listener.getUUID(), listener);
            this.pendingConfirms.put(listener,
                    Collections.synchronizedSortedMap(new TreeMap<Long, PendingConfirm>()));
            if (logger.isDebugEnabled()) {
                logger.debug("Added listener " + listener);
            }
        }
        return this.pendingConfirms.get(listener);
    }

    public synchronized boolean removeListener(Listener listener) {
        Listener mappedListener = this.listeners.remove(listener.getUUID());
        boolean result = mappedListener != null;
        if (result && this.listeners.size() == 0) {
            this.delegate.removeConfirmListener(this);
            this.delegate.removeReturnListener(this);
        }
        Iterator<Entry<Long, Listener>> iterator = this.listenerForSeq.entrySet().iterator();
        while (iterator.hasNext()) {
            Entry<Long, Listener> entry = iterator.next();
            if (entry.getValue() == listener) {
                iterator.remove();
            }
        }
        this.pendingConfirms.remove(listener);
        return result;
    }

    //   ConfirmListener

    public void handleAck(long seq, boolean multiple) throws IOException {
        if (logger.isDebugEnabled()) {
            logger.debug(this.toString() + " PC:Ack:" + seq + ":" + multiple);
        }
        this.processAck(seq, true, multiple);
    }

    public void handleNack(long seq, boolean multiple) throws IOException {
        if (logger.isDebugEnabled()) {
            logger.debug(this.toString() + " PC:Nack:" + seq + ":" + multiple);
        }
        this.processAck(seq, false, multiple);
    }

    private void processAck(long seq, boolean ack, boolean multiple) {
        if (multiple) {
            /*
             * Piggy-backed ack - extract all Listeners for this and earlier
             * sequences. Then, for each Listener, handle each of it's acks.
             * Finally, remove the sequences from listenerForSeq.
             */
            synchronized (this.pendingConfirms) {
                Map<Long, Listener> involvedListeners = this.listenerForSeq.headMap(seq + 1);
                // eliminate duplicates
                Set<Listener> listeners = new HashSet<Listener>(involvedListeners.values());
                for (Listener involvedListener : listeners) {
                    // find all unack'd confirms for this listener and handle them
                    SortedMap<Long, PendingConfirm> confirmsMap = this.pendingConfirms.get(involvedListener);
                    if (confirmsMap != null) {
                        Map<Long, PendingConfirm> confirms = confirmsMap.headMap(seq + 1);
                        Iterator<Entry<Long, PendingConfirm>> iterator = confirms.entrySet().iterator();
                        while (iterator.hasNext()) {
                            Entry<Long, PendingConfirm> entry = iterator.next();
                            iterator.remove();
                            doHandleConfirm(ack, involvedListener, entry.getValue());
                        }
                    }
                }
                List<Long> seqs = new ArrayList<Long>(involvedListeners.keySet());
                for (Long key : seqs) {
                    this.listenerForSeq.remove(key);
                }
            }
        } else {
            Listener listener = this.listenerForSeq.remove(seq);
            if (listener != null) {
                PendingConfirm pendingConfirm = this.pendingConfirms.get(listener).remove(seq);
                if (pendingConfirm != null) {
                    doHandleConfirm(ack, listener, pendingConfirm);
                }
            } else {
                logger.error("No listener for seq:" + seq);
            }
        }
    }

    private void doHandleConfirm(boolean ack, Listener listener, PendingConfirm pendingConfirm) {
        try {
            if (listener.isConfirmListener()) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Sending confirm " + pendingConfirm);
                }
                listener.handleConfirm(pendingConfirm, ack);
            }
        } catch (Exception e) {
            logger.error("Exception delivering confirm", e);
        }
    }

    public void addPendingConfirm(Listener listener, long seq, PendingConfirm pendingConfirm) {
        SortedMap<Long, PendingConfirm> pendingConfirmsForListener = this.pendingConfirms.get(listener);
        Assert.notNull(pendingConfirmsForListener, "Listener not registered");
        synchronized (this.pendingConfirms) {
            pendingConfirmsForListener.put(seq, pendingConfirm);
        }
        this.listenerForSeq.put(seq, listener);
    }

    //  ReturnListener

    public void handleReturn(int replyCode, String replyText, String exchange, String routingKey,
            AMQP.BasicProperties properties, byte[] body) throws IOException {
        Object uuidObject = properties.getHeaders().get(RETURN_CORRELATION).toString();
        Listener listener = this.listeners.get(uuidObject);
        if (listener == null || !listener.isReturnListener()) {
            if (logger.isWarnEnabled()) {
                logger.warn("No Listener for returned message");
            }
        } else {
            listener.handleReturn(replyCode, replyText, exchange, routingKey, properties, body);
        }
    }

    // ShutdownListener

    @Override
    public void shutdownCompleted(ShutdownSignalException cause) {
        generateNacksForPendingAcks(cause.getMessage());
    }

    // Object

    @Override
    public int hashCode() {
        return this.delegate.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        return this.delegate.equals(obj);
    }

    @Override
    public String toString() {
        return "PublisherCallbackChannelImpl: " + this.delegate.toString();
    }

}