/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2007 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.tm.jotm;

import java.util.EmptyStackException;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Stack;
import java.util.Vector;

import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.transaction.NotSupportedException;
import javax.transaction.SystemException;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;


import org.objectweb.jotm.Current;
import org.objectweb.jotm.TransactionContext;
import org.objectweb.jotm.TransactionFactory;
import org.objectweb.jotm.TransactionRecovery;
import org.objectweb.jotm.TransactionResourceManager;

import org.ow2.jonas.lib.util.Log;
import org.ow2.jonas.tm.Enlistable;
import org.ow2.jonas.tm.TransactionManager;
import org.ow2.jonas.tm.TxResourceManager;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

/**
 * Wrapper on the JOTM TransactionManager (Current)
 * This is necessary to track the begin() calls and implement
 * the additional methods defined in org.ow2.jonas.tm.TransactionManager.
 * @author durieuxp
 */
public class JOTMTransactionManager extends Current implements TransactionManager {

    private static Logger logger = Log.getLogger(Log.JONAS_SERVER_PREFIX);

    // Must be init at null, for clients that do not get UserTransaction
    private transient static JOTMTransactionManager unique = null;

    private transient ThreadLocal connectionListStack = new ThreadLocal();

    public JOTMTransactionManager(TransactionFactory tmfact) {
        super(tmfact);
        unique = this;
    }

    /**
     * Constructor for clients (See UserTramsactionFactory)
     */
    public JOTMTransactionManager() {
        super();
        unique = this;
    }

    public static JOTMTransactionManager getUnique() {
        return unique;
    }

    /**
     * We want to use our UserTransactionFactory here, that will
     * instanciate a JOTMTransactionManager.
     */
    public Reference getReference() throws NamingException {
        logger.log(BasicLevel.DEBUG, "");

        // create the reference
        Reference ref = new Reference(this.getClass().getName(), UserTransactionFactory.class.getName(), null);

        // Should be: getTransactionTimeout()
        int i = getDefaultTimeout();
        ref.add(new StringRefAddr("jotm.timeout", new String("" + i)));

        return ref;
    }

    /**
     * Creates a new transaction and associate it with the current thread.
     *
     * @exception NotSupportedException Thrown if the thread is already
     *    associated with a transaction. (nested transaction are not
     *    supported)
     *
     * @exception SystemException Thrown if the transaction manager
     *    encounters an unexpected error condition
     */
    public void begin() throws NotSupportedException, SystemException {
        super.begin();

        Stack curStack = (Stack) connectionListStack.get();
        if (curStack != null) {
            try {
                List list = (List) curStack.peek();

                if (list != null) {
                    List templist = new Vector(list); // avoid using an iterator on global list
                    for (Iterator it = templist.iterator(); it.hasNext();) {
                        Enlistable mce = (Enlistable) it.next();
                        mce.enlistConnection(getTransaction());
                    }
                } else {
                    logger.log(BasicLevel.DEBUG, "Current.begin called with null list");
                }
            } catch (EmptyStackException e) {
                logger.log(BasicLevel.DEBUG, "Current.begin called with empty stack");
            }
        }
    }

    /**
     * A Connection has been opened. Must keep it in a list in
     * case a transaction is started, in order to enlist it in the transaction.
     * This is done here. to avoid depending on a specific TM implementation.
     * @param mce The Connection to be enlisted
     */
    public void notifyConnectionOpen(Enlistable mce) {
        logger.log(BasicLevel.DEBUG, "");

        Stack curStack = (Stack) connectionListStack.get();
        if (curStack == null) {
            // no stack yet : create one
            connectionListStack.set(curStack = new Stack());
        }

        List list = null;
        try {
            list = (List) curStack.pop();
        } catch (EmptyStackException e) {
            // ignore: might happen if thread used by session bean
        }
        if (list == null) {
            // no list yet: create one
            list = new Vector(1);
        }

        // add connection to list
        list.add(mce);

        // push list into stack
        curStack.push(list);
    }

    /**
     * A Connection has been closed.
     * @param mce The Connection to be enlisted
     */
    public void notifyConnectionClose(Enlistable mce) {
        logger.log(BasicLevel.DEBUG, "");
        removeFromCurrentStack(mce);
    }

    /**
     * A Connection has been in error.
     * @param mce The Connection to be enlisted
     */
    public void notifyConnectionError(Enlistable mce) {
        logger.log(BasicLevel.DEBUG, "");
        removeFromCurrentStack(mce);
    }

    private void removeFromCurrentStack(Enlistable mce) {

        Stack curStack = (Stack) connectionListStack.get();
        if (curStack == null) {
            // if no transaction currently executing (no begin), no curStack
            // has been put on the eventListStack so just return
            return;
        }

        try {
            List list = (List) curStack.peek();
            if (list != null) {
                list.remove(mce);
            }
        } catch (EmptyStackException e) {
            // no list of RM Events are known, just return
        }
    }

    public void pushConnectionList(List cl) {
        logger.log(BasicLevel.DEBUG, "");
        Stack curStack = (Stack) connectionListStack.get();
        if (curStack == null) {
            // no stack yet : create one
            connectionListStack.set(curStack = new Stack());
        }
        curStack.push(cl);
    }

    public List popConnectionList() {
        logger.log(BasicLevel.DEBUG, "");
        Stack curStack = (Stack) connectionListStack.get();
        return (List) curStack.pop();
    }

    public boolean nonJotmTransactionContext() {
        TransactionContext transContext = getPropagationContext(false);
        if (transContext == null) {
            return false;
        }
        return ! transContext.isJotmCtx();
    }

    public boolean isRecoveryEnabled() {
        TransactionRecovery tr = getTransactionRecovery();
        if (tr == null || !getDefaultRecovery()) {
            return false;
        }
        return true;
    }

    public void registerResourceManager(String rmName, XAResource xares,
            String info, Properties p, TxResourceManager trm) throws XAException {

        TransactionRecovery tr = getTransactionRecovery();

        TransactionResourceManager mytrm = null;
        if (trm != null) {
            mytrm = new JOTMTransactionResourceManager(trm);
        }
        if (p != null) {
            tr.registerResourceManager(rmName, xares, info, p, mytrm);
        } else {
            tr.registerResourceManager(rmName, xares, info, mytrm);
        }
    }
}
