/** 
 * 
 * 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.store.bdbn;

import com.sleepycat.bdb.CurrentTransaction;
import com.sleepycat.db.Db;
import com.sleepycat.db.DbEnv;
import com.sleepycat.db.DbException;
import com.sleepycat.db.DbTxn;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.activemq.util.JMSExceptionHelper;

import javax.jms.JMSException;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.LinkedList;

/**
 * Some helper factory methods for creating default configured Berkeley DB objects
 *
 * @version $Revision: 1.1 $
 */
public class BDbHelper {
    private static final Log log = LogFactory.getLog(BDbHelper.class);
    private static ThreadLocal threadLocalTxn = new ThreadLocal();
    public static final int TRANSACTION_FLAGS = Db.DB_TXN_SYNC;
    private static DbEnv cachedEnvironment;

    public static DbEnv createEnvironment(File dir, boolean runRecovery) throws DbException, FileNotFoundException {
        DbEnv env = new DbEnv(0);

        // Open the Berkeley DB environment in transactional mode.
        int envFlags = Db.DB_INIT_TXN | Db.DB_INIT_LOCK | Db.DB_INIT_LOG | Db.DB_INIT_MPOOL |
                Db.DB_CREATE;

        if (runRecovery) {
            envFlags |= Db.DB_RECOVER;
        }
        env.open(dir.getPath(), envFlags, 0);
        return env;
    }

    public static Db open(DbEnv environment, String name, boolean isQueue) throws FileNotFoundException, DbException, JMSException {
        int flags = Db.DB_CREATE; // | Db.DB_AUTO_COMMIT
        Db db = new Db(environment, 0);

        if (isQueue) {
            db.setFlags(Db.DB_RENUMBER);
        }
        //  only use Db.DB_RECNUM for list tables as its a performance hog
        int type = Db.DB_BTREE;
        if (isQueue) {
            type = Db.DB_RECNO;
        }
        String databaseName = null;
        DbTxn transaction = createTransaction(environment);
        try {
            db.open(transaction, name, databaseName, type, flags, 0);
            transaction = commitTransaction(transaction);
        }
        finally {
            rollbackTransaction(transaction);
        }
        return db;
    }


    /**
     * @return the current thread local transaction that is in progress or null if there is no
     *         transaction in progress
     */
    public static DbTxn getTransaction() {
        LinkedList list = (LinkedList) threadLocalTxn.get();
        if (list != null && !list.isEmpty()) {
            return (DbTxn) list.getFirst();
        }
        return null;
    }

    /**
     * Pops off the current transaction from the stack
     */
    public static DbTxn popTransaction() {
        LinkedList list = (LinkedList) threadLocalTxn.get();
        if (list == null || list.isEmpty()) {
            log.warn("Attempt to pop transaction when no transaction in progress");
            return null;
        }
        else {
            return (DbTxn) list.removeFirst();
        }
    }

    /**
     * Sets the current transaction, possibly including nesting
     */
    public static void pushTransaction(DbTxn transaction) {
        LinkedList list = (LinkedList) threadLocalTxn.get();
        if (list == null) {
            list = new LinkedList();
            threadLocalTxn.set(list);
        }
        list.addLast(transaction);
    }

    public static int getTransactionCount() {
        LinkedList list = (LinkedList) threadLocalTxn.get();
        if (list != null) {
            return list.size();
        }
        return 0;
    }


    public static DbTxn createTransaction(DbEnv environment) throws DbException {
        // TODO remove dirty hack!
        cachedEnvironment = environment;
        CurrentTransaction currentTxn = CurrentTransaction.getInstance(environment);
        return currentTxn.beginTxn();
        /**
         // TODO temporary hack until BDB supports nested transactions
         if (getTransactionCount() == 0) {
         DbTxn transaction = environment.txnBegin(getTransaction(), TRANSACTION_FLAGS);
         pushTransaction(transaction);
         return transaction;
         }
         else {
         DbTxn transaction = getTransaction();
         pushTransaction(transaction);
         return transaction;
         }
         */
    }


    /**
     * Commit a transaction, throwing a JMSException if a failure occurs to avoid                                                TRA
     * rolling back
     *
     * @param transaction
     * @throws javax.jms.JMSException if the transaction could not be committed
     */
    public static DbTxn commitTransaction(DbTxn transaction) throws JMSException {
        try {
            CurrentTransaction currentTxn = CurrentTransaction.getInstance(cachedEnvironment);
            currentTxn.commitTxn();
            return null;
        }
        catch (DbException e) {
            throw JMSExceptionHelper.newJMSException("Failed to commit transaction: " + transaction + " in container: " + e, e);
        }

        /*
        // TODO temporary hack until BDB supports nested transactions
        if (getTransactionCount() == 1) {
            try {
                transaction.commit(TRANSACTION_FLAGS);
                return null;
            }
            catch (DbException e) {
                throw JMSExceptionHelper.newJMSException("Failed to commit transaction: " + transaction + " in container: " + e, e);
            }
            finally {
                popTransaction();
            }
        }
        else {
            popTransaction();
            return null;
        }
        */
    }

    /**
     * Rolls back the transaction, catching all exceptions as we only rollback
     * if we are about to throw an exception anyways
     *
     * @param transaction
     */
    public static void rollbackTransaction(DbTxn transaction) {
        if (transaction != null) {
            try {
                CurrentTransaction currentTxn = CurrentTransaction.getInstance(cachedEnvironment);
                currentTxn.abortTxn();
            }
            catch (DbException e) {
                log.warn("Cannot rollback transaction due to: " + e, e);
            }
        }
        /*
        // TODO temporary hack until BDB supports nested transactions
        if (transaction != null) {
            if (getTransactionCount() == 1) {
                try {
                    transaction.abort();
                }
                catch (DbException e) {
                    log.warn("Cannot rollback transaction due to: " + e, e);
                }
                finally {
                    popTransaction();
                }
            }
            else {
                popTransaction();
            }
        }
        */
    }
}
