/** 
 * 
 * Copyright 2004 Protique 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 org.activemq.broker.impl;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;

import javax.jms.JMSException;
import javax.naming.Context;
import javax.transaction.xa.XAException;

import org.activemq.broker.Broker;
import org.activemq.broker.BrokerAdmin;
import org.activemq.broker.BrokerClient;
import org.activemq.broker.ConsumerInfoListener;
import org.activemq.capacity.DelegateCapacityMonitor;
import org.activemq.io.util.MemoryBoundedObjectManager;
import org.activemq.io.util.MemoryBoundedQueueManager;
import org.activemq.jndi.ReadOnlyContext;
import org.activemq.message.ActiveMQDestination;
import org.activemq.message.ActiveMQMessage;
import org.activemq.message.ActiveMQXid;
import org.activemq.message.ConnectionInfo;
import org.activemq.message.ConsumerInfo;
import org.activemq.message.MessageAck;
import org.activemq.message.ProducerInfo;
import org.activemq.security.SecurityAdapter;
import org.activemq.service.DeadLetterPolicy;
import org.activemq.service.MessageContainerAdmin;
import org.activemq.service.MessageContainerManager;
import org.activemq.service.RedeliveryPolicy;
import org.activemq.service.Transaction;
import org.activemq.service.TransactionManager;
import org.activemq.service.boundedvm.DurableQueueBoundedMessageManager;
import org.activemq.service.boundedvm.TransientQueueBoundedMessageManager;
import org.activemq.service.boundedvm.TransientTopicBoundedMessageManager;
import org.activemq.service.impl.DurableTopicMessageContainerManager;
import org.activemq.store.PersistenceAdapter;
import org.activemq.store.PersistenceAdapterFactory;
import org.activemq.store.TransactionStore;
import org.activemq.store.vm.VMPersistenceAdapter;
import org.activemq.store.vm.VMTransactionManager;
import org.activemq.util.Callback;
import org.activemq.util.ExceptionTemplate;
import org.activemq.util.JMSExceptionHelper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import EDU.oswego.cs.dl.util.concurrent.ConcurrentHashMap;
import EDU.oswego.cs.dl.util.concurrent.CopyOnWriteArrayList;

/**
 * The default {@link Broker} implementation
 *
 * @version $Revision: 1.1.1.1 $
 */
public class DefaultBroker extends DelegateCapacityMonitor implements Broker, BrokerAdmin {

    private static final Log log = LogFactory.getLog(DefaultBroker.class);

    protected static final String PROPERTY_STORE_DIRECTORY = "activemq.store.dir";
    protected static final String PERSISTENCE_ADAPTER_FACTORY = "activemq.persistenceAdapterFactory";

    protected static final Class[] NEWINSTANCE_PARAMETER_TYPES = {File.class};

    private static final long DEFAULT_MAX_MEMORY_USAGE = 20 * 1024 * 1024; //20mb

    private PersistenceAdapter persistenceAdapter;
    private TransactionManager transactionManager;
    private MessageContainerManager[] containerManagers;
    private File tempDir;
    private MemoryBoundedObjectManager memoryManager;
    private MemoryBoundedQueueManager queueManager;
    private TransactionStore preparedTransactionStore;
    private final String brokerName;
    private final String brokerClusterName;
    private Map containerManagerMap;
    private CopyOnWriteArrayList consumerInfoListeners;
    private MessageContainerManager persistentTopicMCM;
    private MessageContainerManager transientTopicMCM;
    private MessageContainerManager transientQueueMCM;
    private DurableQueueBoundedMessageManager persistentQueueMCM;
    private SecurityAdapter securityAdapter;
    private RedeliveryPolicy redeliveryPolicy;
    private DeadLetterPolicy deadLetterPolicy;
    private AdvisorySupport  advisory;



    public DefaultBroker(String brokerName, String brokerClusterName, MemoryBoundedObjectManager memoryManager) {
        this.brokerName = brokerName;
        this.brokerClusterName = brokerClusterName;
        this.memoryManager = memoryManager;
        queueManager = new MemoryBoundedQueueManager(memoryManager);
        setDelegate(memoryManager);
        containerManagerMap = new ConcurrentHashMap();
        consumerInfoListeners = new CopyOnWriteArrayList();
        this.advisory = new AdvisorySupport(this);
    }

    public DefaultBroker(String brokerName, MemoryBoundedObjectManager memoryManager) {
        this(brokerName, "default", memoryManager);
    }

    public DefaultBroker(String brokerName, String cluserName) {
        this(brokerName, cluserName, new MemoryBoundedObjectManager("Broker Memory Manager", DEFAULT_MAX_MEMORY_USAGE));
    }

    public DefaultBroker(String brokerName) {
        this(brokerName, new MemoryBoundedObjectManager("Broker Memory Manager", DEFAULT_MAX_MEMORY_USAGE));
    }

    public DefaultBroker(String brokerName, String brokerClusterName, PersistenceAdapter persistenceAdapter) {
        this(brokerName, brokerClusterName, new MemoryBoundedObjectManager("Broker Memory Manager", DEFAULT_MAX_MEMORY_USAGE));
        this.persistenceAdapter = persistenceAdapter;
    }

    public DefaultBroker(String brokerName, PersistenceAdapter persistenceAdapter) {
        this(brokerName);
        this.persistenceAdapter = persistenceAdapter;
    }

    /**
     * Start this Service
     *
     * @throws JMSException
     */
    public void start() throws JMSException {
        if (redeliveryPolicy == null) {
            redeliveryPolicy = new RedeliveryPolicy();
        }
        if (deadLetterPolicy == null){
            deadLetterPolicy = new DeadLetterPolicy(this);
        }
        if (persistenceAdapter == null) {
            persistenceAdapter = createPersistenceAdapter();
        }
        persistenceAdapter.start();

        if (transactionManager == null) {
            preparedTransactionStore = persistenceAdapter.createTransactionStore();
            transactionManager = new VMTransactionManager(this, preparedTransactionStore);
        }
        transactionManager.start();

        // force containers to be created
        if (containerManagerMap.isEmpty()) {
            makeDefaultContainerManagers();
        }
        getContainerManagers();

        for (int i = 0; i < containerManagers.length; i++) {
            containerManagers[i].setDeadLetterPolicy(this.deadLetterPolicy);
            containerManagers[i].start();
        }
    }


    /**
     * stop this Service
     *
     * @throws JMSException
     */

    public void stop() throws JMSException {
        ExceptionTemplate template = new ExceptionTemplate();

        if (containerManagers != null) {
            for (int i = 0; i < containerManagers.length; i++) {
                final MessageContainerManager containerManager = containerManagers[i];
                template.run(new Callback() {
                    public void execute() throws Throwable {
                        containerManager.stop();
                    }
                });
            }
        }
        if (transactionManager != null) {
            template.run(new Callback() {
                public void execute() throws Throwable {
                    transactionManager.stop();
                }
            });
        }

        template.run(new Callback() {
            public void execute() throws Throwable {
                persistenceAdapter.stop();
            }
        });

        template.throwJMSException();
    }

    // Broker interface
    //-------------------------------------------------------------------------

    public void addClient(BrokerClient client, ConnectionInfo info) throws JMSException {
        if (securityAdapter != null) {
            securityAdapter.authorizeConnection(client, info);
        }
        advisory.addConnection(client,info);
    }

    public void removeClient(BrokerClient client, ConnectionInfo info) throws JMSException {
        if (transactionManager != null) {
            transactionManager.cleanUpClient(client);
        }
        advisory.removeConnection(client,info);
    }

    public void addMessageProducer(BrokerClient client, ProducerInfo info) throws JMSException {
        if (securityAdapter != null) {
            securityAdapter.authorizeProducer(client, info);
        }
        advisory.addProducer(client,info);
    }

    public void removeMessageProducer(BrokerClient client, ProducerInfo info) throws JMSException {
        advisory.removeProducer(client,info);
    }

    /**
     * Add an active message consumer
     */
    public void addMessageConsumer(BrokerClient client, ConsumerInfo info) throws JMSException {
        validateConsumer(info);
        if (securityAdapter != null) {
            securityAdapter.authorizeConsumer(client, info);
        }
        advisory.addAdvisory(client, info);
        MessageContainerManager[] array = getContainerManagers();
        for (int i = 0;i < array.length;i++) {
            array[i].addMessageConsumer(client, info);
        }
        fireConsumerInfo(client, info);
    }

    /**
     * remove an active message consumer
     */
    public void removeMessageConsumer(BrokerClient client, ConsumerInfo info) throws JMSException {
        validateConsumer(info);
        advisory.removeAdvisory(client, info);
        for (int i = 0;i < containerManagers.length;i++) {
            containerManagers[i].removeMessageConsumer(client, info);
        }
        fireConsumerInfo(client, info);
    }


    /**
     * send a message to the broker
     */
    public void sendMessage(BrokerClient client, ActiveMQMessage message) throws JMSException {
        checkValid();
        if (message.getJMSMessageID() == null && !message.getJMSActiveMQDestination().isAdvisory()) {
            throw new JMSException("No messageID specified for the Message");
        }
        associateTransaction(message);
        try {
            ActiveMQDestination destination = message.getJMSActiveMQDestination();
            if (destination.isComposite()) {
                boolean first = true;
                for (Iterator iter = destination.getChildDestinations().iterator();iter.hasNext();) {
                    ActiveMQDestination childDestination = (ActiveMQDestination) iter.next();
                    // lets shallow copy just in case
                    if (first) {
                        first = false;
                    }
                    else {
                        message = message.shallowCopy();
                    }
                    message.setJMSDestination(childDestination);
                    doMessageSend(client, message);
                }
            }
            else {
                if (destination.isTempDestinationAdvisory() && !client.isBrokerConnection()) {
                    advisory.processTempDestinationAdvisory(client,message);
                }
                doMessageSend(client, message);
            }
        }
        finally {
            disAssociateTransaction();
        }
    }

    /**
     * Acknowledge consumption of a message by the Message Consumer
     */
    public void acknowledgeMessage(BrokerClient client, MessageAck ack) throws JMSException {
        
        associateTransaction(ack);
        try {
	        for (int i = 0; i < containerManagers.length; i++) {
	            containerManagers[i].acknowledgeMessage(client, ack);
	        }
        } finally {
            disAssociateTransaction();
        }
        
    }

    public void deleteSubscription(String clientId, String subscriberName) throws JMSException {
        for (int i = 0; i < containerManagers.length; i++) {
            containerManagers[i].deleteSubscription(clientId, subscriberName);
        }
    }


    /**
     * Start a transaction.
     *
     * @see org.activemq.broker.Broker#startTransaction(org.activemq.broker.BrokerClient, java.lang.String)
     */
    public void startTransaction(BrokerClient client, String transactionId) throws JMSException {
        transactionManager.createLocalTransaction(client, transactionId);
    }

    public void commitTransaction(BrokerClient client, String transactionId) throws JMSException {
        try {
            Transaction transaction = transactionManager.getLocalTransaction(transactionId);
            transaction.commit(true);
        }
        catch (XAException e) {
            // TODO: I think the XAException should propagate all the way to the client.
            throw (JMSException) new JMSException(e.getMessage()).initCause(e);
        }
    }

    /**
     * rollback a transaction
     */
    public void rollbackTransaction(BrokerClient client, String transactionId) throws JMSException {
        try {
            Transaction transaction = transactionManager.getLocalTransaction(transactionId);
            transaction.rollback();
        }
        catch (XAException e) {
            // TODO: I think the XAException should propagate all the way to the client.
            throw (JMSException) new JMSException(e.getMessage()).initCause(e);
        }
    }

    /**
     * Starts an XA Transaction.
     *
     * @see org.activemq.broker.Broker#startTransaction(org.activemq.broker.BrokerClient, org.activemq.message.ActiveMQXid)
     */
    public void startTransaction(BrokerClient client, ActiveMQXid xid) throws XAException {
        transactionManager.createXATransaction(client, xid);
    }

    /**
     * Prepares an XA Transaciton.
     *
     * @see org.activemq.broker.Broker#prepareTransaction(org.activemq.broker.BrokerClient, org.activemq.message.ActiveMQXid)
     */
    public int prepareTransaction(BrokerClient client, ActiveMQXid xid) throws XAException {
        Transaction transaction = transactionManager.getXATransaction(xid);
        return transaction.prepare();
    }

    /**
     * Rollback an XA Transaction.
     *
     * @see org.activemq.broker.Broker#rollbackTransaction(org.activemq.broker.BrokerClient, org.activemq.message.ActiveMQXid)
     */
    public void rollbackTransaction(BrokerClient client, ActiveMQXid xid) throws XAException {
        Transaction transaction = transactionManager.getXATransaction(xid);
        transaction.rollback();
    }

    /**
     * Commit an XA Transaction.
     *
     * @see org.activemq.broker.Broker#commitTransaction(org.activemq.broker.BrokerClient, org.activemq.message.ActiveMQXid, boolean)
     */
    public void commitTransaction(BrokerClient client, ActiveMQXid xid, boolean onePhase) throws XAException {
        Transaction transaction = transactionManager.getXATransaction(xid);
        transaction.commit(onePhase);
    }

    /**
     * Gets the prepared XA transactions.
     *
     * @see org.activemq.broker.Broker#getPreparedTransactions(org.activemq.broker.BrokerClient)
     */
    public ActiveMQXid[] getPreparedTransactions(BrokerClient client) throws XAException {
        return transactionManager.getPreparedXATransactions();
    }
    
    


    // Properties
    //-------------------------------------------------------------------------

    /**
     * Get a temp directory - used for spooling
     *
     * @return a File ptr to the directory
     */
    public File getTempDir() {
        if (tempDir == null) {
            String dirName = System.getProperty("activemq.store.tempdir", "ActiveMQTemp");
            tempDir = new File(dirName);
        }
        return tempDir;
    }

    public String getBrokerName() {
        return brokerName;
    }

    /**
     * @return Returns the brokerClusterName.
     */
    public String getBrokerClusterName() {
        return brokerClusterName;
    }

    public void setTempDir(File tempDir) {
        this.tempDir = tempDir;
    }

    public MessageContainerManager[] getContainerManagers() {
        if (containerManagers == null) {
            containerManagers = createContainerManagers();
        }
        return containerManagers;
    }

    public Map getContainerManagerMap() {
        return containerManagerMap;
    }

    public void setContainerManagerMap(Map containerManagerMap) {
        this.containerManagerMap = containerManagerMap;
        this.containerManagers = null;
    }

    public PersistenceAdapter getPersistenceAdapter() {
        return persistenceAdapter;
    }

    public void setPersistenceAdapter(PersistenceAdapter persistenceAdapter) {
        this.persistenceAdapter = persistenceAdapter;
    }

    public TransactionManager getTransactionManager() {
        return transactionManager;
    }

    public void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public SecurityAdapter getSecurityAdapter() {
        return securityAdapter;
    }

    public void setSecurityAdapter(SecurityAdapter securityAdapter) {
        this.securityAdapter = securityAdapter;
    }

    public RedeliveryPolicy getRedeliveryPolicy() {
        return redeliveryPolicy;
    }

    public void setRedeliveryPolicy(RedeliveryPolicy redeliveryPolicy) {
        this.redeliveryPolicy = redeliveryPolicy;
    }

    public TransactionStore getPreparedTransactionStore() {
        return preparedTransactionStore;
    }

    public void setPreparedTransactionStore(TransactionStore preparedTransactionStore) {
        this.preparedTransactionStore = preparedTransactionStore;
    }
    
    /**
     * @return the DeadLetterPolicy
     */
    public DeadLetterPolicy getDeadLetterPolicy(){
        return deadLetterPolicy;
    }
    
    /**
     * set the dead letter policy
     * @param deadLetterPolicy
     */
    public void setDeadLetterPolicy(DeadLetterPolicy deadLetterPolicy){
        this.deadLetterPolicy = deadLetterPolicy;
    }

    /**
     * @return Returns the maximumMemoryUsage.
     */
    public long getMaximumMemoryUsage() {
        return memoryManager.getValueLimit();
    }

    /**
     * @param maximumMemoryUsage The maximumMemoryUsage to set.
     */
    public void setMaximumMemoryUsage(long maximumMemoryUsage) {
        this.memoryManager.setValueLimit(maximumMemoryUsage);
    }


    public Context getDestinationContext(Hashtable environment) {
        Map data = new ConcurrentHashMap();
        for (Iterator iter = containerManagerMap.entrySet().iterator(); iter.hasNext();) {
            Map.Entry entry = (Map.Entry) iter.next();
            String name = entry.getKey().toString();
            MessageContainerManager manager = (MessageContainerManager) entry.getValue();
            Context context = new ReadOnlyContext(environment, manager.getDestinations());
            data.put(name, context);
        }
        return new ReadOnlyContext(environment, data);
    }

    // Implementation methods
    //-------------------------------------------------------------------------


    protected void doMessageSend(BrokerClient client, ActiveMQMessage message) throws JMSException {
        if (securityAdapter != null) {
            securityAdapter.authorizeSendMessage(client, message);
        }
        ActiveMQDestination dest = message.getJMSActiveMQDestination();
        if (dest.isTopic()){
            if (message.isPersistent() && !dest.isTemporary()){
                persistentTopicMCM.sendMessage(client,message);
            }
            transientTopicMCM.sendMessage(client, message);
        }else {
            transientQueueMCM.sendMessage(client, message);
            persistentQueueMCM.sendMessage(client, message);
        }
    }

    /**
     * Factory method to create a default persistence adapter
     *
     * @return
     */
    protected PersistenceAdapter createPersistenceAdapter() throws JMSException {
        File directory = new File(getStoreDirectory());

        // lets use reflection to avoid runtime dependency on persistence libraries
        PersistenceAdapter answer = null;
        String property = System.getProperty(PERSISTENCE_ADAPTER_FACTORY);
        if (property != null) {
            answer = tryCreatePersistenceAdapter(property, directory, false);
        }
        if (answer == null) {
            answer = tryCreatePersistenceAdapter("org.activemq.broker.impl.DefaultPersistenceAdapterFactory", directory, true);
        }
        if (answer != null) {
            return answer;
        }
        else {
            log.warn("Default message store (journal+derby) could not be found in the classpath or property '" + PERSISTENCE_ADAPTER_FACTORY
                    + "' not specified so defaulting to use RAM based message persistence");
            return new VMPersistenceAdapter();
        }
    }

    protected PersistenceAdapter tryCreatePersistenceAdapter(String className, File directory, boolean ignoreErrors) throws JMSException {
        Class adapterClass = loadClass(className, ignoreErrors);
        if (adapterClass != null) {
            try {
                PersistenceAdapterFactory factory = (PersistenceAdapterFactory) adapterClass.newInstance();
                PersistenceAdapter answer = factory.createPersistenceAdapter(directory, memoryManager);
                log.info("Persistence adapter created using: " + className);
                return answer;
            }
            catch (IOException cause) {
                throw createInstantiateAdapterException(className, (Exception) cause);
            }
            catch (Throwable e) {
                if (!ignoreErrors) {
                    throw createInstantiateAdapterException(className, e);
                }
            }
        }
        return null;
    }

    protected JMSException createInstantiateAdapterException(String className, Throwable e) {
        return JMSExceptionHelper.newJMSException("Persistence adapter could not be created using: "
                + className + ". Reason: " + e, e);
    }

    /**
     * Tries to load the given class from the current context class loader or
     * class loader which loaded us or return null if the class could not be found
     */
    protected Class loadClass(String name, boolean ignoreErrors) throws JMSException {
        try {
            return Thread.currentThread().getContextClassLoader().loadClass(name);
        }
        catch (ClassNotFoundException e) {
            try {
                return getClass().getClassLoader().loadClass(name);
            }
            catch (ClassNotFoundException e2) {
                if (ignoreErrors) {
                    log.trace("Could not find class: " + name + " on the classpath");
                    return null;
                }
                else {
                    throw JMSExceptionHelper.newJMSException("Could not find class: " + name + " on the classpath. Reason: " + e, e);
                }
            }
        }
    }

    protected String getStoreDirectory() {
        return System.getProperty(PROPERTY_STORE_DIRECTORY, "ActiveMQ");
    }

    /**
     * Factory method to create the default container managers
     *
     * @return
     */
    protected MessageContainerManager[] createContainerManagers() {
        int size = containerManagerMap.size();
        MessageContainerManager[] answer = new MessageContainerManager[size];
        containerManagerMap.values().toArray(answer);
        return answer;
    }

    protected void makeDefaultContainerManagers() {
        transientTopicMCM = new TransientTopicBoundedMessageManager(queueManager);
        containerManagerMap.put("transientTopicContainer", transientTopicMCM);
        persistentTopicMCM = new DurableTopicMessageContainerManager(persistenceAdapter, redeliveryPolicy, deadLetterPolicy);
        containerManagerMap.put("persistentTopicContainer", persistentTopicMCM);
        persistentQueueMCM = new DurableQueueBoundedMessageManager(persistenceAdapter, queueManager, redeliveryPolicy, deadLetterPolicy);
        containerManagerMap.put("persistentQueueContainer", persistentQueueMCM);
        transientQueueMCM = new TransientQueueBoundedMessageManager(queueManager,redeliveryPolicy, deadLetterPolicy);
        containerManagerMap.put("transientQueueContainer", transientQueueMCM);
    }

    /**
     * Ensures the consumer is valid, throwing a meaningful exception if not
     *
     * @param info
     * @throws JMSException
     */
    protected void validateConsumer(ConsumerInfo info) throws JMSException {
        if (info.getConsumerId() == null) {
            throw new JMSException("No consumerId specified for the ConsumerInfo");
        }
    }

    protected void checkValid() throws JMSException {
        if (containerManagers == null) {
            throw new JMSException("This Broker has not yet been started. Ensure start() is called before invoking action methods");
        }
    }

    /**
     * Add a ConsumerInfoListener to the Broker
     *
     * @param l
     */
    public void addConsumerInfoListener(ConsumerInfoListener l) {
        consumerInfoListeners.add(l);
    }

    /**
     * Remove a ConsumerInfoListener from the Broker
     *
     * @param l
     */
    public void removeConsumerInfoListener(ConsumerInfoListener l) {
        consumerInfoListeners.remove(l);
    }

    protected void fireConsumerInfo(BrokerClient client, ConsumerInfo info) {
        for (Iterator i = consumerInfoListeners.iterator(); i.hasNext();) {
            ConsumerInfoListener l = (ConsumerInfoListener) i.next();
            l.onConsumerInfo(client, info);
        }
    }

    /**
     * @return the MessageContainerManager for durable topics
     */
    public MessageContainerManager getPersistentTopicContainerManager() {
        return persistentTopicMCM;
    }

    /**
     * @return the MessageContainerManager for transient topics
     */
    public MessageContainerManager getTransientTopicContainerManager() {
        return transientTopicMCM;
    }

    /**
     * @return the MessageContainerManager for persistent queues
     */
    public MessageContainerManager getPersistentQueueContainerManager() {
        return persistentQueueMCM;
    }

    /**
     * @return the MessageContainerManager for transient queues
     */
    public MessageContainerManager getTransientQueueContainerManager() {
        return transientQueueMCM;
    }

    /**
     * @see org.activemq.broker.Broker#getBrokerAdmin()
     */
    public BrokerAdmin getBrokerAdmin() {
        return this;
    }

    public void createMessageContainer(ActiveMQDestination dest) throws JMSException {
        for (int i = 0; i < containerManagers.length; i++) {
            containerManagers[i].createMessageContainer(dest);
        }
    }

    public void destoryMessageContainer(ActiveMQDestination dest) throws JMSException {
        for (int i = 0; i < containerManagers.length; i++) {
            containerManagers[i].destroyMessageContainer(dest);
        }
    }

    public MessageContainerAdmin getMessageContainerAdmin(ActiveMQDestination dest) throws JMSException {
        for (int i = 0; i < containerManagers.length; i++) {
            Map messageContainerAdmins = containerManagers[i].getMessageContainerAdmins();
            MessageContainerAdmin mca = (MessageContainerAdmin) messageContainerAdmins.get(dest);
            if( mca != null ) {
                return mca;
            }
        }
        return null;
    }

    /**
     * @throws JMSException
     * @see org.activemq.broker.BrokerAdmin#listDestinations()
     */
    public MessageContainerAdmin[] listMessageContainerAdmin() throws JMSException {
        
        ArrayList l = new ArrayList();
        for (int i = 0; i < containerManagers.length; i++) {
            Map messageContainerAdmins = containerManagers[i].getMessageContainerAdmins();
            for (Iterator iter = messageContainerAdmins.values().iterator(); iter.hasNext();) {
                MessageContainerAdmin mca = (MessageContainerAdmin) iter.next();
                l.add(mca);
            }
        }
        
        MessageContainerAdmin answer[] = new MessageContainerAdmin[l.size()];
        l.toArray(answer);
        return answer;
    }
    
    
    /**
     * Add a message to a dead letter queue
     * @param deadLetterName
     * @param message
     * @throws JMSException
     */
    public void sendToDeadLetterQueue(String deadLetterName,ActiveMQMessage message) throws JMSException{
        if (persistentQueueMCM != null){
            persistentQueueMCM.sendToDeadLetterQueue(deadLetterName, message);
            log.debug(message + " sent to DLQ: " + deadLetterName);
        }  
    }

    /**
     * send a message to the broker within a transaction
    public void sendTransactedMessage(final BrokerClient client, final String transactionId, final ActiveMQMessage message) throws JMSException {
        getTransactionFor(message).addPostCommitTask(new SendMessageTransactionTask(client, message));
    }
     */
    
    /**
     * Acknowledge consumption of a message within a transaction
    public void acknowledgeTransactedMessage(final BrokerClient client, final String transactionId, final MessageAck ack) throws JMSException {
        Transaction transaction;
        if (ack.isXaTransacted()) {
            try {
                transaction = transactionManager.getXATransaction(new ActiveMQXid(transactionId));
            }
            catch (XAException e) {
                throw (JMSException) new JMSException(e.getMessage()).initCause(e);
            }
        }
        else {
            transaction = transactionManager.getLocalTransaction(transactionId);
        }
        transaction.addPostCommitTask(new MessageAckTransactionTask(client, ack));
        transaction.addPostRollbackTask(new RedeliverMessageTransactionTask(client, ack));

        // we need to tell the dispatcher that we can now accept another message
        // even though we don't really ack the message until the commit
        // this is because if we have a prefetch value of 1, we can never consume 2 messages
        // in a transaction, since the ack for the first message never arrives until the commit
        for (int i = 0; i < containerManagers.length; i++) {
            containerManagers[i].acknowledgeTransactedMessage(client, transactionId, ack);
        }
    }
     */


    /**
     * @param message
     * @return
     * @throws JMSException
    private Transaction getTransactionFor(ActiveMQMessage message) throws JMSException {
        String transactionId = message.getTransactionId();        
        if (message.isXaTransacted()) {
            try {
                return transactionManager.getXATransaction(new ActiveMQXid(transactionId));
            }
            catch (XAException e) {
                throw (JMSException) new JMSException(e.getMessage()).initCause(e);
            }
        }
        return transactionManager.getLocalTransaction(transactionId);
    }


    public void acknowledgeMessageRecover(MessageAck ack) {
    }
    public void sendMessageRecover(ActiveMQMessage message) throws JMSException {
    }
     */

    /**
     * Associates a Transaction with the current thread.  Once this call is finished,
     * the Transactio ncan be obtained via TransactionManager.getContexTransaction().
     * @param message
     * @throws JMSException
     */
    private final void associateTransaction(ActiveMQMessage message) throws JMSException {
        Transaction transaction;
        if( message.isPartOfTransaction() ) {
            if (message.isXaTransacted()) {
                try {
                    transaction = transactionManager.getXATransaction((ActiveMQXid) message.getTransactionId());
                }
                catch (XAException e) {
                    throw (JMSException) new JMSException(e.getMessage()).initCause(e);
                }
            } else {
                transaction = transactionManager.getLocalTransaction((String) message.getTransactionId());
            }
            
        } else {
            transaction = null;
        }                
        TransactionManager.setContexTransaction(transaction);
    }

    private void disAssociateTransaction() {
        TransactionManager.setContexTransaction(null);
    }
    
    /**
     * Associates a Transaction with the current thread.  Once this call is finished,
     * the Transactio ncan be obtained via TransactionManager.getContexTransaction().
     * @param ack
     * @throws JMSException
     */
    private void associateTransaction(MessageAck ack) throws JMSException {
        Transaction transaction;
        if( ack.isPartOfTransaction() ) {
            if (ack.isXaTransacted()) {
                try {
                    transaction = transactionManager.getXATransaction((ActiveMQXid) ack.getTransactionId());
                }
                catch (XAException e) {
                    throw (JMSException) new JMSException(e.getMessage()).initCause(e);
                }
            } else {
                transaction = transactionManager.getLocalTransaction((String) ack.getTransactionId());
            }
            
        } else {
            transaction = null;
        }                
        TransactionManager.setContexTransaction(transaction);
    }

    /**
     * @return Returns the memoryManager.
     */
    public MemoryBoundedObjectManager getMemoryManager() {
        return memoryManager;
    }


    /**
     * @return Returns the queueManager.
     */
    public MemoryBoundedQueueManager getQueueManager() {
        return queueManager;
    }

    
    

}
