/*
 * $Header: /home/cvspublic/jakarta-slide/src/share/org/apache/slide/transaction/SlideTransactionManager.java,v 1.14 2004/07/28 09:34:33 ib Exp $
 * $Revision: 1.14 $
 * $Date: 2004/07/28 09:34:33 $
 *
 * ====================================================================
 *
 * Copyright 1999-2002 The Apache Software Foundation 
 *
 * 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.apache.slide.transaction;

import java.util.Hashtable;

import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.InvalidTransactionException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;

import org.apache.slide.util.Messages;
import org.apache.slide.util.logger.Logger;
import org.apache.slide.util.logger.SimpleLogger;

/**
 * JTA Transaction manager implementation.
 * <p>
 * Implementation notes :
 * <ul>
 * <li>Does not support nested transactions</li>
 * <li>No security</li>
 * </ul>
 *
 * @version $Revision: 1.14 $
 */
public final class SlideTransactionManager implements TransactionManager {


    // -------------------------------------------------------------- Constants


    protected static final String LOG_CHANNEL =
        SlideTransactionManager.class.getName();


    public static final int DEFAULT_TRANSACTION_TIMEOUT = 30;


    // ------------------------------------------------------------ Constructor


    // ----------------------------------------------------- Instance Variables


    /**
     * Transaction bindings thread id <-> transaction object.
     */
    private Hashtable bindings = new Hashtable();


    /**
     * Transaction bindings thread id <-> transaction timeout.
     */
    private Hashtable timeouts = new Hashtable();


    /**
     * Associated logger.
     */
    private Logger logger = new SimpleLogger();


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


    // --------------------------------------------------------- Public Methods


    /**
     * Get the logger associated with the transaction manager.
     */
    public Logger getLogger() {
        return logger;
    }


    /**
     * Set the logger of the transaction manager.
     */
    public void setLogger(Logger logger) {
        this.logger = logger;
    }


    // --------------------------------------------- TransactionManager Methods


    /**
     * Create a new transaction and associate it with the current thread.
     *
     * @exception NotSupportedException Thrown if the thread is already
     * associated with a transaction and the Transaction Manager
     * implementation does not support nested transactions.
     * @exception SystemException Thrown if the transaction manager encounters
     * an unexpected error condition.
     */
    public void begin()
        throws NotSupportedException, SystemException {

        Transaction currentTransaction = getTransaction();
        if (currentTransaction != null)
            throw new NotSupportedException();

        currentTransaction = new SlideTransaction(this);
        bindings.put(Thread.currentThread(), currentTransaction);

        if (logger.isEnabled(LOG_CHANNEL, Logger.DEBUG)) {
            String logMessage = Messages.format
                (SlideTransactionManager.class.getName() + ".begin",
                 currentTransaction.toString());
            logger.log(logMessage, LOG_CHANNEL, Logger.DEBUG);
        }

    }


    /**
     * Complete the transaction associated with the current thread. When this
     * method completes, the thread becomes associated with no transaction.
     * If the commit is terminated with an exception, the rollback should be
     * called, to do a proper clean-up.
     *
     * @exception RollbackException Thrown to indicate that the transaction
     * has been rolled back rather than committed.
     * @exception HeuristicMixedException Thrown to indicate that a heuristic
     * decision was made and that some relevant updates have been committed
     * while others have been rolled back.
     * @exception HeuristicRollbackException Thrown to indicate that a
     * heuristic decision was made and that some relevant updates have been
     * rolled back.
     * @exception SecurityException Thrown to indicate that the thread is not
     * allowed to commit the transaction.
     * @exception IllegalStateException Thrown if the current thread is not
     * associated with a transaction.
     * @exception SystemException Thrown if the transaction manager encounters
     * an unexpected error condition.
     */
    public void commit()
        throws RollbackException, HeuristicMixedException,
        HeuristicRollbackException, SecurityException, IllegalStateException,
        SystemException {

        Thread currentThread = Thread.currentThread();
        Transaction currentTransaction =
            (Transaction) bindings.get(currentThread);
        if (currentTransaction == null)
            throw new IllegalStateException();

        timeouts.remove(currentThread);

        if (logger.isEnabled(LOG_CHANNEL, Logger.DEBUG)) {
            String logMessage = Messages.format
                (SlideTransactionManager.class.getName() + ".commit",
                 currentTransaction.toString());
            logger.log(logMessage, LOG_CHANNEL, Logger.DEBUG);
        }

        try {
            currentTransaction.commit();
        } finally {
            bindings.remove(currentThread);
        }

    }


    /**
     * Roll back the transaction associated with the current thread. When
     * this method completes, the thread becomes associated with no
     * transaction.
     *
     * @exception SecurityException Thrown to indicate that the thread is not
     * allowed to commit the transaction.
     * @exception IllegalStateException Thrown if the current thread is not
     * associated with a transaction.
     * @exception SystemException Thrown if the transaction manager encounters
     * an unexpected error condition.
     */
    public void rollback()
        throws SecurityException, IllegalStateException, SystemException {

        Thread currentThread = Thread.currentThread();
        Transaction currentTransaction =
            (Transaction) bindings.remove(currentThread);
        if (currentTransaction == null)
            throw new IllegalStateException();

        timeouts.remove(currentThread);

        String logMessage = Messages.format
            (SlideTransactionManager.class.getName() + ".rollback",
             currentTransaction.toString());
        logger.log(logMessage, LOG_CHANNEL, Logger.DEBUG);

        currentTransaction.rollback();

    }


    /**
     * Modify the transaction associated with the current thread such that
     * the only possible outcome of the transaction is to roll back the
     * transaction.
     *
     * @exception IllegalStateException Thrown if the current thread is not
     * associated with a transaction.
     * @exception SystemException Thrown if the transaction manager encounters
     * an unexpected error condition.
     */
    public void setRollbackOnly()
        throws IllegalStateException, SystemException {

        Transaction currentTransaction = getTransaction();
        if (currentTransaction == null)
            throw new IllegalStateException();

        String logMessage = Messages.format
            (SlideTransactionManager.class.getName() + ".rollbackOnly",
             currentTransaction.toString());
        logger.log(logMessage, LOG_CHANNEL, Logger.INFO);

        currentTransaction.setRollbackOnly();

    }


    /**
     * Obtain the status of the transaction associated with the current thread.
     *
     * @exception SystemException Thrown if the transaction manager encounters
     * an unexpected error condition.
     * @return The transaction status. If no transaction is associated with
     * the current thread, this method returns the Status.NoTransaction value.
     */
    public int getStatus()
        throws SystemException {

        Transaction currentTransaction = getTransaction();
        if (currentTransaction == null)
            return Status.STATUS_NO_TRANSACTION;

        return currentTransaction.getStatus();

    }


    /**
     * Get the transaction object that represents the transaction context of
     * the calling thread.
     *
     * @return the Transaction object representing the transaction associated
     * with the calling thread.
     * @exception SystemException Thrown if the transaction manager encounters
     * an unexpected error condition.
     */
    public Transaction getTransaction()
        throws SystemException {
        return (Transaction) bindings.get(Thread.currentThread());
    }


    /**
     * Resume the transaction context association of the calling thread with
     * the transaction represented by the supplied Transaction object. When
     * this method returns, the calling thread is associated with the
     * transaction context specified.
     *
     * @param tobj The Transaction object that represents the transaction to
     * be resumed.
     * @exception InvalidTransactionException Thrown if the parameter
     * transaction object contains an invalid transaction.
     * @exception IllegalStateException Thrown if the thread is already
     * associated with another transaction.
     * @exception SystemException Thrown if the transaction manager encounters
     * an unexpected error condition.
     */
    public void resume(Transaction tobj)
        throws InvalidTransactionException, IllegalStateException,
        SystemException {

        if (getTransaction() != null)
            throw new IllegalStateException();

        if (tobj == null)
            throw new InvalidTransactionException();

        bindings.put(Thread.currentThread(), tobj);

    }


    /**
     * Suspend the transaction currently associated with the calling thread
     * and return a Transaction object that represents the transaction
     * context being suspended. If the calling thread is not associated with
     * a transaction, the method returns a null object reference. When this
     * method returns, the calling thread is associated with no transaction.
     *
     * @return Transaction object representing the suspended transaction.
     * @exception SystemException Thrown if the transaction manager encounters
     * an unexpected error condition.
     */
    public Transaction suspend()
        throws SystemException {

        Transaction currentTransaction = getTransaction();

        if (currentTransaction != null) {
            Thread currentThread = Thread.currentThread();
            bindings.remove(currentThread);
            timeouts.remove(currentThread);
        }

        return currentTransaction;

    }


    /**
     * Modify the value of the timeout value that is associated with the
     * transactions started by the current thread with the begin method.
     * <p>
     * If an application has not called this method, the transaction service
     * uses some default value for the transaction timeout.
     *
     * @param seconds The value of the timeout in seconds. If the value is
     * zero, the transaction service restores the default value.
     * @exception SystemException Thrown if the transaction manager encounters
     * an unexpected error condition.
     */
    public void setTransactionTimeout(int seconds)
        throws SystemException {

        timeouts.put(Thread.currentThread(), new Integer(seconds));

    }


}
