/FileExtraction/Java_unseen/dlitz_resin/modules/kernel/src/com/caucho/transaction/UserTransactionImpl.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.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.UserTransaction;
import javax.transaction.xa.Xid;

import com.caucho.inject.Module;
import com.caucho.util.L10N;

/**
 * Implementation of the UserTransactionImpl for a thread instance.
 */
public class UserTransactionImpl
  implements UserTransaction
{
  private static final Logger log
    = Logger.getLogger(UserTransactionImpl.class.getName());
  private static final L10N L = new L10N(UserTransactionImpl.class);

  private TransactionManagerImpl _transactionManager;

  private ArrayList<ManagedResource> _resources
    = new ArrayList<ManagedResource>();
  private ArrayList<ManagedXAResource> _xaResources
    = new ArrayList<ManagedXAResource>();

  private boolean _isInContext;
  private boolean _isTransactionActive;
  
  /**
   * Creates the proxy.
  */
  public UserTransactionImpl(TransactionManagerImpl tm)
  {
    _transactionManager = tm;
  }
  
  /**
   * Sets the transaction's timeout.
   */
  @Override
  public void setTransactionTimeout(int seconds)
    throws SystemException
  {
    _transactionManager.setTransactionTimeout(seconds);
  }
  
  /**
   * Gets the transaction's status
   */
  @Override
  public int getStatus()
    throws SystemException
  {
    return _transactionManager.getStatus();
  }

  /**
   * inContext is valid within a managed UserTransactionImpl context, e.g
   * in a webApp, but not in a cron job.
   */
  public boolean isInContext()
  {
    return _isInContext;
  }

  /**
   * inContext is valid within a managed UserTransactionImpl context, e.g
   * in a webApp, but not in a cron job.
   */
  public void setInContext(boolean isInContext)
  {
    _isInContext = isInContext;
  }
  
  public boolean isActive()
  {
    return _isTransactionActive;
  }

  /**
   * Enlist a resource.
   */
  public void enlistResource(ManagedResource resource)
    throws SystemException, RollbackException
  {
    if (resource == null)
      throw new NullPointerException();
    
    if (_resources.contains(resource))
      return;
    
    TransactionImpl xa = _transactionManager.getTransaction();
    if (xa != null && xa.isActive()) {
      ManagedXAResource xaResource = resource.getXAResource();

      if (xaResource != null)
        enlistXaResource(xa, xaResource);
    }
    
    _resources.add(resource);
  }

  private void enlistXaResource(Transaction xa,
                                ManagedXAResource xaResource)
    throws SystemException, RollbackException
  {
    if (xaResource == null)
      return;
    else if (! xaResource.supportsTransaction()) {
      // server/164j
      return;
    }
    
    if (_xaResources.contains(xaResource))
      return;
    
    xaResource.setTransaction(this);

    if (xa instanceof TransactionImpl) {
      TransactionImpl xaImpl = (TransactionImpl) xa;
      
      // server/164l
      if (xaImpl.allowLocalTransactionOptimization())
        xaResource.enableLocalTransactionOptimization(true);
    }

    if (xaResource.getXid() == null)
      xa.enlistResource(xaResource);
    
    _xaResources.add(xaResource);
  }

  /**
   * Delist a resource.
   */
  public void delistResource(ManagedResource resource)
  {
    _resources.remove(resource);
  }
  
  @Module
  public ArrayList<ManagedXAResource> getXaResources()
  {
    return _xaResources;
  }

  /**
   * Returns the XID.
   */
  public Xid getXid()
    throws SystemException, RollbackException
  {
    TransactionImpl xa = (TransactionImpl) _transactionManager.getTransaction();

    if (xa != null)
      return xa.getXid();
    else
      return null;
  }

  /**
   * Returns the number of currently enlisted resources.
   */
  public int getEnlistedResourceCount()
    throws SystemException, RollbackException
  {
    TransactionImpl xa = (TransactionImpl) _transactionManager.getTransaction();

    if (xa != null)
      return xa.getEnlistedResourceCount();
    else
      return 0;
  }
  
  /**
   * Start the transaction.
   */
  @Override
  public void begin()
    throws NotSupportedException, SystemException
  {
    if (_isTransactionActive)
      throw new NotSupportedException(L.l("UserTransaction.begin() is not allowed because an active transaction already exists.  This may be caused by either a missing commit/rollback or a nested begin().  Nested transactions are not supported."));
    
    _transactionManager.begin();
    _isTransactionActive = true;
    boolean isOkay = false;

    try {
      TransactionImpl xa = (TransactionImpl) _transactionManager.getTransaction();
      xa.setUserTransaction(this);
    
      _xaResources.clear();
    
      // enlist "cached" resources
      int length = _resources.size();

      for (int i = 0; i < length; i++) {
        ManagedResource resource = _resources.get(i);

        for (int j = _xaResources.size() - 1; j >= 0; j--) {
          ManagedXAResource xaResource = _xaResources.get(j);

          if (xaResource.share(resource)) {
            break;
          }
        }

        ManagedXAResource xaResource = resource.getXAResource();
        if (xaResource != null && ! _xaResources.contains(xaResource))
          _xaResources.add(xaResource);
      }

      for (int i = 0; i < _xaResources.size(); i++) {
        ManagedXAResource xaResource = _xaResources.get(i);

        xaResource.enableLocalTransactionOptimization(_xaResources.size() == 1);

        try {
          xa.enlistResource(xaResource);
        } catch (Exception e) {
          String message = L.l("Failed to begin UserTransaction due to: {0}", e);
          log.log(Level.SEVERE, message, e);

          throw new SystemException(message);
        }
      }

      isOkay = true;
    } finally {
      if (! isOkay) {
        Exception e1 = new IllegalStateException(L.l("Rolling back transaction from failed begin."));
        e1.fillInStackTrace();
        log.log(Level.WARNING, e1.toString(), e1);

        // something has gone very wrong
        _isTransactionActive = false;

        ArrayList<ManagedXAResource> xaResources
          = new ArrayList<ManagedXAResource>(_xaResources);
        _xaResources.clear();
        
        // XXX: need to free _resources as well
        _resources.clear();

        for (int i = 0; i < xaResources.size(); i++) {
          try {
            ManagedXAResource item = xaResources.get(i);

            item.abortConnection();

            item.destroy();
          } catch (Throwable e) {
            log.log(Level.FINE, e.toString(), e);
          }
        }

        _transactionManager.rollback();
      }
    }
  }

  /**
   * Suspends the transaction.
   */
  public UserTransactionSuspendState userSuspend()
  {
    if (! _isTransactionActive)
      throw new IllegalStateException(L.l("UserTransaction.suspend may only be called in a transaction, but no transaction is active."));

    _isTransactionActive = false;
    
    UserTransactionSuspendState state;
    state = new UserTransactionSuspendState(_xaResources);
    _xaResources.clear();

    return state;
  }

  /**
   * Resumes the transaction.
   */
  public void userResume(UserTransactionSuspendState state)
  {
    if (_isTransactionActive)
      throw new IllegalStateException(L.l("UserTransaction.resume may only be called outside of a transaction, because the resumed transaction must not conflict with an active transaction."));

    _isTransactionActive = true;

    _xaResources.addAll(state.getXAResources());
  }
  
  /**
   * Marks the transaction as rollback only.
   */
  @Override
  public void setRollbackOnly()
    throws IllegalStateException, SystemException
  {
    _transactionManager.setRollbackOnly();
  }
  
  /**
   * Marks the transaction as rollback only.
   */
  public void setRollbackOnly(Exception e)
    throws IllegalStateException
  {
    _transactionManager.setRollbackOnly(e);
  }
  
  /**
   * Commits the transaction
   */
  @Override
  public void commit()
    throws IllegalStateException, RollbackException, HeuristicMixedException,
           HeuristicRollbackException, SecurityException, SystemException
  {
    try {
      if (! _isTransactionActive)
        throw new IllegalStateException("UserTransaction.commit() requires an active transaction.  Either the UserTransaction.begin() is missing or the transaction has already been committed or rolled back.");

      _transactionManager.commit();
    } finally {
      _xaResources.clear();

      _isTransactionActive = false;
    }
  }
  
  /**
   * Rolls the transaction back
   */
  @Override
  public void rollback()
    throws IllegalStateException, SecurityException, SystemException
  {
    try {
      _transactionManager.rollback();
    } finally {
      _isTransactionActive = false;
      
      _xaResources.clear();
    }
  }

  /**
   * Aborts the transaction.
   */
  public void abortTransaction()
    throws IllegalStateException
  {
    IllegalStateException exn = null;

    _isInContext = false;
    
    boolean isTransactionActive = _isTransactionActive;
    _isTransactionActive = false;

    if (! isTransactionActive && _xaResources.size() > 0) {
      Exception e = new IllegalStateException("Internal error: user transaction pool broken because poolItems exist, but no transaction is active.");
      log.log(Level.WARNING, e.toString(), e);
    }
    
    _xaResources.clear();

    if (isTransactionActive) {
      try {
        exn = new IllegalStateException(L.l("Transactions must have a commit() or rollback() in a finally block."));

        log.warning("Rolling back unclosed transaction.  All transactions must have a commit() or rollback() in a finally block.");

        _transactionManager.addUnclosedTransaction("Rolling back unclosed transaction.");
        _transactionManager.rollback();
      } catch (Throwable e) {
        log.log(Level.WARNING, e.toString());
      }

    }

    _xaResources.clear();
    
    exn = clearDanglingResources(exn);

    try {
      _transactionManager.setTransactionTimeout(0);
    } catch (Throwable e) {
      log.log(Level.WARNING, e.toString(), e);
    }

    if (exn != null)
      throw exn;
  }
  
  private IllegalStateException clearDanglingResources(IllegalStateException exn)
  {
    if (_resources.size() == 0)
      return exn;

    ArrayList<ManagedResource> resourceList
      = new ArrayList<ManagedResource>(_resources);
    
    _resources.clear();

    boolean hasWarning = false;
    for (ManagedResource resource : resourceList) {
      if (! resource.isCloseDanglingConnections())
        continue;

      if (! hasWarning) {
        hasWarning = true;

        log.warning("Closing dangling resources.  Applications must close all resources in a finally block.");
      }

      try {
        IllegalStateException stackTrace = resource.getAllocationStackTrace();

        if (stackTrace != null)
          log.log(Level.WARNING, stackTrace.getMessage(), stackTrace);
        else {
          // start saving the allocation stack trace.
          resource.setSaveAllocationStackTrace(true);
        }
        
        String msg = L.l("Resource {0} was not closed. Applications must close all resources in a finally block.",
                         resource.getUserConnection());

        if (exn == null)
          exn = new IllegalStateException(msg);
        
        _transactionManager.addUnclosedResource(msg);

        resource.abortConnection();
      } catch (Throwable e) {
        log.log(Level.WARNING, e.toString(), e);
      }
    }

    return exn;
  }

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

