/*
   Copyright The Narayana Authors
   SPDX short identifier: Apache-2.0
 */



package com.arjuna.ats.internal.jts.orbspecific;

import java.util.Hashtable;

import org.omg.CORBA.BAD_OPERATION;
import org.omg.CORBA.SystemException;
import org.omg.CosTransactions.Control;
import org.omg.CosTransactions.Coordinator;
import org.omg.CosTransactions.Terminator;
import org.omg.CosTransactions.Unavailable;

import com.arjuna.ArjunaOTS.ActiveThreads;
import com.arjuna.ArjunaOTS.ActiveTransaction;
import com.arjuna.ArjunaOTS.ArjunaTransaction;
import com.arjuna.ArjunaOTS.BadControl;
import com.arjuna.ArjunaOTS.Destroyed;
import com.arjuna.ArjunaOTS.UidCoordinator;
import com.arjuna.ats.arjuna.common.Uid;
import com.arjuna.ats.arjuna.coordinator.ActionStatus;
import com.arjuna.ats.arjuna.coordinator.BasicAction;
import com.arjuna.ats.internal.jts.ORBManager;
import com.arjuna.ats.internal.jts.orbspecific.coordinator.ArjunaTransactionImple;
import com.arjuna.ats.internal.jts.utils.Helper;
import com.arjuna.ats.jts.logging.jtsLogger;

/*
 * Although a transaction may have a timeout associated with it,
 * this can only happen for a top-level transaction. This, combined
 * with the fact that the default timeout is 0 means that many (most?)
 * transactions will not have a timeout. So, rather than increase the
 * size of all of the transaction objects, we keep the information
 * separate in the TransactionReaper. (Since it already needs to have this
 * information anyway this is no extra burden.) It also means that we can
 * support non-JBoss transactions: if we were to add a new method to the
 * control (get_timeout, say) then this would be Arjuna specific.
 */

/**
 * An implementation of CosTransactions::Control
 * 
 * @author Mark Little (mark@arjuna.com)
 * @version $Id: ControlImple.java 2342 2006-03-30 13:06:17Z  $
 * @since JTS 1.0.
 */

public class ControlImple extends com.arjuna.ArjunaOTS.ActionControlPOA
{

	/**
	 * Create a new instance with the specified parent.
	 */

	public ControlImple (Control parentCon, ArjunaTransactionImple parentTran)
	{
		if (jtsLogger.logger.isTraceEnabled()) {
            jtsLogger.logger.trace("ControlImple::ControlImple ( Control parentCon, "
                    + ((parentTran != null) ? parentTran.get_uid()
                    : Uid.nullUid()) + " )");
        }

		_theTerminator = null;
		_theCoordinator = null;
		_parentControl = parentCon;
		_transactionHandle = new ArjunaTransactionImple(_parentControl,
				parentTran);
		_theUid = _transactionHandle.get_uid();
		_transactionImpl = null;
		_myControl = null;
		_destroyed = false;

		/*
		 * Pass a pointer to the control to the transaction so it knows what the
		 * control is. We use this for transaction comparison and
		 * thread-to-context management.
		 */

		_transactionHandle.setControlHandle(this);

		addControl();
	}

	public void finalize () throws Throwable
	{
		if (jtsLogger.logger.isTraceEnabled()) {
            jtsLogger.logger.trace("ControlImple.finalize ()");
        }

		if (!_destroyed)
		{
			try
			{
				destroy();
			}
			catch (Exception e)
			{
			}
		}

		tidyup();

		/*
		 * Do this here rather than in tidyup so anyone else with a reference to
		 * this control can continue to determine the status of the transaction
		 * until the control is garbage collected.
		 */

		_theTerminator = null;
		_theCoordinator = null;

		_theUid = null;
	}

	/**
	 * Used for garbage collection so we can keep a list of controls and delete
	 * local ones.
	 */

	public Uid get_uid ()
	{
		return _theUid;
	}

	/**
	 * @return the transaction implementation.
	 */

	public final ArjunaTransactionImple getImplHandle ()
	{
		return _transactionHandle;
	}

	/**
	 * @return the CORBA Control object.
	 */

	public final synchronized Control getControl ()
	{
		/*
		 * If we have been committed then the reference will be null. There is
		 * no point in recreating it and in some cases (e.g., JacORB) this will
		 * in fact cause an exception to be thrown.
		 */

		if ((_myControl == null) && (!_destroyed))
		{
			ORBManager.getPOA().objectIsReady(this);

			_myControl = com.arjuna.ArjunaOTS.ActionControlHelper.narrow(ORBManager.getPOA().corbaReference(this));
		}

		/*
		 * In C++ we had to narrow to Control for some ORBs, despite the fact
		 * that an ArjunaControl is a Control. Does now seem to be necessary for
		 * Java.
		 * 
		 * return ControlHelper.narrow(_myControl);
		 */

		return _myControl;
	}

	public Terminator get_terminator () throws SystemException,
			org.omg.CosTransactions.Unavailable
	{
		if ((_transactionHandle != null) && (_theTerminator == null))
			createTransactionHandle();

		if (_theTerminator != null)
			return _theTerminator;
		else
			throw new Unavailable();
	}

	public Coordinator get_coordinator () throws SystemException,
			org.omg.CosTransactions.Unavailable
	{
		if ((_transactionHandle != null) && (_theCoordinator == null))
			createTransactionHandle();

		if (_theCoordinator != null)
			return _theCoordinator;
		else
			throw new Unavailable();
	}

	public void set_terminator (Terminator terminator) throws SystemException,
			org.omg.CosTransactions.Unavailable
	{
		throw new org.omg.CosTransactions.Unavailable();
	}

	public void set_coordinator (Coordinator coordinator)
			throws SystemException, org.omg.CosTransactions.Unavailable
	{
		throw new org.omg.CosTransactions.Unavailable();
	}

	public Control getParentControl () throws Unavailable, SystemException
	{
		if (_parentControl != null)
			return _parentControl;
		else
			return null;
	}

	/**
	 * destroy should only be called for remote Control objects. Destroy them
	 * locally by calling DESTROY_IMPL.
	 * 
	 * Since we assume that a factory will either be remote or local, we can
	 * destroy this object and rely upon the ORB to return an exception to
	 * subsequent clients which indicates they no longer have a valid reference.
	 */

	public synchronized void destroy () throws ActiveTransaction,
			ActiveThreads, BadControl, Destroyed, SystemException
	{
		if (jtsLogger.logger.isTraceEnabled()) {
            jtsLogger.logger.trace("Control::destroy called for "
                    + get_uid());
        }

		canDestroy();

		try
		{
			_destroyed = true;

			removeControl();

			/*
			 * We do a lazy connect to the ORB, so we may never have to do a
			 * disconnect either.
			 */

			if (_myControl != null)
			{
				ORBManager.getPOA().shutdownObject(this);
				_myControl = null;
			}

			/*
			 * If this is a proxy then there won't be a local transaction
			 * implementation.
			 */

			if (_transactionHandle != null)
			{
				_transactionHandle.setControlHandle(null); // for gc
				_transactionHandle = null;
			}

			tidyup();
		}
		catch (Exception e)
		{
			throw new BAD_OPERATION(
                    "ControlImple "
                            + jtsLogger.i18NLogger.get_orbspecific_destroyfailed()
							+ e);
		}
	}

	public ControlImple getParentImple ()
	{
		BasicAction parent = ((_transactionHandle != null) ? _transactionHandle.parent()
				: null);

		if (parent != null)
		{
			try
			{
				synchronized (ControlImple.allControls)
				{
					return (ControlImple) ControlImple.allControls.get(parent.get_uid());
				}
			}
			catch (Exception ex)
			{
				return null;
			}
		}
		else
			return null;
	}

	public String toString ()
	{
		return "ControlImple < " + get_uid() + " >";
	}

	public boolean equals (java.lang.Object obj)
	{
		if (obj instanceof ControlImple)
		{
			if (((ControlImple) obj).get_uid().equals(get_uid()))
				return true;
		}

		return false;
	}
	
	/**
	 * In the case that the transaction is terminated by the reaper then it will
	 * also be tidied up. This means that the internal handle to the real transaction
	 * instance will be nulled out. In that case we cache the status just before removing
	 * the handle and this method can be used to obtain it.
	 * 
	 * @return the final termination status of the transaction.
	 * @throws IllegalStateException thrown if the transaction is still available.
	 */
	
	public org.omg.CosTransactions.Status getFinalStatus () throws IllegalStateException
	{
	    if (getImplHandle() != null)
	        throw new IllegalStateException();
	    else
	        return _finalStatus;
	}

	protected synchronized void canDestroy () throws ActiveTransaction,
			ActiveThreads, BadControl, Destroyed, SystemException
	{
		canDestroy(true);
	}

	/**
	 * Generally we do not want to destroy the transaction if it is doing some
	 * work, or other threads are still registered with it. However, for some
	 * situations (e.g., the transaction reaper) we must terminate the
	 * transaction regardless.
	 */

	protected synchronized void canDestroy (boolean force)
			throws ActiveTransaction, ActiveThreads, BadControl, Destroyed,
			SystemException
	{
		if (jtsLogger.logger.isTraceEnabled()) {
            jtsLogger.logger.trace("Control::canDestroy ( "
                    + force + " ) called for " + get_uid());
        }

		if (_destroyed)
			throw new Destroyed();

		if (_transactionHandle != null) // not a proxy control.
		{
			if ((_transactionHandle.activeThreads() != 0) && (!force))
			{
				if (jtsLogger.logger.isTraceEnabled()) {
                    jtsLogger.logger.trace("ControlImple::canDestroy for "
                            + get_uid()
                            + " - transaction has "
                            + _transactionHandle.activeThreads()
                            + " active threads.");
                }

				throw new ActiveThreads();
			}

			boolean active = false;

			try
			{
				if ((force)
						|| ((_transactionHandle.status() == ActionStatus.CREATED)
								|| (_transactionHandle.status() == ActionStatus.ABORTED) || (_transactionHandle.status() == ActionStatus.COMMITTED)))
				{
					active = false;
				}
				else
					active = true; // might be committing, aborting, etc.
			}
			catch (Exception e)
			{
				active = true;
			}

			if (active)
			{
				if (jtsLogger.logger.isTraceEnabled()) {
                    jtsLogger.logger.trace("Control::canDestroy for "
                            + get_uid() + " - transaction active.");
                }

				throw new ActiveTransaction();
			}
		} // it is always safe to delete proxies.

		/*
		 * Got here, so it is either ok to destroy or the caller wants to force
		 * the destruction regardless.
		 */
	}

	/**
	 * This is used for implicit context propagation, and for Current.resume on
	 * remote transactions. In both cases we need to create a local control
	 * given a remove coordinator and terminator, but we can't create a
	 * transaction handle.
	 */

	protected ControlImple (Coordinator coordinator, Terminator terminator)
	{
		this(coordinator, terminator, null, null);
	}

	protected ControlImple (Coordinator coordinator, Terminator terminator, Uid uid)
	{
		this(coordinator, terminator, null, uid);
	}

	protected ControlImple (Coordinator coordinator, Terminator terminator, Control parentControl, Uid uid)
	{
		if (jtsLogger.logger.isTraceEnabled()) {
            jtsLogger.logger.trace("ControlImple::ControlImple (Coordinator, Terminator, Control, "
                    + uid + " )");
        }

		_theTerminator = terminator;
		_theCoordinator = coordinator;
		_parentControl = parentControl;
		_transactionHandle = null;
		_transactionImpl = null;
		_myControl = null;
		_destroyed = false;

		if (uid == null)
		{
			UidCoordinator uidCoord = Helper.getUidCoordinator(coordinator);

			if (uidCoord != null)
			{
				try
				{
					_theUid = Helper.getUid(uidCoord);
				}
				catch (Exception e)
				{
					/*
					 * Not an JBoss transaction, so allocate any Uid.
					 */

					_theUid = new Uid();
				}

				uidCoord = null;
			}
			else
				_theUid = new Uid();
		}
		else
			_theUid = uid;

		duplicateTransactionHandle(coordinator, terminator);

		addControl();
	}

	/**
	 * Protected constructor for inheritance. The derived classes are
	 * responsible for setting everything up, including adding the control to
	 * the list of controls and assigning the Uid variable.
	 */

	protected ControlImple ()
	{
		if (jtsLogger.logger.isTraceEnabled()) {
            jtsLogger.logger.trace("ControlImple::ControlImple ()");
        }

		_theTerminator = null;
		_theCoordinator = null;
		_parentControl = null;
		_transactionHandle = null;
		_theUid = Uid.nullUid();
		_transactionImpl = null;
		_myControl = null;
		_destroyed = false;
	}

	protected final void createTransactionHandle ()
	{
		if (jtsLogger.logger.isTraceEnabled()) {
            jtsLogger.logger.trace("ControlImple::createTransactionHandle ()");
        }

		/* Create/bind the 2 IDL interfaces to the same implementation */

		_transactionImpl = new com.arjuna.ArjunaOTS.ArjunaTransactionPOATie(
				_transactionHandle);

		ORBManager.getPOA().objectIsReady(_transactionImpl);

		ArjunaTransaction transactionReference = com.arjuna.ArjunaOTS.ArjunaTransactionHelper.narrow(ORBManager.getPOA().corbaReference(_transactionImpl));

		_theCoordinator = com.arjuna.ArjunaOTS.UidCoordinatorHelper.narrow(transactionReference);
		_theTerminator = org.omg.CosTransactions.TerminatorHelper.narrow(transactionReference);

		transactionReference = null;
	}

	protected final void duplicateTransactionHandle (Coordinator coord, Terminator term)
	{
		if (jtsLogger.logger.isTraceEnabled()) {
            jtsLogger.logger.trace("ControlImple::duplicateTransactionHandle ()");
        }

		_theCoordinator = coord;
		_theTerminator = term;
	}

	/**
	 * Transaction needs to call these methods to enable garbage collection to
	 * occur.
	 * 
	 * Note, we assume that one ContorlImple per transaction is maintained per address
	 * space, so that overwriting a previously added ControlImple for the same tx is
	 * not possible.
	 */

	protected boolean addControl ()
	{
		if (jtsLogger.logger.isTraceEnabled()) {
            jtsLogger.logger.trace("ControlImple::addControl ()");
        }

		try
		{
			synchronized (ControlImple.allControls)
			{
				ControlImple.allControls.put(get_uid(), this);
			}
		}
		catch (Exception ex)
		{
			return false;
		}

		return true;
	}

	protected boolean removeControl ()
	{
		if (jtsLogger.logger.isTraceEnabled()) {
            jtsLogger.logger.trace("ControlImple::removeControl ()");
        }

		try
		{
			synchronized (ControlImple.allControls)
			{
				ControlImple.allControls.remove(get_uid());
			}
		}
		catch (Exception ex)
		{
			return false;
		}

		return true;
	}

	/**
	 * No need to protect with mutex since only called from destroy (and
	 * destructor), which is protected with a mutex. Do not call directly
	 * without synchronizing.
	 */

	protected final void tidyup ()
	{
		if (jtsLogger.logger.isTraceEnabled()) {
            jtsLogger.logger.trace("ControlImple::tidyup ()");
        }

		_myControl = null;
		_parentControl = null;

		try
		{
			if (_transactionImpl != null)
			{
			    _finalStatus = _transactionImpl.get_status();
			    
				ORBManager.getPOA().shutdownObject(_transactionImpl);

				_transactionHandle = null;
				_transactionImpl = null;
			}

		}
		catch (Exception e)
		{
            jtsLogger.i18NLogger.warn_orbspecific_tidyfail("ControlImple.tidyup", e);
		}
	}
	
	/*
	 * Make private, with public accessor.
	 */

	public static Hashtable allControls = new Hashtable();

	protected Terminator _theTerminator;

	protected Coordinator _theCoordinator;

	protected Control _parentControl;

	protected ArjunaTransactionImple _transactionHandle;

	protected Uid _theUid;

	protected com.arjuna.ArjunaOTS.ActionControl _myControl;

	protected com.arjuna.ArjunaOTS.ArjunaTransactionPOATie _transactionImpl;

	protected boolean _destroyed;

	private org.omg.CosTransactions.Status _finalStatus = org.omg.CosTransactions.Status.StatusUnknown;
}