/FileExtraction/Java_unseen/dlitz_resin/modules/kernel/src/com/caucho/transaction/TransactionManagerImpl.java
/*
 * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.transaction;

import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;

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

import com.caucho.config.inject.SingletonBindingHandle;
import com.caucho.config.types.Period;
import com.caucho.env.meter.MeterService;
import com.caucho.env.meter.TimeSensor;
import com.caucho.loader.Environment;
import com.caucho.transaction.xalog.AbstractXALogManager;
import com.caucho.transaction.xalog.AbstractXALogStream;
import com.caucho.util.Alarm;
import com.caucho.util.Crc64;
import com.caucho.util.L10N;
import com.caucho.util.RandomUtil;

/**
 * Implementation of the transaction manager.
 */
public class TransactionManagerImpl
  implements TransactionManager,
             Serializable
{
  private static final long serialVersionUID = 1L;
  private static L10N L = new L10N(TransactionManagerImpl.class);
  private static Logger log
    = Logger.getLogger(TransactionManagerImpl.class.getName());

  private static TransactionManagerImpl _tm = new TransactionManagerImpl();

  private long _serverId;

  private long _randomId = RandomUtil.getRandomLong();

  private AtomicLong _sequence = new AtomicLong(Alarm.getCurrentTime());

  private AbstractXALogManager _xaLogManager;

  private TransactionSynchronizationRegistry _syncRegistry
    = new TransactionSynchronizationRegistryImpl(this);

  // Thread local is dependent on the transaction manager.
  private ThreadLocal<TransactionImpl> _threadTransaction = new ThreadLocal<TransactionImpl>();

  private ArrayList<WeakReference<TransactionImpl>> _transactionList
    = new ArrayList<WeakReference<TransactionImpl>>();

  private long _timeout = -1;

  // statistics and counters
  // private TransactionManagerAdmin _admin;

  private TimeSensor _commitSensor
    = MeterService.createTimeMeter("Resin|XA|Commit");

  private TimeSensor _rollbackSensor
    = MeterService.createTimeMeter("Resin|XA|Rollback");

  private AtomicInteger _transactionCount
    = new AtomicInteger();

  private AtomicLong _commitCount
    = new AtomicLong();

  private AtomicLong _commitResourceFailCount
    = new AtomicLong();

  private AtomicLong _rollbackCount
    = new AtomicLong();

  private AtomicLong _unclosedResourceCount
    = new AtomicLong();

  private String _lastUnclosedResourceMessage;

  private AtomicLong _unclosedTransactionCount
    = new AtomicLong();

  public TransactionManagerImpl()
  {
    new TransactionManagerAdmin(this);
  }

  /**
   * Returns the local transaction manager.
   */
  public static TransactionManagerImpl getInstance()
  {
    return _tm;
  }

  /**
   * Returns the local transaction manager.
   */
  public static TransactionManagerImpl getLocal()
  {
    return _tm;
  }

  /**
   * Sets the timeout.
   */
  public void setTimeout(Period timeout)
  {
    _timeout = timeout.getPeriod();
  }

  /**
   * Gets the timeout.
   */
  public long getTimeout()
  {
    return _timeout;
  }

  /**
   * Sets the XA log manager.
   */
  public void setXALogManager(AbstractXALogManager xaLogManager)
  {
    _xaLogManager = xaLogManager;
  }

  /**
   * Returns the synchronization registry
   */
  public TransactionSynchronizationRegistry getSyncRegistry()
  {
    return _syncRegistry;
  }

  /**
   * Create a new transaction and associate it with the thread.
   */
  @Override
  public void begin() throws NotSupportedException, SystemException
  {
    getCurrent().begin();
  }

  /**
   * Creates a new transaction id.
   */
  XidImpl createXID()
  {
    return new XidImpl(getServerId(), _randomId, _sequence.incrementAndGet());
  }

  /**
   * Creates a new transaction id.
   */
  AbstractXALogStream getXALogStream()
  {
    if (_xaLogManager != null)
      return _xaLogManager.getStream();
    else
      return null;
  }

  /**
   * Returns the server id.
   */
  private long getServerId()
  {
    if (_serverId == 0) {
      String server = (String) Environment.getAttribute("caucho.server-id");

      if (server == null)
        _serverId = 1;
      else
        _serverId = Crc64.generate(server);
    }

    return _serverId;
  }

  /**
   * Returns the transaction for the current thread.
   */
  @Override
  public TransactionImpl getTransaction()
  {
    TransactionImpl trans = _threadTransaction.get();

    if (trans == null || trans.getStatus() == Status.STATUS_NO_TRANSACTION
        || trans.getStatus() == Status.STATUS_UNKNOWN || trans.isSuspended()) {
      return null;
    } else {
      return trans;
    }
  }

  /**
   * Suspend the transaction.
   */
  @Override
  public Transaction suspend() throws SystemException
  {
    TransactionImpl trans = _threadTransaction.get();

    if (trans == null
        || (!trans.hasResources() && (trans.getStatus() == Status.STATUS_NO_TRANSACTION || trans
            .getStatus() == Status.STATUS_UNKNOWN)))
      return null;

    _threadTransaction.set(null);
    trans.suspend();

    return trans;
  }

  /**
   * Resume the transaction.
   */
  @Override
  public void resume(Transaction tobj) throws InvalidTransactionException,
      SystemException
  {
    Transaction old = _threadTransaction.get();

    if (old != null && old.getStatus() != Status.STATUS_NO_TRANSACTION)
      throw new SystemException(L.l(
          "can't resume transaction with active transaction {0}", String
              .valueOf(old)));

    TransactionImpl impl = (TransactionImpl) tobj;

    impl.resume();

    _threadTransaction.set(impl);
  }

  /**
   * Force any completion to be a rollback.
   */
  @Override
  public void setRollbackOnly() throws SystemException
  {
    getCurrent().setRollbackOnly();
  }

  /**
   * Force any completion to be a rollback.
   */
  public void setRollbackOnly(Exception e)
  {
    getCurrent().setRollbackOnly(e);
  }

  /**
   * Returns the transaction's status
   */
  @Override
  public int getStatus() throws SystemException
  {
    return getCurrent().getStatus();
  }

  /**
   * sets the timeout for the transaction
   */
  public void setTransactionTimeout(int seconds) throws SystemException
  {
    getCurrent().setTransactionTimeout(seconds);
  }

  /**
   * Commit the transaction.
   */
  @Override
  public void commit() throws RollbackException, HeuristicMixedException,
      HeuristicRollbackException, SystemException
  {
    getCurrent().commit();
  }

  /**
   * Rollback the transaction.
   */
  @Override
  public void rollback()
  {
    getCurrent().rollback();
  }

  /**
   * Returns the current TransactionImpl, creating if necessary.
   *
   * <p/>
   * The TransactionImpl is not an official externally visible Transaction if
   * the status == NO_TRANSACTION.
   */
  public TransactionImpl getCurrent()
  {
    TransactionImpl trans = _threadTransaction.get();

    if (trans == null || trans.isDead()) {
      trans = new TransactionImpl(this);
      _threadTransaction.set(trans);

      addTransaction(trans);
    }

    return trans;
  }

  private void addTransaction(TransactionImpl trans)
  {
    synchronized (_transactionList) {
      for (int i = _transactionList.size() - 1; i >= 0; i--) {
        WeakReference<TransactionImpl> ref = _transactionList.get(i);

        if (ref.get() == null)
          _transactionList.remove(i);
      }

      _transactionList.add(new WeakReference<TransactionImpl>(trans));
    }
  }

  /**
   * Returns the corresponding user transaction.
   */
  public void recover(XAResource xaRes) throws XAException
  {
    Xid [] xids = null;

    try {
      xids = xaRes.recover(XAResource.TMSTARTRSCAN | XAResource.TMENDRSCAN);
    } catch (XAException e) {
      int code = e.errorCode;

      if (e.getMessage() == null || e.getMessage().isEmpty()) {
        XAException e1 = new XAException(L.l("Error during recovery (code=" + code + ")", e));
        e1.errorCode = code;

        throw e1;
      }

      throw e;
    }

    if (xids == null)
      return;

    for (int i = 0; i < xids.length; i++) {
      byte []global = xids[i].getGlobalTransactionId();

      if (global.length != XidImpl.GLOBAL_LENGTH)
        continue;

      XidImpl xidImpl = new XidImpl(xids[i].getGlobalTransactionId());

      if (! xidImpl.isSameServer(getServerId()))
        continue;

      if (_xaLogManager != null && _xaLogManager.hasCommittedXid(xidImpl)) {
        log.fine(L.l("XAResource {0} commit xid {1}", xaRes, xidImpl));

        try {
          xaRes.commit(xids[i], false);
        } catch (Throwable e) {
          log.log(Level.WARNING, e.toString(), e);
        }
      } else {
        log.fine(L.l("XAResource {0} rollback/forget xid {1}", xaRes, xidImpl));

        boolean isValid = false;
        
        try {
          // #4748
          xaRes.rollback(xids[i]);
          // xaRes.forget(xids[i]);
          isValid = true;
        } catch (Throwable e) {
          if (log.isLoggable(Level.FINER))
            log.log(Level.FINER, e.toString(), e);
          else
            log.fine(e.toString());
        }
        
        if (! isValid) {
          try {
            // #4748
            xaRes.forget(xids[i]);
            isValid = true;
          } catch (Throwable e) {
            if (log.isLoggable(Level.FINER))
              log.log(Level.FINER, e.toString(), e);
            else
              log.fine(e.toString());
          }
        }
        
        if (! isValid) {
          // Oracle: If forget fails, try committing so we don't stay in a stuck
          // state
          try {
            xaRes.commit(xids[i], false);
            isValid = true;
          } catch (Throwable e1) {
            log.log(Level.WARNING, e1.toString(), e1);
          }
        }
      }
    }
  }

  /**
   * Flushes the log stream (primarily for QA).
   */
  public void flush()
  {
    if (_xaLogManager != null)
      _xaLogManager.flush();
  }

  /**
   * Statistics
   */

  long beginTransactionTime()
  {
    _transactionCount.incrementAndGet();

    return Alarm.getCurrentTime();
  }

  int getTransactionCount()
  {
    return _transactionCount.get();
  }

  void addCommitResourceFail()
  {
    _commitResourceFailCount.incrementAndGet();
  }

  long getCommitResourceFailCount()
  {
    return _commitResourceFailCount.get();
  }

  long getCommitCount()
  {
    return _commitCount.get();
  }

  void endCommitTime(long startTime)
  {
    _transactionCount.decrementAndGet();

    _commitCount.incrementAndGet();
    _commitSensor.add(startTime);
  }

  long getRollbackCount()
  {
    return _rollbackCount.get();
  }

  void endRollbackTime(long startTime)
  {
    _transactionCount.decrementAndGet();

    _rollbackCount.incrementAndGet();
    _rollbackSensor.add(startTime);
  }

  void addUnclosedResource(String message)
  {
    _unclosedResourceCount.incrementAndGet();
    _lastUnclosedResourceMessage = message;
  }

  long getUnclosedResourceCount()
  {
    return _unclosedResourceCount.get();
  }

  String getLastUnclosedResourceMessage()
  {
    return _lastUnclosedResourceMessage;
  }

  void addUnclosedTransaction(String message)
  {
    _unclosedTransactionCount.incrementAndGet();
    // _lastUnclosedTransactionMessage = message;
  }

  long getUnclosedTransactionCount()
  {
    return _unclosedTransactionCount.get();
  }

  /**
   * Handles the case where a class loader is dropped.
   */
  public void destroy()
  {
    AbstractXALogManager xaLogManager = _xaLogManager;
    _xaLogManager = null;

    if (xaLogManager != null)
      xaLogManager.close();

    _serverId = 0;

    ArrayList<TransactionImpl> xaList = new ArrayList<TransactionImpl>();

    synchronized (_transactionList) {
      for (int i = _transactionList.size() - 1; i >= 0; i--) {
        WeakReference<TransactionImpl> ref = _transactionList.get(i);
        TransactionImpl xa = ref.get();

        if (xa != null)
          xaList.add(xa);
      }
    }

    for (TransactionImpl xa : xaList) {
      try {
        xa.rollback();
      } catch (Throwable e) {
        log.log(Level.WARNING, e.toString(), e);
      }
    }
  }

  /**
   * Clearing for test purposes.
   */
  public void testClear()
  {
    _serverId = 0;
    _timeout = -1;
    AbstractXALogManager logManager = _xaLogManager;
    _xaLogManager = null;
    _sequence.set(Alarm.getCurrentTime());
    // _randomId = RandomUtil.getRandomLong();

    if (logManager != null)
      logManager.close();
  }

  /**
   * Serialize to a handle
   */
  private Object writeReplace()
  {
    return new SingletonBindingHandle(TransactionManager.class);
  }

  public String toString()
  {
    return getClass().getSimpleName() + "[]";
  }
}
