/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2000-2009 Sun Microsystems, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License ("CDDL") (collectively, 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/CDDL+GPL.html
 * or mq/legal/LICENSE.txt.  See the License for the specific language
 * governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at mq/legal/LICENSE.txt.  Sun designates
 * this particular file as subject to the "Classpath" exception as provided by
 * Sun in the GPL Version 2 section of the License file that accompanied this
 * code.  If applicable, add the following below the License Header, with the
 * fields enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or  to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright holder.
 */

package com.sun.messaging.bridge.service.jms.tx;


import java.io.File;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Properties;
import java.util.Iterator;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.nio.ByteBuffer;
import java.io.UnsupportedEncodingException;
import javax.transaction.*;
import javax.transaction.xa.*;
import com.sun.messaging.jmq.util.UID;
import com.sun.messaging.bridge.service.jms.tx.log.TxLog;
import com.sun.messaging.bridge.service.jms.tx.log.LogRecord;
import com.sun.messaging.bridge.service.jms.tx.log.GlobalXidDecision;
import com.sun.messaging.bridge.service.jms.tx.log.BranchXidDecision;
import com.sun.messaging.bridge.service.JMSBridgeStore;
import com.sun.messaging.bridge.service.jms.tx.log.JDBCTxLogImpl;
import com.sun.messaging.bridge.service.FaultInjection;
import com.sun.messaging.bridge.service.jms.JMSBridge;
import com.sun.messaging.bridge.service.jms.resources.JMSBridgeResources;


/**
 * Implements JTA TransactionManager interface
 *
 * @author amyk
 */
public class TransactionManagerImpl implements TransactionManager, 
                                       TransactionManagerAdapter {

    private static int FORMATID = 1246580992; //JMQ'\0'

    private enum TMState { UNINITIALIZED, INITIALIZED, CLOSING, CLOSED }

    private Logger _logger = null;

    private static final int DEFAULT_TRANSACTION_TIMEOUT = 0;    //no timeout
    private int _transactionTimeout = DEFAULT_TRANSACTION_TIMEOUT;

    private String _tmName = null; 
    private String _jmsbridge = null; 

    private static final int DEFAULT_MAX_BRANCHES = 16;
    private int _maxBranches = DEFAULT_MAX_BRANCHES;

    private TMState _state = TMState.UNINITIALIZED;

    private Map<String, List<XAResource>> _rmToXAResources = new LinkedHashMap<String, List<XAResource>>();

    private ThreadLocal<TransactionImpl> _threadLocal =  new ThreadLocal<TransactionImpl>();

    private String _txlogdir = null;
    private TxLog _txlog = null;
    private String _txlogType = TxLog.FILETYPE; 
    private String _txlogClass = null;

    private JMSBridgeStore _jdbcStore = null; 

    private List<LogRecord> _recoveredLRs = new ArrayList<LogRecord>();
    private LinkedHashMap<String, ArrayList<String>> _keepGxidsForRM = new LinkedHashMap<String, ArrayList<String>>();

    private FaultInjection _fi = FaultInjection.getInjection();

    private static JMSBridgeResources _jbr = JMSBridge.getJMSBridgeResources(); 

    private boolean _sameXARSameRM = true;

    public TransactionManagerImpl() {}

    /**
     * to be called before init() if JDBC store
     */
    public void setJDBCStore(JMSBridgeStore store) 
                    throws IllegalStateException {

        if (_state != TMState.UNINITIALIZED) {
            throw new IllegalStateException("setJDBCStore");
        }
        _jdbcStore = store;
    }

    public synchronized void init(Properties props, boolean reset) throws Exception {
        if (_logger == null) {
            throw new IllegalStateException("TM has no logger set");
        }

        if (props != null) {
            Enumeration en = props.propertyNames();
            String name = null;
            String value = null;
            while (en.hasMoreElements()) {
                name = (String)en.nextElement();
                value = props.getProperty(name);
                _logger.log(Level.INFO, _jbr.getString(_jbr.I_SET_PROP_TM, name+"="+value, this.getClass().getName()));
                setProperty(name, value);
            }
        }

        if (_tmName == null) {
            throw new IllegalStateException("TM name not set for "+this.getClass().getName());
        }

        if (_state != TMState.UNINITIALIZED) {
           _logger.log(Level.WARNING, "init "+this); 
           throw new IllegalStateException("init "+this);
        }

        String txlogc = null;
        if (_txlogClass == null) {
            if (_txlogType.equals(TxLog.JDBCTYPE)) {
                _txlog = (TxLog)Class.forName(TxLog.JDBCCLASS).newInstance();
                ((JDBCTxLogImpl)_txlog).setJDBCStore(_jdbcStore);
            } else {
                _txlog = (TxLog)Class.forName(TxLog.FILECLASS).newInstance();
            }
        } else {
            _logger.log(Level.INFO, "loading txlog class "+_txlogClass);
            _txlog = (TxLog)Class.forName(_txlogClass).newInstance();
        }
        _txlog.setLogger(_logger);
        if (props.getProperty("txlogMaxBranches") == null) {
            props.setProperty("txlogMaxBranches", String.valueOf(_maxBranches));
        }
        try {
            _txlog.init(props, reset);
        } catch (Exception e) {
            _txlog.close();
            throw e;
        }
        _recoveredLRs = _txlog.getAllLogRecords();
        _logger.log(Level.INFO, _jbr.getString(_jbr.I_TM_START_WITH, _tmName, String.valueOf(_recoveredLRs.size()))); 
        if (_logger.isLoggable(Level.FINE)) {
            LogRecord lr = null;
            Iterator<LogRecord> itr = _recoveredLRs.iterator();
            while (itr.hasNext()) {
                lr = itr.next();
                _logger.log(Level.INFO, "\t"+lr+"\n");
            }
        }
        _state = TMState.INITIALIZED;
    }

    /**
     */
    public String[] getAllTransactions() throws Exception {
        if (_state != TMState.INITIALIZED || _txlog == null) {
            return null;
        } 
        try {
            List<LogRecord> lrs = _txlog.getAllLogRecords();

            Iterator<LogRecord> itr = lrs.iterator();
            String[] lrss = new String[lrs.size()];
            int i = 0;
            while (itr.hasNext()) {
                lrss[i++] = itr.next().toString();
            }
            return lrss;
        } catch (Exception e) { 
            _logger.log(Level.WARNING, "Unable to get "+this+" all log records: "+e.getMessage(), e);
        }
        return null;

    }


    /**
     *
     */
    public TransactionManager getTransactionManager() 
                             throws SystemException {
        return this;
    }

    public void setProperty(String key, String value) 
                                   throws Exception {
        if (_state != TMState.UNINITIALIZED) { 
            throw new IllegalStateException("setProperty("+key+", "+value+")");
        }
        if (key.equals("tmname")) {
            setName(value);
            return;
        } 
        if (key.equals("txlogType")) {
            setTxlogType(value); 
            return;
        }
        if (key.equals("txlogClass")) {
            setTxlogClass(value); 
            return;
        }
        if (key.equals("jmsbridge")) {
            _jmsbridge = value;
            return;
        }
        if (key.equals("txSameXAResourceSameRM")) {
            _sameXARSameRM = Boolean.valueOf(value); 
            return;
        }
    }

    /**
     */
    public void setName(String v) throws SystemException {
        if (_state != TMState.UNINITIALIZED) { 
            throw new IllegalStateException("setName("+v+")");
        }
        if (v.getBytes().length > MAX_TMNAME_LENGTH) {
            throw new SystemException(
            "TM name "+v+" exceeds maximum "+MAX_TMNAME_LENGTH+" bytes");
        }
        _tmName = v;
    }

    public void setTxlogType(String type) throws Exception {
        if (_state != TMState.UNINITIALIZED) { 
            throw new IllegalStateException("setTxlogType("+type+")");
        }

        if (type == null || 
            (!type.trim().equals(TxLog.FILETYPE) &&
             !type.trim().equals(TxLog.JDBCTYPE))) {
            throw new IllegalArgumentException(this+": Invalid txlog type "+type);
        }
        _txlogType = type;
    }

    public void setTxlogClass(String cs) throws Exception {
        if (_state != TMState.UNINITIALIZED) { 
            throw new IllegalStateException("setTxlogClass("+cs+")");
        }

        if (cs == null || cs.trim().length() == 0) { 
            throw new IllegalArgumentException(this+": Invalid txlog class "+cs);
        }
        Class c = Class.forName(cs.trim());
        c.newInstance();
        _txlogClass = cs.trim();
    }

    public void setMaxBranches(int v) {
        if (_state != TMState.UNINITIALIZED) { 
            throw new IllegalStateException("setMaxBranches("+v+")");
        }

        if (v < 0 || v > Byte.MAX_VALUE) {
            throw new IllegalArgumentException(
            "Invalid value "+v+ " for maximum branches"); 
        }
        _maxBranches = v;
    }

    protected int getMaxBranches() {
        return _maxBranches;
    }

    public boolean registerRM() {
        return true;
    }

    /**
     * More than one XAResource object can be registered to a same rmName 
     * and isSameRM can be false among them; However resources registered 
     * to a same rmName and if isSameRM true among them, they must have same
     * class name 
     * 
     */
    public void registerRM(String rmName, XAResource xaRes) 
                                       throws Exception {
        if (_state != TMState.INITIALIZED) {
            throw new IllegalStateException("TM not initialized");
        }
        if (rmName == null) {
            throw new SystemException("null RM name");
        }
        if (xaRes == null) {
            throw new SystemException("null XAResource object for RM "+rmName);
        }

        if (rmName.getBytes().length > MAX_RMNAME_LENGTH) {
            throw new SystemException(
            "RM name "+rmName+" exceeds maximum "+MAX_RMNAME_LENGTH+" bytes");
        }
        
        synchronized(_rmToXAResources) {
            List<XAResource> l = _rmToXAResources.get(rmName); 
            if (l == null) {
                l = new ArrayList<XAResource>();
                _rmToXAResources.put(rmName, l);
            }
            for (XAResource xar : l) {
                if (xar.isSameRM(xaRes) || xaRes.isSameRM(xar)) {
                    if (!xar.getClass().getName().equals(xaRes.getClass().getName())) { //XXX
                        String emsg = "XAResource "+xaRes+" has different class name from what's registered "+xar+" for RM "+rmName;
                        _logger.log(Level.SEVERE, emsg);
                        throw new IllegalArgumentException(emsg);
                    }
                }
            }
            if (!l.contains(xaRes)) l.add(xaRes);
        } 
        ArrayList<Xid[]> axids = new ArrayList<Xid[]>();
        Xid[] xidsr = null;
        int flag = XAResource.TMSTARTRSCAN;
        do {
            
            try {
                if (_fi.FAULT_INJECTION) {
                    Map p = new HashMap();
                    p.put(FaultInjection.CFREF_PROP, rmName);
                    _fi.setLogger(_logger);
                    _fi.checkFaultAndThrowException(FaultInjection.FAULT_XA_RECOVER_1, p, "javax.transaction.xa.XAException", true);
                }

                xidsr = xaRes.recover(flag);
                if (xidsr.length > 0) flag = XAResource.TMNOFLAGS;
                axids.add(xidsr);
            } catch (Throwable t) {
                _logger.log(Level.SEVERE, "Recovering XAResource "+xaRes+" from RM "+rmName+ " failed", t);
                SystemException ex = new SystemException(t.getMessage());
                ex.initCause(t);
                throw ex;
            }
        } while (xidsr.length > 0);

        ArrayList<String> rmNameKeepGxids = new ArrayList<String>();
        ArrayList<String> rmNameRealNames = new ArrayList<String>();
        
        BranchXid bxid = null;
        byte[] gdata, bdata;
        boolean commit = false;
        for (Xid[] xids : axids) {
            for (int i = 0; i < xids.length; i++) {
                bxid = new BranchXid();
                bxid.copy(xids[i]);
                if (bxid.getFormatId() != FORMATID) {
                    _logger.log(Level.WARNING, "Ignore foreign XID "+bxid +"["+bxid.getFormatId()+"]");
                    continue;
                }
                gdata = bxid.getGlobalTransactionId();
                int tmnlen = (int)gdata[0];
                String tmn = new  String(gdata, 1, tmnlen, "UTF-8");
                if (!tmn.equals(_tmName)) {
                    _logger.log(Level.WARNING, "Ignore global XID "+bxid +" from different TM name ["+tmn+"] from mine ["+_tmName+"]");
                    continue;
                }
                bdata = bxid.getBranchQualifier();
                int rmnlen = (int)bdata[0];
                String rmn = new String(bdata, 1, rmnlen, "UTF-8");
                if (!rmn.equals(rmName)) {
                    _logger.log(Level.WARNING, "XID "+bxid +" from RM ["+rmName+"]"+xaRes+" has different RM name ["+rmn+"]");
                    rmNameRealNames.add(rmn);
                }
                commit = false;
                GlobalXid gxid = new GlobalXid();
                gxid.setFormatId(bxid.getFormatId());
                gxid.setGlobalTransactionId(gdata);
                _logger.log(Level.INFO, 
                "Recovering branch "+bxid +" for global transaction "+gxid+" from RM ["+rmName+"("+rmn+")]"+xaRes);

                LogRecord lr = _txlog.getLogRecord(gxid);
                if (lr != null && 
                    (lr.getGlobalDecision() == GlobalXidDecision.COMMIT)) {
                    commit = true;
                }
                try {
                     if (lr != null && lr.isHeuristicBranch(bxid)) {
                         _logger.log(Level.WARNING, 
                         "Branch "+bxid+" was heuristically completed in "+gxid+"["+commit+"]"); //XXX
                         rmNameKeepGxids.add(gxid.toString());
                         continue; //XXX
                     }
                } catch (NoSuchElementException e) {
                     rmNameKeepGxids.add(gxid.toString());
                     _logger.log(Level.WARNING, "Unable to find branch "+bxid+" in "+lr+" for recovery");
                     continue;
                }
                XAParticipant party = new XAParticipant(rmn, xaRes, bxid, true);
                party.setLogger(_logger);
                Throwable et = null;
                if (commit) {
                    try {
                        _logger.log(Level.INFO, "Commiting recovered branch "+bxid+" to RM ["+rmName+"("+rmn+")]"+xaRes);
                        party.commit(false); 
                    } catch (Throwable t) { //XXX
                        rmNameKeepGxids.add(gxid.toString());
                        et = t;
                        _logger.log(Level.WARNING, "Failed to commit recovered branch "+bxid, t);
                    }
                } else {
                    try {
                        _logger.log(Level.INFO, "Rolling back recovered branch "+bxid+" to RM "+rmName);
                        party.rollback();
                    } catch (Throwable t) { //XXX
                        et = t;
                        _logger.log(Level.WARNING, "Failed to rollback recovered branch "+bxid, t);
                    }
                }
            }
        }
        cleanupRecoveredLRs(rmName, rmNameKeepGxids, rmNameRealNames);
    }

    private void keepGxidForRM(String gxid, String effectiveRM, String realRM) {
        synchronized(_keepGxidsForRM) {
            if (effectiveRM != null) {
                ArrayList<String> gxids = _keepGxidsForRM.get(effectiveRM);
                if (gxids == null) {
                    gxids =  new ArrayList<String>();
                    _keepGxidsForRM.put(effectiveRM, gxids);
                }
                if (gxid != null) {
                    if (!gxids.contains(gxid)) gxids.add(gxid);
                }
            }

            if (realRM != null) {
                ArrayList<String> gxids = _keepGxidsForRM.get(realRM);
                if (gxids == null) {
                    gxids =  new ArrayList<String>();
                    _keepGxidsForRM.put(realRM, gxids);
                }
                if (gxid != null) {
                    if (!gxids.contains(gxid)) gxids.add(gxid);
                }
            }
        }
    }

    private synchronized void cleanupRecoveredLRs(String rmName, 
                                  ArrayList<String> keepGxids,
                                  ArrayList<String> realRMNames) {

        _logger.log(Level.INFO, _jbr.getString(_jbr.I_UPDATE_RECOVER_INFO_GXIDS, rmName, Integer.valueOf(keepGxids.size())));
        keepGxidForRM(null, rmName, null);
        String realn = null;
        Iterator<String> itr1 = realRMNames.iterator(); 
        while (itr1.hasNext()) {
            realn = itr1.next();
            _logger.log(Level.INFO, _jbr.getString(_jbr.I_UPDATE_RECOVER_INFO_FOR, rmName, realn));
            keepGxidForRM(null, null, realn);
        }

        String gxid = null;
        Iterator<String> itr0 = keepGxids.iterator();
        while(itr0.hasNext()) {
            gxid = itr0.next();
            keepGxidForRM(gxid, rmName, null);
            itr1 = realRMNames.iterator();
            while (itr1.hasNext()) {
                realn = itr1.next();
                keepGxidForRM(gxid, null, realn);
            }
        }

        synchronized(_recoveredLRs) {

        gxid = null;
        LogRecord lr = null;
        BranchXidDecision[] bxidds = null;
        Iterator<LogRecord> itr = _recoveredLRs.iterator();
        while (itr.hasNext()) {
            lr =itr.next();
            gxid = lr.getGlobalXid().toString();
            bxidds = lr.getBranchXidDecisions();
            if (_logger.isLoggable(Level.FINE)) {
                _logger.log(Level.INFO, "Check recovery completion for global xid "+gxid);
            }

            BranchXid bxid = null;
            byte[] bq = null;
            int len = 0;
            String rmn = null;
            boolean keep = false;
            for (int i = 0; i < bxidds.length; i++) {
                bxid = bxidds[i].getBranchXid();
                bq = bxid.getBranchQualifier();
                len = (int)bq[0];
                try {
                    rmn = new String(bq, 1, len, "UTF-8"); 
                } catch (UnsupportedEncodingException e) {
                    _logger.log(Level.WARNING, 
                    "Unable to get RM name from branch "+bxid.toString()+" for recovery global xid "+gxid+": "+e.getMessage()); 
                    keep = true; 
                    break;
                }
                if (_logger.isLoggable(Level.FINE)) {
                    _logger.log(Level.INFO, "Check recovery completion for branch xid "+bxid+" with RM["+rmn+"]");
                }
                ArrayList<String> g = _keepGxidsForRM.get(rmn);
                if (g == null || g.contains(gxid)) {
                    if (_logger.isLoggable(Level.FINE)) {
                        if (g == null) {
                        _logger.log(Level.INFO, "Keep global xid "+gxid+" for no RM["+rmn+"] info");
                        } else {
                        _logger.log(Level.INFO, "Keep global xid "+gxid+" for RM["+rmn+"]");
                        }
                    }
                    keep = true;
                    break;
                }
                if (_logger.isLoggable(Level.FINE)) {
                    _logger.log(Level.INFO, "GXIDs "+g.size()+" to keep for RM["+rmn+"]");
                    Iterator<String> gitr = g.iterator(); 
                    while (gitr.hasNext()) {
                    _logger.log(Level.INFO, "GXIDs to keep for RM["+rmn+"]: "+gitr.next());
                    }
                }
            }
            if (!keep) {
                try {
                    _logger.log(Level.INFO, _jbr.getString(_jbr.I_TM_CLEANUP_RECOVERED_GTXN, gxid.toString()));
                    _txlog.reap(gxid);
                    itr.remove();
                } catch (Exception e) {
                    _logger.log(Level.WARNING, "Unable to cleanup recovered global xid "+gxid, e); 
                }
            }
        }
        }
    }

    /**
     *
     */
    public void unregisterRM(String rmName) throws Exception { 
        if (rmName == null) {
            throw new SystemException("null RM name");
        }
        synchronized(_rmToXAResources) {
            List<XAResource> l = _rmToXAResources.get(rmName); 
            if (l == null) {
                _logger.log(Level.WARNING,  "Removing a unknown RM "+rmName);
                return;
            }
            l.remove(rmName);
            if (l.size() == 0) _rmToXAResources.remove(rmName);
        } 
    }

    /**
     */
    protected String getRM(XAResource xaRes) throws Exception {
        synchronized(_rmToXAResources) {
            for (Map.Entry<String, List<XAResource>> pair: _rmToXAResources.entrySet()) {
                List<XAResource> l = pair.getValue();
                for (XAResource xar : l) {
                     if (xar.isSameRM(xaRes) || (_sameXARSameRM && xar == xaRes)) {
                         if (!xar.getClass().getName().equals(xaRes.getClass().getName())) {
                             _logger.log(Level.WARNING, 
                             "XAResource "+xaRes+" has different class name from what's registered "+xar+" for RM "+pair.getKey());
                             continue;
                         }
                         return pair.getKey();
                     }
                }
            }
        }
        return null;
    }


    /**
     * 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 {
        checkState();
        TransactionImpl tx = _threadLocal.get();
        if (tx != null) {
            throw new NotSupportedException(
            "Nested transaction is not supported. The calling thread "+
             Thread.currentThread()+" is currently associated with transaction "+tx);
        }
        GlobalXid xid = genGlobalXid();
        tx = new TransactionImpl(xid, this);
        _threadLocal.set(tx);
    }

    /**
     * Complete the transaction associated with the current thread. When this
     * method completes, the thread is no longer associated with a transaction.
     *
     * @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 all 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 {

        TransactionImpl tx = _threadLocal.get();
        if (tx == null) {
            throw new IllegalStateException(
            "No transaction associate with the calling thread "+Thread.currentThread());
        }
        try {
            tx.commit();
        } finally {
            _threadLocal.set(null);
            if (tx.getStatus() == Status.STATUS_NO_TRANSACTION) {
            }
        }
    }

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

    /**
     * Get the transaction object that represents the transaction
     * context of the calling thread.
     *
     * @return the <code>Transaction</code> 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 {
        TransactionImpl tx = _threadLocal.get();
        if (tx == null) {
            throw new SystemException(
            "No transaction associated with calling thread "+Thread.currentThread());
        }
        return tx;
    }

    /**
     * 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 <code>Transaction</code> 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 {
        throw new SystemException("operation not supported");
        
    }

    /**
     * Roll back the transaction associated with the current thread. When this
     * method completes, the thread is no longer associated with a
     * transaction.
     *
     * @exception SecurityException Thrown to indicate that the thread is
     *    not allowed 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 rollback() throws IllegalStateException, SecurityException,
                            SystemException {
        TransactionImpl tx = _threadLocal.get();
        if (tx == null) {
            throw new IllegalStateException(
            "No transaction associate with the calling thread "+Thread.currentThread());
        }
        try {
            tx.rollback();
        } finally {
            _threadLocal.set(null); 
        }
    }

    /**
     * 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 {
        TransactionImpl tx = _threadLocal.get();
        if (tx == null) {
            throw new IllegalStateException(
            "No transaction associate with the calling thread "+Thread.currentThread());
        }
        tx.setRollbackOnly();
    }

    /**
     * Modify the timeout value that is associated with 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. If the value
     *        is negative a SystemException is thrown.
     *
     * @exception SystemException Thrown if the transaction manager
     *    encounters an unexpected error condition.
     *
     */
    public synchronized void setTransactionTimeout(int seconds) throws SystemException {
        throw new SystemException("operation not supported");
        /*
        checkState();

        if (seconds = 0) {
            _transactionTimeout = DEFAULT_TIMEOUT;
            _logger.log(Level.INFO, "Restored TM default transaction timeout "+DEFAULT_TIMEOUT);
            return;
        }
        if (seconds > 0) {
            _transactionTimeout = seconds;
            _logger.log(Level.INFO, "Set TM transaction timeout to "+seconds);
            return;
        }
        throw new SystemException("Invalid timeout value: "+seconds);
        */
    }

    /**
     * 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 not associated with a 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 {
        throw new SystemException("operation not supported");
    }

    public synchronized void shutdown() throws SystemException {
        if (_state == TMState.CLOSED) { 
            _logger.log(Level.INFO, _jbr.getString(_jbr.I_TM_ALREADY_SHUTDOWN, this.toString()));
            return;
        }

        _state = TMState.CLOSING; 
        try {
            _txlog.close();
        } catch (Exception e) {
            _logger.log(Level.WARNING, "Failed to close txlog: ", e.getMessage());
            SystemException ex = new SystemException();
            ex.initCause(e);
            throw ex;
        }

        _state = TMState.CLOSED; 
    }

    private void checkState() throws SystemException {
        if (_state == TMState.CLOSING || _state ==  TMState.CLOSED) {
            throw new SystemException("TM is shuting down");
        }
    }

    private static final int MAX_TMNAME_LENGTH = Xid.MAXGTRIDSIZE - 9;

    private GlobalXid genGlobalXid() throws SystemException {
        try {

        GlobalXid xid = new GlobalXid();
        xid.setFormatId(FORMATID);

        byte[] tmn = _tmName.getBytes("UTF-8");
        if (tmn.length > (MAX_TMNAME_LENGTH)) {
            throw new SystemException(
            "TM name "+_tmName+" byte length exceeds "+MAX_RMNAME_LENGTH);
        }
        UID uid = new UID();

        ByteBuffer bf = ByteBuffer.wrap(new byte[Xid.MAXGTRIDSIZE]);
        bf.put((byte)tmn.length);
        bf.put(tmn);
        bf.putLong(uid.longValue());
        xid.setGlobalTransactionId(bf.array());
        return xid;

        } catch (Exception e) {
        if (e instanceof SystemException) throw (SystemException)e;
        SystemException se = new SystemException(e.getMessage());
        se.initCause(e);
        throw se;
        }
    }

    private static final int MAX_RMNAME_LENGTH = Xid.MAXBQUALSIZE - 2;

    protected BranchXid genBranchXid(GlobalXid xid, String rmName,
                                     String className, byte nth)
                                     throws SystemException {
        try {

        BranchXid bxid = new BranchXid();
        bxid.copy(xid);
        byte[] rn = rmName.getBytes("UTF-8");  
        if (rn.length > MAX_RMNAME_LENGTH) {
            throw new SystemException(
            "Resource manager name "+rmName+" byte length exceeds "+MAX_RMNAME_LENGTH);
        }
        byte[] cn = className.getBytes("UTF-8");
        int cnlen = MAX_RMNAME_LENGTH - rn.length;
        if (cnlen > cn.length) cnlen = cn.length;

        if (_logger.isLoggable(Level.FINE)) {
        _logger.log(Level.INFO, "genBranchXid:rmName="+rmName+"["+rn.length+"], className="+className+"["+cnlen+"]"); 
        }

        ByteBuffer bf = ByteBuffer.wrap(new byte[Xid.MAXBQUALSIZE]);
        bf.put((byte)rn.length);
        bf.put(rn);
        bf.put(cn, 0, cnlen);
        bf.put(nth);
        bxid.setBranchQualifier(bf.array());
        return bxid;

        } catch (Exception e) {
        if (e instanceof SystemException) throw (SystemException)e;
        SystemException se = new SystemException(e.getMessage());
        se.initCause(e);
        throw se;
        }
    }

    public TxLog getTxLog() {
        return _txlog;
    }

    private static String stateString(TMState s) {
        switch(s) {
            case UNINITIALIZED: return "UNINITIALIZED";
            case INITIALIZED: return "INITIALIZED";
            case CLOSING: return "CLOSING";
            case CLOSED: return "CLOSED";
            default: return "UNKNOWN";
        }
    }

    public String toString() {
        return "TM:"+_tmName+"["+stateString(_state)+"]";
    }

    public void setLogger(Logger logger) {
        _logger = logger;
    }

    protected Logger getLogger() {
        return _logger;
    }

}
