/*
 * JBoss, Home of Professional Open Source
 * Copyright 2006, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags.
 * See the copyright.txt in the distribution for a full listing
 * of individual contributors.
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License, v. 2.1.
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * 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,
 * v.2.1 along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 *
 * (C) 2005-2006,
 * @author JBoss Inc.
 */
/*
 * Copyright (C) 2005,
 *
 * Arjuna Technologies Ltd,
 * Newcastle upon Tyne,
 * Tyne and Wear,
 * UK.
 *
 * $Id: XATerminatorImple.java 2342 2006-03-30 13:06:17Z  $
 */

package com.arjuna.ats.internal.jta.transaction.jts.jca;

import java.io.IOException;
import java.util.Stack;

import javax.transaction.*;
import javax.transaction.xa.*;

import com.arjuna.ats.arjuna.common.Uid;
import com.arjuna.ats.arjuna.coordinator.TwoPhaseOutcome;
import com.arjuna.ats.arjuna.coordinator.TxControl;
import com.arjuna.ats.arjuna.objectstore.ObjectStore;
import com.arjuna.ats.arjuna.state.InputObjectState;
import com.arjuna.ats.internal.jta.transaction.jts.subordinate.jca.TransactionImple;
import com.arjuna.ats.internal.jta.transaction.jts.subordinate.jca.coordinator.ServerTransaction;
import com.arjuna.ats.internal.jta.transaction.arjunacore.jca.SubordinationManager;
import com.arjuna.ats.internal.jta.transaction.arjunacore.jca.SubordinateTransaction;

/**
 * The XATerminator implementation.
 *
 * @author mcl
 *
 */

public class XATerminatorImple implements javax.resource.spi.XATerminator
{

	public void commit (Xid xid, boolean onePhase) throws XAException
	{
		try
		{
			SubordinateTransaction tx = SubordinationManager.getTransactionImporter().getImportedTransaction(xid);

			if (tx == null)
				throw new XAException(XAException.XAER_INVAL);

			if (tx.baseXid() != null)  // activate failed?
			{
				if (onePhase)
					tx.doOnePhaseCommit();
				else
					tx.doCommit();

				SubordinationManager.getTransactionImporter().removeImportedTransaction(xid);
			}
			else
				throw new XAException(XAException.XA_RETRY);
		}
        catch(RollbackException e)
        {
            SubordinationManager.getTransactionImporter().removeImportedTransaction(xid);
            XAException xaException = new XAException(XAException.XA_RBROLLBACK);
            xaException.initCause(e);
            throw xaException;
        }
		catch (XAException ex)
		{
			// resource hasn't had a chance to recover yet

			if (ex.errorCode != XAException.XA_RETRY)
			{
				SubordinationManager.getTransactionImporter().removeImportedTransaction(xid);
			}

			throw ex;
		}
		catch (final HeuristicCommitException ex)
		{
		    throw new XAException(XAException.XA_HEURCOM);
		}
		catch (HeuristicRollbackException ex)
		{
			throw new XAException(XAException.XA_HEURRB);
		}
		catch (HeuristicMixedException ex)
		{
			throw new XAException(XAException.XA_HEURMIX);
		}
		catch (final IllegalStateException ex)
		{
	            SubordinationManager.getTransactionImporter().removeImportedTransaction(xid);
	                  
		    throw new XAException(XAException.XAER_NOTA);
		}
		catch (SystemException ex)
		{
			SubordinationManager.getTransactionImporter().removeImportedTransaction(xid);

			throw new XAException(XAException.XAER_RMERR);
		}
	}

	public void forget (Xid xid) throws XAException
	{
		try
		{
			SubordinateTransaction tx = SubordinationManager.getTransactionImporter().getImportedTransaction(xid);

			if (tx == null)
				throw new XAException(XAException.XAER_INVAL);

			tx.doForget();
		}
		catch (Exception ex)
		{
			throw new XAException(XAException.XAER_RMERR);
		}
		finally
		{
			SubordinationManager.getTransactionImporter().removeImportedTransaction(xid);
		}
	}

	public int prepare (Xid xid) throws XAException
	{
		try
		{
			SubordinateTransaction tx = SubordinationManager.getTransactionImporter().getImportedTransaction(xid);

			if (tx == null)
				throw new XAException(XAException.XAER_INVAL);

			switch (tx.doPrepare())
			{
			case TwoPhaseOutcome.PREPARE_READONLY:
				SubordinationManager.getTransactionImporter().removeImportedTransaction(xid);

				return XAResource.XA_RDONLY;
			case TwoPhaseOutcome.PREPARE_NOTOK:
			    try
			    {
			        rollback(xid);
			    }
			    catch (final Throwable ex)
			    {
			        // if we failed to prepare then rollback should not fail!
			    }
			    
				SubordinationManager.getTransactionImporter().removeImportedTransaction(xid);

				throw new XAException(XAException.XA_RBROLLBACK);
			case TwoPhaseOutcome.PREPARE_OK:
				return XAResource.XA_OK;
			case TwoPhaseOutcome.INVALID_TRANSACTION:
			    throw new XAException(XAException.XAER_NOTA);
			default:
				throw new XAException(XAException.XA_RBOTHER);
			}
		}
		catch (XAException ex)
		{
			throw ex;
		}
	}

	public Xid[] recover (int flag) throws XAException
	{
		/*
		 * Requires going through the objectstore for the states of imported
		 * transactions. Our own crash recovery takes care of transactions imported
		 * via CORBA, Web Services etc.
		 */

		/*
		 * Requires going through the objectstore for the states of imported
		 * transactions. Our own crash recovery takes care of transactions imported
		 * via CORBA, Web Services etc.
		 */

		switch (flag)
		{
		case XAResource.TMSTARTRSCAN: // check the object store
			if (_recoveryStarted)
				throw new XAException(XAException.XAER_PROTO);
			else
				_recoveryStarted = true;
			break;
		case XAResource.TMENDRSCAN: // null op for us
			if (_recoveryStarted)
				_recoveryStarted = false;
			else
				throw new XAException(XAException.XAER_PROTO);
			return null;
		case XAResource.TMNOFLAGS:
			if (_recoveryStarted)
				break;
		default:
			throw new XAException(XAException.XAER_PROTO);
		}

		// if we are here, then check the object store

		Xid[] indoubt = null;

		try
		{
			ObjectStore objStore = new ObjectStore(TxControl.getActionStoreType());
			InputObjectState states = new InputObjectState();

			// only look in the JCA section of the object store

			if (objStore.allObjUids(ServerTransaction.getType(), states) && (states.notempty()))
			{
				Stack values = new Stack();
				boolean finished = false;

				do
				{
					Uid uid = new Uid(Uid.nullUid());

					try
					{
						uid.unpack(states);
					}
					catch (IOException ex)
					{
						ex.printStackTrace();

						finished = true;
					}

					if (uid.notEquals(Uid.nullUid()))
					{
						Transaction tx = SubordinationManager.getTransactionImporter().recoverTransaction(uid);

						values.push(tx);
					}
					else
						finished = true;

				} while (!finished);

				if (values.size() > 0)
				{
					int index = 0;

					indoubt = new Xid[values.size()];

					while (!values.empty())
					{
						TransactionImple id = (TransactionImple) values.pop();

						indoubt[index] = id.baseXid();

						index++;
					}
				}
			}
		}
		catch (Exception ex)
		{
			ex.printStackTrace();
		}

		return indoubt;
	}

	public void rollback (Xid xid) throws XAException
	{
		try
		{
			SubordinateTransaction tx = SubordinationManager.getTransactionImporter().getImportedTransaction(xid);

			if (tx == null)
				throw new XAException(XAException.XAER_INVAL);

			if (tx.baseXid() != null)
			{
				tx.doRollback();

				SubordinationManager.getTransactionImporter().removeImportedTransaction(xid);
			}
			else
				throw new XAException(XAException.XA_RETRY);
		}
		catch (XAException ex)
		{
			// resource hasn't had a chance to recover yet

			if (ex.errorCode != XAException.XA_RETRY)
			{
				SubordinationManager.getTransactionImporter().removeImportedTransaction(xid);
			}

			throw ex;
		}
		catch (HeuristicCommitException ex)
		{
			throw new XAException(XAException.XA_HEURCOM);
		}
		catch (final HeuristicRollbackException exx)
		{
		    throw new XAException(XAException.XA_HEURRB);
		}
		catch (HeuristicMixedException ex)
		{
			throw new XAException(XAException.XA_HEURMIX);
		}
		catch (final IllegalStateException ex)
		{
		    SubordinationManager.getTransactionImporter().removeImportedTransaction(xid);
		    
		    throw new XAException(XAException.XAER_NOTA);
		}
		catch (SystemException ex)
		{
			SubordinationManager.getTransactionImporter().removeImportedTransaction(xid);

			throw new XAException(XAException.XAER_RMERR);
		}
	}

	private boolean _recoveryStarted = false;

}
