/*
 * The contents of this file are subject to the terms 
 * of the Common Development and Distribution License 
 * (the License).  You may not use this file except in
 * compliance with the License.
 * 
 * You can obtain a copy of the license at 
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing 
 * permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL 
 * Header Notice in each file and include the License file 
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.  
 * If applicable, add the following below the CDDL Header, 
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information: 
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 */

/*
 * Copyright 2004-2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

//----------------------------------------------------------------------------
//
// Module:      TimeoutManager.java
//
// Description: Transaction time-out manager.
//
// Product:     com.sun.jts.CosTransactions
//
// Author:      Simon Holdsworth
//
// Date:        March, 1997
//
// Copyright (c):   1995-1997 IBM Corp.
//
//   The source code for this program is not published or otherwise divested
//   of its trade secrets, irrespective of what has been deposited with the
//   U.S. Copyright Office.
//
//   This software contains confidential and proprietary information of
//   IBM Corp.
//----------------------------------------------------------------------------

package com.sun.jts.CosTransactions;

import java.util.*;

import org.omg.CosTransactions.*;
import com.sun.jts.jtsxa.XID;

import com.sun.jts.trace.*;

import java.util.logging.Logger;
import java.util.logging.Level;
import com.sun.logging.LogDomains;
import com.sun.jts.utils.LogFormatter;

/**
 * This class records state for timing out transactions, and runs a thread
 * which performs occasional checks to time out transactions.
 *
 * @version 0.01
 *
 * @author Simon Holdsworth, IBM Corporation
 *
 * @see
*/

//----------------------------------------------------------------------------
// CHANGE HISTORY
//
// Version By     Change Description
//   0.01  SAJH   Initial implementation.
//----------------------------------------------------------------------------

class TimeoutManager {
    /**
     * Constants which define the types of timeout possible.
     */
    static final int CANCEL_TIMEOUT   = 0;
    static final int NO_TIMEOUT       = 0;
    static final int ACTIVE_TIMEOUT   = 1;
    static final int IN_DOUBT_TIMEOUT = 2;

    /**
     * this attribute indicates whether initialisation has been started.
     */
    private static boolean initialised = false;

    private static Hashtable     pendingTimeouts = new Hashtable();
    private static Hashtable     indoubtTimeouts = new Hashtable();
    private static TimeoutThread timeoutThread = null;
    private static boolean       timeoutActive = false;
    private static boolean       quiescing = false;
    private static boolean       isSetTimeout = false;

	/*
		Logger to log transaction messages
	*/  
    static Logger _logger = LogDomains.getLogger(LogDomains.TRANSACTION_LOGGER);
    /**
     * Initialises the static state of the TimeoutManager class.
     *
     * @param
     *
     * @return
     *
     * @see
     */
    synchronized static void initialise() {

        // If already initialised, return immediately.

        if (initialised) {
            return;
        }

        initialised = true;

        // Start the timeout thread.

        if (!timeoutActive && timeoutThread == null) {
           //  timeoutThread = new TimeoutThread();
           //  timeoutThread.start();
            timeoutActive = true;
        }
    }


    static synchronized void initSetTimeout() {
	if (isSetTimeout)
	   return;
        isSetTimeout = true;
        timeoutThread = new TimeoutThread();
        timeoutThread.start();
    }

    /**
     * Sets the timeout for the transaction to the specified type and time in
     * seconds.
     * <p>
     * If the type is none, the timeout for the transaction is
     * cancelled, otherwise the current timeout for the transaction is modified
     * to be of the new type and duration.
     *
     * @param localTID     The local identifier for the transaction.
     * @param timeoutType  The type of timeout to establish.
     * @param seconds      The length of the timeout.
     *
     * @return  Indicates success of the operation.
     *
     * @see
     */
     static boolean setTimeout(Long localTID, int timeoutType,
                                           int seconds) {

        boolean result = true;

        // Modify the timeout to the required type and value.

        if (timeoutActive) {

            TimeoutInfo timeoutInfo = null;

            switch (timeoutType) {

            // If the new type is active or in_doubt, then create a
            // new TimeoutInfo if necessary, and set up the type and interval.

            case TimeoutManager.ACTIVE_TIMEOUT :
		 if (!isSetTimeout) {
		     initSetTimeout();
                 }
                 timeoutInfo = new TimeoutInfo();
                 timeoutInfo.expireTime  =
                    new Date().getTime() + seconds * 1000;
                 timeoutInfo.localTID    = localTID;
                 timeoutInfo.timeoutType = timeoutType;
                 pendingTimeouts.put(localTID,timeoutInfo);
                 break;
            case TimeoutManager.IN_DOUBT_TIMEOUT :
		 if (!isSetTimeout) {
		     initSetTimeout();
		     // isSetTimeout = true;
                 }
                timeoutInfo = new TimeoutInfo();
                timeoutInfo.expireTime  =
                    new Date().getTime() + seconds * 1000;
                timeoutInfo.localTID    = localTID;
                timeoutInfo.timeoutType = timeoutType;
                indoubtTimeouts.put(localTID,timeoutInfo);
                break;

            // For any other type, remove the timeout if there is one.

            default:
		if (!isSetTimeout)
		  break;
                   result = (pendingTimeouts.remove(localTID) != null);
		   if (!result)
                      result = (indoubtTimeouts.remove(localTID) != null);

                    // If the transaction service is quiescing and
                    // there are no more pending timeouts,
                    // deactivate timeout and stop the timeout thread.

                    if (quiescing && pendingTimeouts.isEmpty() && indoubtTimeouts.isEmpty()) {
                        timeoutThread.stop();
                        timeoutActive = false;
                        // pendingTimeouts = null;
                    }
                break;
            }
        } else {
            // If timeouts are not active, just return false.
            result = false;
        }

        return result;
    }

    /**
     * Takes appropriate action for a timeout.
     * <p>
     * The type fo timeout is given, and the transaction represented by the
     * Coordinator and its local identifier.
     * <p>
     * This method does not reference the TimeoutManager's state directly
     * and so does not need to be synchronized.
     *
     * @param localTID     The local identifier for the transaction.
     * @param timeoutType  The type of timeout.
     *
     * @return
     *
     * @see
     */
    static void timeoutCoordinator(Long localTID, int  timeoutType) {

        // Look up the Coordinator for the transaction.
        // If there is none, then the transaction has already gone.
        // Otherwise do something with the transaction.


        CoordinatorImpl coord = RecoveryManager.getLocalCoordinator(localTID);
        if (coord == null) {
            if(_logger.isLoggable(Level.FINER))
            {
                _logger.logp(Level.FINER,"TimeoutManager","timeoutCoordinator()",
		    "RecoveryManager.getLocalCoordinator() returned null,"+
		    "which means txn is done. Setting timeout type to CANCEL_TIMEOUT");
            }
            TimeoutManager.setTimeout(localTID,
                                      TimeoutManager.CANCEL_TIMEOUT,
                                      0);
        } else {
            synchronized (coord) {
                boolean[] isRoot = new boolean[1];

                switch (timeoutType) {

                // If active, then attempt to roll the transaction back.

                case TimeoutManager.ACTIVE_TIMEOUT :
                    if(_logger.isLoggable(Level.FINER))
            	    {
                        _logger.logp(Level.FINER,"TimeoutManager","timeoutCoordinator()",
                                     "TimeoutManager.timeoutCoordinator():case ACTIVE_TIMEOUT"+
                                     "RecoveryManager.getLocalCoordinator() returned non-null,"+
                                     "which means txn is still around. Marking for Rollback the"+
                                     "transaction...: GTID is : " + 
                                     ((TopCoordinator)coord).superInfo.globalTID.toString());
                    }
                    try {
                        // coord.rollback(true);
                        coord.rollback_only();
                    } catch (Throwable exc) {}
                    break;

                    // If in doubt, it must be a TopCoordinator.
                    // In that case replay_completion needs to be driven.
                    // This is done by telling the TopCoordinator to act as
                    // if in recovery.  The result is then used to
                    // determine what to do with the Coordinator.

                case TimeoutManager.IN_DOUBT_TIMEOUT :
                    if(_logger.isLoggable(Level.FINER))
                    {
                        _logger.logp(Level.FINER,"TimeoutManager","timeoutCoordinator()",
                                     "TimeoutManager.timeoutCoordinator():case IN_DOUBT_TIMEOUT"+
                                     "RecoveryManager.getLocalCoordinator() returned non-null,"+
                                     "which means txn is still around. Invoking recover(boolean)"+
                                     "on TopCoordinator...: GTID is: "+ 
                                     ((TopCoordinator)coord).superInfo.globalTID.toString());
                    }
                    Status state = ((TopCoordinator) coord).recover(isRoot);

                    if (state == Status.StatusUnknown) {
                    
                        // If the outcome is not currently known, we do
                        // nothing with the transaction, as we expect to
                        // eventually get an outcome from the parent.

                        // GDH put out warning in case this state
                        // continues for a long time.
                        _logger.log(Level.WARNING, "jts.transaction_resync_from_orginator_failed");

                    } else if (state == Status.StatusCommitted) {

                        // For committed or rolled back, proceed with
                        // completion of the transaction, regardless of whether
                        // it is the root or a subordinate. This will
                        // result in the removal of the in-doubt timeout.

                        try {
                            ((TopCoordinator)coord).commit();
                            if (isRoot[0]) {
                                ((TopCoordinator) coord).
                                    afterCompletion(state);
                            }
                        } catch (Throwable exc) {}
                    } else {
                        // By default, roll the transaction back.
                        try {
                            ((TopCoordinator) coord).rollback(true);
                            if (isRoot[0]) {
                                ((TopCoordinator) coord).
                                    afterCompletion(Status.StatusRolledBack);
                            }
                        } catch (Throwable exc) {}
                    }

                    break;

                default:
                    // Otherwise do nothing.
                    break;
                }
            }
        }
    }

    /**
     * Periodically checks the existing timeouts.
     * <p>
     * This is done to discover if any transactions have overrun their allotted
     * time.  Those which have are returned as an Enumeration.
     * <p>
     * Note that this method should not do anything that will cause a
     * synchronized method in the RecoveryManager to be called, as this could
     * cause a deadlock when RecoveryManager methods on other threads call
     * setTimeout.
     *
     * @param
     *
     * @return  The information for transactions which have timed out.
     *
     * @see
     */
     static Enumeration checkTimeouts() {
        if (!isSetTimeout)
           return null;

        Enumeration result = null;

        // When woken up, go through all current timeouts and identify those
        // which have expired.

        if (timeoutActive && ((pendingTimeouts.size() != 0) || (indoubtTimeouts.size() != 0))) {
            Vector timedOut = null;

            Enumeration timeouts = null;
          
            synchronized (pendingTimeouts) {
                timeouts = pendingTimeouts.elements();

                while (timeouts.hasMoreElements()) {

                    TimeoutInfo timeoutInfo = (TimeoutInfo)timeouts.nextElement();

                    // For each timeout in the list, check whether it has expired.
                    // If so, look up the Coordinator and roll it back.

                    if (new Date().getTime() > timeoutInfo.expireTime) {

                       // Add the TimeoutInfo to the queue of
                       //those that have timed out.

                       if (timedOut == null) {
                           timedOut = new Vector();
                       }

                       timedOut.addElement(timeoutInfo);
                    }
               }
            }

	    synchronized (indoubtTimeouts) {

                timeouts = indoubtTimeouts.elements();

                while (timeouts.hasMoreElements()) {

                    TimeoutInfo timeoutInfo = (TimeoutInfo)timeouts.nextElement();

                    // For each timeout in the list, check whether it has expired.
                    // If so, look up the Coordinator and roll it back.

                    if (new Date().getTime() > timeoutInfo.expireTime) {

                       // Add the TimeoutInfo to the queue of
                       //those that have timed out.

                       if (timedOut == null) {
                           timedOut = new Vector();
                       }

                       timedOut.addElement(timeoutInfo);
                    }
                }

            }
            // Enumerate the transactions which have timed out.

            if (timedOut != null) {
                result = timedOut.elements();
            }
           }

        // The remainder of the timeout processing is not carried out here
        // because we would get deadlocked with addCoordinator or
        // removeCoordinator that also update the timeout list.  Hence the
        // returned enumeration, which may be processed with
        // no concurrency control.

        return result;
    }

    /**
     * @return a set of in-doubt transaction ids.
     */
    static XID[] getInDoubtXids() {
        
       synchronized (indoubtTimeouts) {
        Vector inDoubtList = new Vector();
        
        Enumeration timeouts = indoubtTimeouts.elements();

        while (timeouts.hasMoreElements()) {

            TimeoutInfo timeoutInfo = (TimeoutInfo) timeouts.nextElement();

            // Look up the Coordinator for the transaction.
            // If there is none, then the transaction has already gone.
            // Otherwise do something with the transaction.
                
            CoordinatorImpl coord = 
                RecoveryManager.getLocalCoordinator(timeoutInfo.localTID);
                                
            if (coord != null) {
                XID xid = new XID();
                xid.copy(coord.getGlobalTID());
                inDoubtList.addElement(xid);
            }
        }
        
        return (XID[]) inDoubtList.toArray(new XID[] {});
       }
    }

    /**
     * Returns the amount of time left before the given transaction times out.
     *
     * @param localTID  The local identifier for the transaction.
     *
     * @return  The time left.  If there is no timeout for the transaction,
     *          this value will be negative.  If the timeout period has been
     *          exceeded, this value will be zero.
     *
     * @see
     */
    static long timeLeft(Long localTID) {

        TimeoutInfo timeoutInfo = (TimeoutInfo) pendingTimeouts.get(localTID);
        if (timeoutInfo == null)
            timeoutInfo = (TimeoutInfo) indoubtTimeouts.get(localTID);
        long result = -1;
        if (timeoutInfo != null) {
            result = timeoutInfo.expireTime - new Date().getTime();
            if (result < 0) {
                result = 0;
            }
        }

        return result;
    }

    /**
     * Informs the TimeoutManager that the transaction service
     * is being shut down. For immediate shutdown, the timeout thread is
     * stopped and all timeout information discarded.
     *
     * For quiesce, the timeout thread is stopped when there are no running
     * transactions left.
     *
     * @param immediate  Indicates whether to stop immediately.
     *
     * @return
     *
     * @see
     */
    static void shutdown(boolean immediate) {

        // For immediate, kill the timeout thread and throw
        // away all information. Also, if there are no pending
        // timeouts, there is nothing to quiesce so
        // shutdown immediately regardless.

        if (immediate ||
                pendingTimeouts == null || pendingTimeouts.isEmpty()) {
            if (timeoutThread != null) {
                timeoutThread.stop();
            }

            if (pendingTimeouts != null) {
                pendingTimeouts.clear();
            }

            pendingTimeouts = null;
            timeoutThread = null;
            timeoutActive = false;
        } else {
            quiescing = true;
        }
    }

    /**
     * Reports the contents of the TimeoutManager tables.
     *$Only required for debug.
     *
     * @param immediate  Indicates whether to stop immediately.
     *
     * @return
     *
     * @see
     */
    /*
    static void report() {

        // Report on pendingTimeouts.

        if (pendingTimeouts.size() > 0) {
			if(_logger.isLoggable(Level.FINER))
			{	
				_logger.logp(Level.FINER,"TimeoutManager","report()",
						"TimeoutManager.pendingTimeouts non-empty");
			}
            Enumeration keys = pendingTimeouts.keys();

            while (keys.hasMoreElements()) {
                Long localTID = (Long) keys.nextElement();
                TimeoutInfo timeInfo =
                    (TimeoutInfo) pendingTimeouts.get(localTID);
				if(_logger.isLoggable(Level.FINER))
				{
					_logger.logp(Level.FINER,"TimeoutManager","report()",
                			"localTid :"+localTID+" -> " + timeInfo);
				}
            }
        } else {
			if(_logger.isLoggable(Level.FINER))
			{
				_logger.logp(Level.FINER,"TimeoutManager","report()",
          		 		"TimeoutManager.pendingTimeouts empty");
			}
        }
    }
    */
}

/**
 * This class records information for a timeout for a transaction.
 *
 * @version 0.1
 *
 * @author Simon Holdsworth, IBM Corporation
 *
 * @see
 */

//----------------------------------------------------------------------------
// CHANGE HISTORY
//
// Version By     Change Description
//   0.1   SAJH   Initial implementation.
//----------------------------------------------------------------------------

class TimeoutInfo extends Object {
    Long localTID = null;
    long expireTime = 0;
    int  timeoutType = TimeoutManager.NO_TIMEOUT;
}

/**
 * This class represents a thread on which the TimeoutManager can perform
 * timeout checking.
 *
 * @version 0.01
 *
 * @author Simon Holdsworth, IBM Corporation
 *
 * @see
 */

//----------------------------------------------------------------------------
// CHANGE HISTORY
//
// Version By     Change Description
//   0.01  SAJH   Initial implementation.
//----------------------------------------------------------------------------

class TimeoutThread extends Thread {

    private int TIMEOUT_INTERVAL ;

    static Logger _logger = LogDomains.getLogger(LogDomains.TRANSACTION_LOGGER);
    /**
     * TimeoutThread constructor.
     * <p>
     * This sets the thread name, and sets the thread to be a daemon thread so
     * that it does not prevent the process from terminating.
     *
     * @param
     *
     * @return
     *
     * @see
     */
    TimeoutThread() {
        setName("JTS Timeout Thread"/*#Frozen*/);
        setDaemon(true);
        try{
            String timeout_interval = Configuration.getPropertyValue(Configuration.TIMEOUT_INTERVAL);
            if(timeout_interval!=null){
                TIMEOUT_INTERVAL= Integer.parseInt(timeout_interval);
		TIMEOUT_INTERVAL*=1000;
                if(TIMEOUT_INTERVAL<10000)
                    TIMEOUT_INTERVAL=10000;
            }
            else{
                TIMEOUT_INTERVAL=10000;
            }
        }catch(Exception e){
            TIMEOUT_INTERVAL=10000;
        }
    }

    /**
     * Performs timeout checking on a regular basis (every ten seconds or so).
     *
     * @param
     *
     * @return
     *
     * @see
     */
    public void run() {
        try {
            while (true) {

                // Sleep for a while between checks.

                Thread.sleep(TIMEOUT_INTERVAL);

                // Perform timeout checks, getting a list of timed-out
                // transactions.

                Enumeration timedOut = TimeoutManager.checkTimeouts();

                // Now we must go through the list, telling each
                // timed-out Coordinator to do something appropriate.

                if (timedOut != null) {
                    while (timedOut.hasMoreElements()) {
                        TimeoutInfo timeoutInfo =
                            (TimeoutInfo) timedOut.nextElement();

                        // Look up the Coordinator and tell it to roll back
                        // if it still exists. Note that we rely on the
                        // Coordinator calling removeCoordinator when it
                        // has finished, which will remove the timeout from 
                        // the list, and remove other associations as well.

                        TimeoutManager.
                            timeoutCoordinator(timeoutInfo.localTID,
                                               timeoutInfo.timeoutType);
                    }
                }
            }
        } catch (InterruptedException exc) {
			_logger.log(Level.INFO,"jts.time_out_thread_stopped");
        }
    }
}
