/*
 * Copyright (c) 2002-2009 "Neo Technology,"
 *     Network Engine for Objects in Lund AB [http://neotechnology.com]
 *
 * This file is part of Neo4j.
 * 
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.neo4j.kernel.impl.transaction;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;

import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
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 javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;

import org.neo4j.graphdb.TransactionFailureException;
import org.neo4j.kernel.impl.core.KernelPanicEventGenerator;
import org.neo4j.kernel.impl.transaction.xaframework.XaResource;
import org.neo4j.kernel.impl.util.ArrayMap;

/**
 * Public for testing purpose only. Use {@link TransactionFactory} to get a
 * <CODE>UserTransaction</CODE>, don't use this class.
 * <p>
 */
public class TxManager implements TransactionManager
{
    private static Logger log = Logger.getLogger( TxManager.class.getName() );

    private ArrayMap<Thread,TransactionImpl> txThreadMap;

    private final String txLogDir;
    private String separator = "/";
    private String logSwitcherFileName = "active_tx_log";
    private String txLog1FileName = "tm_tx_log.1";
    private String txLog2FileName = "tm_tx_log.2";
    private int maxTxLogRecordCount = 1000;
    private int eventIdentifierCounter = 0;

    private TxLog txLog = null;
    private XaDataSourceManager xaDsManager = null;
    private boolean tmOk = false;
    
    private final KernelPanicEventGenerator kpe;
    
    private final AtomicInteger startedTxCount = new AtomicInteger( 0 );
    private final AtomicInteger comittedTxCount = new AtomicInteger( 0 );
    private final AtomicInteger rolledBackTxCount = new AtomicInteger( 0 );
    private int peakConcurrentTransactions = 0;

    TxManager( String txLogDir, KernelPanicEventGenerator kpe )
    {
        this.txLogDir = txLogDir;
        this.kpe = kpe;
    }

    synchronized int getNextEventIdentifier()
    {
        return eventIdentifierCounter++;
    }

    void stop()
    {
        if ( txLog != null )
        {
            try
            {
                txLog.close();
            }
            catch ( IOException e )
            {
                log.warning( "Unable to close tx log[" + txLog.getName() + "]"
                    + ", " + e );
            }
        }
    }

    void init( XaDataSourceManager xaDsManagerToUse )
    {
        this.xaDsManager = xaDsManagerToUse;
        txThreadMap = new ArrayMap<Thread,TransactionImpl>( 5, true, true );
        separator = System.getProperty( "file.separator" );
        logSwitcherFileName = txLogDir + separator + "active_tx_log";
        txLog1FileName = "tm_tx_log.1";
        txLog2FileName = "tm_tx_log.2";
        try
        {
            if ( new File( logSwitcherFileName ).exists() )
            {
                FileChannel fc = new RandomAccessFile( logSwitcherFileName,
                    "rw" ).getChannel();
                byte fileName[] = new byte[256];
                ByteBuffer buf = ByteBuffer.wrap( fileName );
                fc.read( buf );
                fc.close();
                String currentTxLog = txLogDir + separator
                    + new String( fileName ).trim();
                if ( !new File( currentTxLog ).exists() )
                {
                    throw new TransactionFailureException( 
                        "Unable to start TM, " + "active tx log file[" + 
                        currentTxLog + "] not found." );
                }
                txLog = new TxLog( currentTxLog );
            }
            else
            {
                if ( new File( txLogDir + separator + txLog1FileName ).exists()
                    || new File( txLogDir + separator + txLog2FileName )
                        .exists() )
                {
                    throw new TransactionFailureException( 
                        "Unable to start TM, "
                        + "no active tx log file found but found either "
                        + txLog1FileName + " or " + txLog2FileName
                        + " file, please set one of them as active or "
                        + "remove them." );
                }
                ByteBuffer buf = ByteBuffer.wrap( txLog1FileName
                    .getBytes( "UTF-8" ) );
                FileChannel fc = new RandomAccessFile( logSwitcherFileName,
                    "rw" ).getChannel();
                fc.write( buf );
                txLog = new TxLog( txLogDir + separator + txLog1FileName );
                fc.force( true );
                fc.close();
            }
            Iterator<List<TxLog.Record>> danglingRecordList = 
                txLog.getDanglingRecords();
            if ( danglingRecordList.hasNext() )
            {
                log.info( "Unresolved transactions found, " + 
                    "recovery started ..." );
                recover( danglingRecordList );
                log.info( "Recovery completed, all transactions have been " + 
                    "resolved to a consistent state." );
            }
            getTxLog().truncate();
            tmOk = true;
        }
        catch ( IOException e )
        {
            log.severe( "Unable to start TM" );
            throw new TransactionFailureException( "Unable to start TM", e );
        }
    }

    synchronized TxLog getTxLog() throws IOException
    {
        if ( txLog.getRecordCount() > maxTxLogRecordCount )
        {
            if ( txLog.getName().endsWith( txLog1FileName ) )
            {
                txLog.switchToLogFile( txLogDir + separator + txLog2FileName );
                changeActiveLog( txLog2FileName );
            }
            else if ( txLog.getName().endsWith( txLog2FileName ) )
            {
                txLog.switchToLogFile( txLogDir + separator + txLog1FileName );
                changeActiveLog( txLog1FileName );
            }
            else
            {
                setTmNotOk();
                log.severe( "Unknown active tx log file[" + txLog.getName()
                    + "], unable to switch." );
                throw new IOException( "Unknown txLogFile[" + txLog.getName()
                    + "] not equals to either [" + txLog1FileName + "] or ["
                    + txLog2FileName + "]" );
            }
        }
        return txLog;
    }

    private void changeActiveLog( String newFileName ) throws IOException
    {
        // change active log
        FileChannel fc = new RandomAccessFile( logSwitcherFileName, "rw" )
            .getChannel();
        ByteBuffer buf = ByteBuffer.wrap( newFileName.getBytes() );
        fc.truncate( 0 );
        fc.write( buf );
        fc.force( true );
        fc.close();
    }

    void setTmNotOk()
    {
        tmOk = false;
        kpe.generateEvent( null );
    }

    private void recover( Iterator<List<TxLog.Record>> danglingRecordList )
    {
        try
        {
            // contains NonCompletedTransaction that needs to be committed
            List<NonCompletedTransaction> commitList = 
                new ArrayList<NonCompletedTransaction>();

            // contains Xids that should be rolledback
            List<Xid> rollbackList = new LinkedList<Xid>();

            // key = Resource(branchId) value = XAResource
            Map<Resource,XAResource> resourceMap = 
                new HashMap<Resource,XAResource>();
            buildRecoveryInfo( commitList, rollbackList, resourceMap,
                danglingRecordList );
            // invoke recover on all xa resources found
            Iterator<Resource> resourceItr = resourceMap.keySet().iterator();
            List<Xid> recoveredXidsList = new LinkedList<Xid>();
            while ( resourceItr.hasNext() )
            {
                XAResource xaRes = resourceMap.get( resourceItr.next() );
                Xid xids[] = xaRes.recover( XAResource.TMNOFLAGS );
                for ( int i = 0; i < xids.length; i++ )
                {
                    if ( XidImpl.isThisTm( xids[i].getGlobalTransactionId() ) )
                    {
                        // linear search
                        if ( rollbackList.contains( xids[i] ) )
                        {
                            log.fine( "Found pre commit " + xids[i]
                                + " rolling back ... " );
                            rollbackList.remove( xids[i] );
                            xaRes.rollback( xids[i] );
                        }
                        else
                        {
                            recoveredXidsList.add( xids[i] );
                        }
                    }
                    else
                    {
                        log.warning( "Unknown xid: " + xids[i] );
                    }
                }
            }
            // sort the commit list after sequence number
            Collections.sort( commitList,
                new Comparator<NonCompletedTransaction>()
                {
                    public int compare( NonCompletedTransaction r1,
                        NonCompletedTransaction r2 )
                    {
                        return r1.getSequenceNumber() - r2.getSequenceNumber();
                    }
                } );
            // go through and commit
            Iterator<NonCompletedTransaction> commitItr = commitList.iterator();
            while ( commitItr.hasNext() )
            {
                NonCompletedTransaction nct = commitItr.next();
                int seq = nct.getSequenceNumber();
                Xid xids[] = nct.getXids();
                log.fine( "Marked as commit tx-seq[" + seq + 
                    "] branch length: " + xids.length );
                for ( int i = 0; i < xids.length; i++ )
                {
                    if ( !recoveredXidsList.contains( xids[i] ) )
                    {
                        log.fine( "Tx-seq[" + seq + "][" + xids[i] + 
                            "] not found in recovered xid list, "
                            + "assuming already committed" );
                        continue;
                    }
                    recoveredXidsList.remove( xids[i] );
                    Resource resource = new Resource( xids[i]
                        .getBranchQualifier() );
                    if ( !resourceMap.containsKey( resource ) )
                    {
                        throw new TransactionFailureException(
                            "Couldn't find XAResource for " + xids[i] );
                    }
                    log.fine( "Commiting tx seq[" + seq + "][" + 
                        xids[i] + "] ... " );
                    resourceMap.get( resource ).commit( xids[i], false );
                }
            }
            // rollback the rest
            Iterator<Xid> rollbackItr = recoveredXidsList.iterator();
            while ( rollbackItr.hasNext() )
            {
                Xid xid = rollbackItr.next();
                Resource resource = new Resource( xid.getBranchQualifier() );
                if ( !resourceMap.containsKey( resource ) )
                {
                    throw new TransactionFailureException( 
                        "Couldn't find XAResource for " + xid );
                }
                log.fine( "Rollback " + xid + " ... " );
                resourceMap.get( resource ).rollback( xid );
            }
            if ( rollbackList.size() > 0 )
            {
                log.fine( "TxLog contained unresolved "
                    + "xids that needed rollback. They couldn't be matched to "
                    + "any of the XAResources recover list. " + "Assuming "
                    + rollbackList.size()
                    + " transactions already rolled back." );
            }
        }
        catch ( XAException e )
        {
            throw new TransactionFailureException( "Recovery failed." + e );
        }
    }

    private void buildRecoveryInfo( List<NonCompletedTransaction> commitList,
        List<Xid> rollbackList, Map<Resource,XAResource> resourceMap,
        Iterator<List<TxLog.Record>> danglingRecordList )
    {
        while ( danglingRecordList.hasNext() )
        {
            Iterator<TxLog.Record> dListItr = 
                danglingRecordList.next().iterator();
            TxLog.Record startRecord = dListItr.next();
            if ( startRecord.getType() != TxLog.TX_START )
            {
                throw new TransactionFailureException(
                    "First record not a start record, type="
                        + startRecord.getType() );
            }
            // get branches & commit status
            HashSet<Resource> branchSet = new HashSet<Resource>();
            int markedCommit = -1;
            while ( dListItr.hasNext() )
            {
                TxLog.Record record = dListItr.next();
                if ( record.getType() == TxLog.BRANCH_ADD )
                {
                    if ( markedCommit != -1 )
                    {
                        throw new TransactionFailureException( 
                            "Already marked commit " + startRecord );
                    }
                    branchSet.add( new Resource( record.getBranchId() ) );
                }
                else if ( record.getType() == TxLog.MARK_COMMIT )
                {
                    if ( markedCommit != -1 )
                    {
                        throw new TransactionFailureException( 
                            "Already marked commit " + startRecord );
                    }
                    markedCommit = record.getSequenceNumber();
                }
                else
                {
                    throw new TransactionFailureException( 
                        "Illegal record type[" + record.getType() + "]" );
                }
            }
            Iterator<Resource> resourceItr = branchSet.iterator();
            List<Xid> xids = new LinkedList<Xid>();
            while ( resourceItr.hasNext() )
            {
                Resource resource = resourceItr.next();
                if ( !resourceMap.containsKey( resource ) )
                {
                    resourceMap.put( resource, getXaResource( 
                        resource.getResourceId() ) );
                }
                xids.add( new XidImpl( startRecord.getGlobalId(), 
                    resource.getResourceId() ) );
            }
            if ( markedCommit != -1 ) // this xid needs to be committed
            {
                commitList.add( 
                    new NonCompletedTransaction( markedCommit, xids ) );
            }
            else
            {
                rollbackList.addAll( xids );
            }
        }
    }

    private static class NonCompletedTransaction
    {
        private int seqNr = -1;
        private List<Xid> xidList = null;

        NonCompletedTransaction( int seqNr, List<Xid> xidList )
        {
            this.seqNr = seqNr;
            this.xidList = xidList;
        }

        int getSequenceNumber()
        {
            return seqNr;
        }

        Xid[] getXids()
        {
            return xidList.toArray( new XidImpl[xidList.size()] );
        }
    }

    private static class Resource
    {
        private byte resourceId[] = null;

        Resource( byte resourceId[] )
        {
            if ( resourceId == null || resourceId.length == 0 )
            {
                throw new IllegalArgumentException( "Illegal resourceId" );
            }
            this.resourceId = resourceId;
        }

        byte[] getResourceId()
        {
            return resourceId;
        }

        public boolean equals( Object o )
        {
            if ( !(o instanceof Resource) )
            {
                return false;
            }
            byte otherResourceId[] = ((Resource) o).getResourceId();

            if ( resourceId.length != otherResourceId.length )
            {
                return false;
            }
            for ( int i = 0; i < resourceId.length; i++ )
            {
                if ( resourceId[i] != otherResourceId[i] )
                {
                    return false;
                }
            }
            return true;
        }

        private volatile int hashCode = 0;

        public int hashCode()
        {
            if ( hashCode == 0 )
            {
                int calcHash = 0;
                for ( int i = 0; i < resourceId.length; i++ )
                {
                    calcHash += resourceId[i] << i * 8;
                }
                hashCode = 3217 * calcHash;
            }
            return hashCode;
        }
    }

    public void begin() throws NotSupportedException, SystemException
    {
        if ( !tmOk )
        {
            throw new SystemException( "TM has encountered some problem, "
                + "please perform neccesary action (tx recovery/restart)" );
        }

        Thread thread = Thread.currentThread();
        TransactionImpl tx = txThreadMap.get( thread );
        if ( tx != null )
        {
            throw new NotSupportedException(
                "Nested transactions not supported" );
        }
        tx = new TransactionImpl( this );
        txThreadMap.put( thread, tx );
        int concurrentTxCount = txThreadMap.size();
        if ( concurrentTxCount > peakConcurrentTransactions )
        {
            peakConcurrentTransactions = concurrentTxCount;
        }
        startedTxCount.incrementAndGet();
        // start record written on resource enlistment
    }
    
    // called when a resource gets enlisted
    void writeStartRecord( byte globalId[] ) throws SystemException
    {
        try
        {
            getTxLog().txStart( globalId );
        }
        catch ( IOException e )
        {
            e.printStackTrace();
            log.severe( "Error writing transaction log" );
            setTmNotOk();
            throw new SystemException( "TM encountered a problem, "
                + " error writing transaction log," + e );
        }
    }

    public void commit() throws RollbackException, HeuristicMixedException,
        HeuristicRollbackException, IllegalStateException, SystemException
    {
        if ( !tmOk )
        {
            throw new SystemException( "TM has encountered some problem, "
                + "please perform neccesary action (tx recovery/restart)" );
        }
        Thread thread = Thread.currentThread();
        TransactionImpl tx = txThreadMap.get( thread );
        if ( tx == null )
        {
            throw new IllegalStateException( "Not in transaction" );
        }
        if ( tx.getStatus() != Status.STATUS_ACTIVE
            && tx.getStatus() != Status.STATUS_MARKED_ROLLBACK )
        {
            throw new IllegalStateException( "Tx status is: "
                + getTxStatusAsString( tx.getStatus() ) );
        }
        tx.doBeforeCompletion();
        // delist resources?
        if ( tx.getStatus() == Status.STATUS_ACTIVE )
        {
            comittedTxCount.incrementAndGet();
            commit( thread, tx );
        }
        else if ( tx.getStatus() == Status.STATUS_MARKED_ROLLBACK )
        {
            rolledBackTxCount.incrementAndGet();
            rollbackCommit( thread, tx );
        }
        else
        {
            throw new IllegalStateException( "Tx status is: "
                + getTxStatusAsString( tx.getStatus() ) );
        }
    }
    
    private void commit( Thread thread, TransactionImpl tx )
        throws SystemException, HeuristicMixedException,
        HeuristicRollbackException
    {
        // mark as commit in log done TxImpl.doCommit()
        Throwable commitFailureCause = null;
        int xaErrorCode = -1;
        if ( tx.getResourceCount() == 0 )
        {
            tx.setStatus( Status.STATUS_COMMITTED );
        }
        else
        {
            try
            {
                tx.doCommit();
            }
            catch ( XAException e )
            {
                xaErrorCode = e.errorCode;
                e.printStackTrace();
                log.severe( "Commit failed, status="
                    + getTxStatusAsString( tx.getStatus() ) + ", errorCode="
                    + xaErrorCode );
                if ( tx.getStatus() == Status.STATUS_COMMITTED )
                {
                    // this should never be
                    setTmNotOk();
                    throw new TransactionFailureException(
                        "commit threw exception but status is committed?", e );
                }
            }
            catch ( Throwable t )
            {
                commitFailureCause = t;
            }
        }
        if ( tx.getStatus() != Status.STATUS_COMMITTED )
        {
            try
            {
                tx.doRollback();
            }
            catch ( XAException e )
            {
                e.printStackTrace();
                log.severe( "Unable to rollback transaction. "
                    + "Some resources may be commited others not. "
                    + "Neo4j kernel should be SHUTDOWN for "
                    + "resource maintance and transaction recovery ---->" );
                setTmNotOk();
                if ( commitFailureCause != null )
                {
                    commitFailureCause.printStackTrace();
                }
                throw new HeuristicMixedException(
                    "Unable to rollback ---> error code in commit: "
                        + xaErrorCode + " ---> error code for rollback: "
                        + e.errorCode );
            }
            tx.doAfterCompletion();
            txThreadMap.remove( thread );
            try
            {
                if ( tx.isGlobalStartRecordWritten() )
                {
                    getTxLog().txDone( tx.getGlobalId() );
                }
            }
            catch ( IOException e )
            {
                e.printStackTrace();
                log.severe( "Error writing transaction log" );
                setTmNotOk();
                throw new SystemException( "TM encountered a problem, "
                    + " error writing transaction log," + e );
            }
            tx.setStatus( Status.STATUS_NO_TRANSACTION );
            if ( commitFailureCause == null )
            {
                throw new HeuristicRollbackException(
                    "Failed to commit, transaction rolledback ---> "
                        + "error code was: " + xaErrorCode );
            }
            else
            {
                throw new HeuristicRollbackException(
                    "Failed to commit, transaction rolledback ---> " + 
                    commitFailureCause );
            }
        }
        tx.doAfterCompletion();
        txThreadMap.remove( thread );
        try
        {
            if ( tx.isGlobalStartRecordWritten() )
            {
                getTxLog().txDone( tx.getGlobalId() );
            }
        }
        catch ( IOException e )
        {
            e.printStackTrace();
            log.severe( "Error writing transaction log" );
            setTmNotOk();
            throw new SystemException( "TM encountered a problem, "
                + " error writing transaction log," + e );
        }
        tx.setStatus( Status.STATUS_NO_TRANSACTION );
    }

    private void rollbackCommit( Thread thread, TransactionImpl tx )
        throws HeuristicMixedException, RollbackException, SystemException
    {
        try
        {
            tx.doRollback();
        }
        catch ( XAException e )
        {
            e.printStackTrace();
            log.severe( "Unable to rollback marked transaction. "
                + "Some resources may be commited others not. "
                + "Neo4j kernel should be SHUTDOWN for "
                + "resource maintance and transaction recovery ---->" );
            setTmNotOk();
            throw new HeuristicMixedException( "Unable to rollback "
                + " ---> error code for rollback: " + e.errorCode );
        }

        tx.doAfterCompletion();
        txThreadMap.remove( thread );
        try
        {
            if ( tx.isGlobalStartRecordWritten() )
            {
                getTxLog().txDone( tx.getGlobalId() );
            }
        }
        catch ( IOException e )
        {
            e.printStackTrace();
            log.severe( "Error writing transaction log" );
            setTmNotOk();
            throw new SystemException( "TM encountered a problem, "
                + " error writing transaction log," + e );
        }
        tx.setStatus( Status.STATUS_NO_TRANSACTION );
        throw new RollbackException( 
            "Failed to commit, transaction rolledback" );
    }

    public void rollback() throws IllegalStateException, SystemException
    {
        if ( !tmOk )
        {
            throw new SystemException( "TM has encountered some problem, "
                + "please perform neccesary action (tx recovery/restart)" );
        }
        Thread thread = Thread.currentThread();
        TransactionImpl tx = txThreadMap.get( thread );
        if ( tx == null )
        {
            throw new IllegalStateException( "Not in transaction" );
        }
        if ( tx.getStatus() == Status.STATUS_ACTIVE ||
            tx.getStatus() == Status.STATUS_MARKED_ROLLBACK || 
            tx.getStatus() == Status.STATUS_PREPARING )
        {
            tx.setStatus( Status.STATUS_MARKED_ROLLBACK );
            tx.doBeforeCompletion();
            // delist resources?
            try
            {
                rolledBackTxCount.incrementAndGet();
                tx.doRollback();
            }
            catch ( XAException e )
            {
                e.printStackTrace();
                log.severe( "Unable to rollback marked or active transaction. "
                    + "Some resources may be commited others not. "
                    + "Neo4j kernel should be SHUTDOWN for "
                    + "resource maintance and transaction recovery ---->" );
                setTmNotOk();
                throw new SystemException( "Unable to rollback "
                    + " ---> error code for rollback: " + e.errorCode );
            }
            tx.doAfterCompletion();
            txThreadMap.remove( thread );
            try
            {
                if ( tx.isGlobalStartRecordWritten() )
                {
                    getTxLog().txDone( tx.getGlobalId() );
                }
            }
            catch ( IOException e )
            {
                e.printStackTrace();
                log.severe( "Error writing transaction log" );
                setTmNotOk();
                throw new SystemException( "TM encountered a problem, "
                    + " error writing transaction log," + e );
            }
            tx.setStatus( Status.STATUS_NO_TRANSACTION );
        }
        else
        {
            throw new IllegalStateException( "Tx status is: "
                + getTxStatusAsString( tx.getStatus() ) );
        }
    }

    public int getStatus()
    {
        Thread thread = Thread.currentThread();
        TransactionImpl tx = txThreadMap.get( thread );
        if ( tx != null )
        {
            return tx.getStatus();
        }
        return Status.STATUS_NO_TRANSACTION;
    }

    public Transaction getTransaction()
    {
        return txThreadMap.get( Thread.currentThread() );
    }

    public void resume( Transaction tx ) throws IllegalStateException,
        SystemException
    {
        if ( !tmOk )
        {
            throw new SystemException( "TM has encountered some problem, "
                + "please perform neccesary action (tx recovery/restart)" );
        }
        Thread thread = Thread.currentThread();
        if ( txThreadMap.get( thread ) != null )
        {
            throw new IllegalStateException( "Transaction already associated" );
        }
        if ( tx != null )
        {
            TransactionImpl txImpl = (TransactionImpl) tx;
            if ( txImpl.getStatus() != Status.STATUS_NO_TRANSACTION )
            {
                txImpl.markAsActive();
                txThreadMap.put( thread, txImpl );
            }
            // generate pro-active event resume
        }
    }

    public Transaction suspend() throws SystemException
    {
        if ( !tmOk )
        {
            throw new SystemException( "TM has encountered some problem, "
                + "please perform neccesary action (tx recovery/restart)" );
        }
        // check for ACTIVE/MARKED_ROLLBACK?
        TransactionImpl tx = txThreadMap.remove( Thread.currentThread() );
        if ( tx != null )
        {
            // generate pro-active event suspend
            tx.markAsSuspended();
        }
        return tx;
    }

    public void setRollbackOnly() throws IllegalStateException, SystemException
    {
        if ( !tmOk )
        {
            throw new SystemException( "TM has encountered some problem, "
                + "please perform neccesary action (tx recovery/restart)" );
        }
        Thread thread = Thread.currentThread();
        TransactionImpl tx = txThreadMap.get( thread );
        if ( tx == null )
        {
            throw new IllegalStateException( "Not in transaction" );
        }
        tx.setRollbackOnly();
    }

    public void setTransactionTimeout( int seconds ) throws SystemException
    {
        if ( !tmOk )
        {
            throw new SystemException( "TM has encountered some problem, "
                + "please perform neccesary action (tx recovery/restart)" );
        }
        // ...
    }

    byte[] getBranchId( XAResource xaRes )
    {
        if ( xaRes instanceof XaResource )
        {
            byte branchId[] = ((XaResource) xaRes).getBranchId();
            if ( branchId != null )
            {
                return branchId;
            }
        }
        return xaDsManager.getBranchId( xaRes );
    }

    XAResource getXaResource( byte branchId[] )
    {
        return xaDsManager.getXaResource( branchId );
    }

    String getTxStatusAsString( int status )
    {
        switch ( status )
        {
            case Status.STATUS_ACTIVE:
                return "STATUS_ACTIVE";
            case Status.STATUS_NO_TRANSACTION:
                return "STATUS_NO_TRANSACTION";
            case Status.STATUS_PREPARING:
                return "STATUS_PREPARING";
            case Status.STATUS_PREPARED:
                return "STATUS_PREPARED";
            case Status.STATUS_COMMITTING:
                return "STATUS_COMMITING";
            case Status.STATUS_COMMITTED:
                return "STATUS_COMMITED";
            case Status.STATUS_ROLLING_BACK:
                return "STATUS_ROLLING_BACK";
            case Status.STATUS_ROLLEDBACK:
                return "STATUS_ROLLEDBACK";
            case Status.STATUS_UNKNOWN:
                return "STATUS_UNKNOWN";
            case Status.STATUS_MARKED_ROLLBACK:
                return "STATUS_MARKED_ROLLBACK";
            default:
                return "STATUS_UNKNOWN(" + status + ")";
        }
    }

    public synchronized void dumpTransactions()
    {
        Iterator<TransactionImpl> itr = txThreadMap.values().iterator();
        if ( !itr.hasNext() )
        {
            System.out.println( "No uncompleted transactions" );
            return;
        }
        System.out.println( "Uncompleted transactions found: " );
        while ( itr.hasNext() )
        {
            System.out.println( itr.next() );
        }
    }

    public int getEventIdentifier()
    {
        TransactionImpl tx = (TransactionImpl) getTransaction();
        if ( tx != null )
        {
            return tx.getEventIdentifier();
        }
        return -1;
    }
    
    public int getStartedTxCount()
    {
        return startedTxCount.get();
    }

    public int getCommittedTxCount()
    {
        return comittedTxCount.get();
    }
    
    public int getRolledbackTxCount()
    {
        return rolledBackTxCount.get();
    }
    
    public int getActiveTxCount()
    {
        return txThreadMap.size();
    }
    
    public int getPeakConcurrentTxCount()
    {
        return peakConcurrentTransactions;
    }
}
