/*
 * Copyright (c) 1998-2012 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.env.dbpool;

import com.caucho.sql.ManagedConnectionImpl;
import com.caucho.transaction.ManagedResource;
import com.caucho.transaction.ManagedXAResource;
import com.caucho.transaction.UserTransactionImpl;
import com.caucho.transaction.XAExceptionWrapper;
import com.caucho.util.Alarm;
import com.caucho.util.CurrentTime;
import com.caucho.util.L10N;

import javax.resource.NotSupportedException;
import javax.resource.ResourceException;
import javax.resource.spi.ConnectionEvent;
import javax.resource.spi.ConnectionEventListener;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.LocalTransaction;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionFactory;
import javax.security.auth.Subject;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Manages a ManagedConnection in the pool. Each ManagedPoolItem corresponds
 * one-to-one to a ManagedConnection.
 *
 * The user's view of the pool item is a UserPoolItem. Because of transaction
 * sharing, it's possible for several UserPoolItems to refer to the same
 * ManagedPoolItem.
 */
class ManagedPoolItem implements ConnectionEventListener, ManagedXAResource {
  private static final L10N L = new L10N(ManagedPoolItem.class);
  private static final Logger log
    = Logger.getLogger(ManagedPoolItem.class.getName());

  private ConnectionPool _cm;

  private ManagedConnectionFactory _mcf;
  private ManagedConnection _mConn;

  private String _id;

  private XAResource _xaResource;
  private LocalTransaction _localTransaction;
  private int _defaultTransactionTimeout;
  private int _transactionTimeout;

  private Subject _subject;
  private ConnectionRequestInfo _requestInfo;

  final Object _shareLock = new Object();

  // The head shared connection for transaction
  // The UserPoolItem code is responsible for this field
  UserPoolItem _shareHead;

  // The other pool items joined transaction
  private ManagedPoolItem _xaHead;
  private ManagedPoolItem _xaNext;

  private boolean _hasConnectionError;
  private boolean _isPastActiveTime;

  private long _poolStartTime;
  private long _poolEventTime;

  private Xid _xid;
  private int _endFlags = -1;

  // flag forcing an XA transaction (for the local transaction optimization)
  private boolean _isXATransaction = true;

  // true if in local transaction
  private boolean _isLocalTransaction;

  private IllegalStateException _allocationStackTrace;

  //
  // statistics
  //

  private long _connectionStartTime;

  public ManagedPoolItem(ConnectionPool cm,
                         ManagedConnectionFactory mcf,
                         ManagedConnection conn)
  {
    _cm = cm;

    _id = _cm.generateId();

    _mcf = mcf;
    _mConn = conn;

    _poolStartTime = CurrentTime.getCurrentTime();
    _poolEventTime = CurrentTime.getCurrentTime();

    _connectionStartTime = cm.getConnectionTimeProbe().start();

    // Gets the resource object from the driver
    try {
      if (cm.isXATransaction()) {
        XAResource xaResource = conn.getXAResource();

        try {
          _defaultTransactionTimeout = xaResource.getTransactionTimeout();
        } catch (Throwable e) {
          log.log(Level.FINE, e.toString(), e);
        }

        _xaResource = xaResource;
      }
    } catch (NotSupportedException e) {
      _cm.setXATransaction(false);
      log.log(Level.FINER, e.toString(), e);
    } catch (Exception e) {
      log.log(Level.FINE, e.toString(), e);
    }

    if (_xaResource == null)
      _isXATransaction = false;

    // Gets the local transaction from the driver
    try {
      if (_cm.isLocalTransaction())
        _localTransaction = conn.getLocalTransaction();
    } catch (NotSupportedException e) {
      _cm.setLocalTransaction(false);
      log.log(Level.FINER, e.toString(), e);
    } catch (Exception e) {
      log.log(Level.FINER, e.toString(), e);
    }

    _mConn.addConnectionEventListener(this);

    if (log.isLoggable(Level.FINE))
      log.fine("create: " + this +
               "(active:" + _cm.getConnectionActiveCount() +
               ", total:" + _cm.getConnectionCount() + ")");
  }

  /**
   * Sets the subject.
   */
  public void setSubject(Subject subject)
  {
    _subject = subject;
  }

  /**
   * Sets the info.
   */
  public void setInfo(ConnectionRequestInfo info)
  {
    _requestInfo = info;
  }

  /**
   * Returns true if the connection is active.
   */
  public boolean isActive()
  {
    return _shareHead != null;
  }

  /**
   * Returns true if the connection is dead
   */
  public boolean isDead()
  {
    return _mConn == null;
  }

  /**
   * Returns the time of the last event.
   */
  public long getEventTime()
  {
    return _poolEventTime;
  }

  /**
   * Returns the time the connection was first used.
   */
  public long getStartTime()
  {
    return _poolStartTime;
  }

  /**
   * Sets the item's transaction.
   */
  @Override
  public void setTransaction(UserTransactionImpl transaction)
  {
    // _transaction = transaction;
  }

  /**
   * Make this connection active.
   *
   * @return true if the pool item is valid, false if it should be removed.
   */
  synchronized UserPoolItem toActive(Subject subject,
                                     ConnectionRequestInfo info,
                                     UserPoolItem oldPoolItem)
    throws ResourceException
  {
    long now = CurrentTime.getCurrentTime();

    long maxIdleTime = _cm.getMaxIdleTime();
    long maxPoolTime = _cm.getMaxPoolTime();

    if (_hasConnectionError) {
      return null;
    }
    else if (0 < maxIdleTime && _poolEventTime + maxIdleTime < now) {
      return null;
    }
    else if (0 < maxPoolTime && _poolStartTime + maxPoolTime < now) {
      return null;
    }
    else if (_shareHead != null)
      throw new IllegalStateException(L.l("trying to activate active pool item."));

    _poolEventTime = now;
    _isXATransaction = _xaResource != null; // disable LT-optim by default

    UserPoolItem userPoolItem = null;

    if (oldPoolItem != null) {
      Object uConn = oldPoolItem.getUserConnection();

      if (uConn != null)
        _mConn.associateConnection(uConn);

      oldPoolItem.associatePoolItem(this);

      userPoolItem = oldPoolItem;
    }
    else
      userPoolItem = new UserPoolItem(_cm, this);

    if (! isValid(subject, info, userPoolItem)) {
      return null;
    }

    _subject = subject;
    _requestInfo = info;
    userPoolItem.associate(this, _mcf, subject, info);

    if (log.isLoggable(Level.FINE))
      log.fine("allocate " + this);

    if (_cm.getSaveAllocationStackTrace())
      _allocationStackTrace = new IllegalStateException(L.l("Connection {0} allocation stack trace", this));

    return userPoolItem;
  }

  /**
   * Checks if the pool item is still valid.
   *
   * @return true if the pool item is valid, false if it should be removed.
   */
  boolean isValid()
  {
    synchronized (this) {
      long now = CurrentTime.getCurrentTime();

      long maxIdleTime = _cm.getMaxIdleTime();
      long maxPoolTime = _cm.getMaxPoolTime();
      long maxActiveTime = _cm.getMaxActiveTime();

      boolean isActive = isActive() || _xid != null;
      boolean isDead = false;

      if (! isActive && _hasConnectionError) {
        isDead = true;
        log.fine("closing pool item from connection error:" + this);
      }
      else if (! isActive &&
               0 < maxIdleTime && _poolEventTime + maxIdleTime < now) {
        isDead = true;
        log.fine("closing pool item from idle timeout:" + this);
      }
      else if (! isActive &&
               0 < maxPoolTime && _poolStartTime + maxPoolTime < now) {
        isDead = true;
        log.fine("closing pool item from pool timeout:" + this);
      }
      else if (isActive &&
               0 < maxActiveTime && _poolEventTime + maxActiveTime < now) {
        isDead = true;
        _isPastActiveTime = true;
        log.warning("closing pool item from active timeout:" + this);
      }

      if (isDead) {
        _hasConnectionError = true;
        return false;
      }
      else
        return true;
    }
  }

  /**
   * Use the item only if it's already been used for the current transaction
   * and is available.  allocateXA returns the same connection for the
   * following case:
   *
   * <pre>
   * UserTransaction.begin();
   *
   * conn = ds.getConnection();
   * ...
   * conn.close();
   *
   * conn = ds.getConnection();
   * ...
   * conn.close();
   * </pre>
   *
   * <p>Nested connections are not reused.
   *
   *
   * @return true if the pool item has been allocated
   */
  UserPoolItem allocateXA(ManagedConnectionFactory mcf,
                          Subject subject,
                          ConnectionRequestInfo info)
  {
    if (_mConn == null)      // already closed
      return null;
    else if (_subject != subject)
      return null;
    else if (_requestInfo != info)
      return null;
    else if (_mcf != mcf)
      return null;
    else if (_shareHead != null && ! _cm.isShareable()) // is currently in use
      return null;
    /* server/14g9, #2708
    else if (_hasConnectionError) // had a fatal error
      return null;
    */

    if (log.isLoggable(Level.FINER))
      log.finer("sharing xa-pool item: " + this);

    UserPoolItem userPoolItem = new UserPoolItem(_cm);
    userPoolItem.associate(this, _mcf, _subject, _requestInfo);

    return userPoolItem;
  }

  /**
   * Returns true if the tested pool item is the Xid leader.
   */
  boolean isJoin(ManagedPoolItem item)
  {
    if (this == item)
      return false;
    else if (_xid != item._xid)
      return false;
    else if (_mcf != item._mcf)
      return false;
    else
      return true;
  }

  /**
   * Try to share the connection.
   */
  @Override
  public boolean share(ManagedResource resource)
  {
    if (! (resource instanceof UserPoolItem))
      return false;

    UserPoolItem userPoolItem = (UserPoolItem) resource;

    if (this == userPoolItem.getOwnPoolItem())
      return true;
    else if (_mConn == null)           // already closed
      return false;
    else if (! _cm.isShareable()) // not shareable
      return false;
    else if (_mcf != userPoolItem.getManagedConnectionFactory())
      return false;
    else if (_subject != userPoolItem.getSubject())
      return false;
    else if (_requestInfo != userPoolItem.getInfo())
      return false;
    else if (_hasConnectionError) // had a fatal error
      return false;

    /*
    // skip for now
    if (true)
      return false;

    userPoolItem.associate(this, _mcf, _subject, _requestInfo);

    return true;
    */

    return false;
  }

  /**
   * Returns the managed connection.
   */
  ManagedConnection getManagedConnection()
  {
    return _mConn;
  }

  /**
   * Returns the user connection.
   */
  /*
  Object getUserConnection()
    throws ResourceException
  {
    return _userPoolItem.getUserConnection();
  }
  */

  /**
   * Returns the user connection.
   */
  Object allocateConnection()
    throws ResourceException
  {
    Object conn = _mConn.getConnection(_subject, _requestInfo);

    return conn;
  }

  /**
   * Returns true for a valid connection.
   */
  boolean isValid(Subject subject,
                  ConnectionRequestInfo requestInfo,
                  UserPoolItem userPoolItem)
  {
    try {
      ManagedConnection mConn = getManagedConnection();

      if (mConn == null) {
        return false;
      }

      Object userConn = userPoolItem.getUserConnection();

      if (userConn == null) {
        userConn = mConn.getConnection(subject, requestInfo);

        userPoolItem.setUserConnection(userConn);
      }

      return userConn != null;
    } catch (ResourceException e) {
      log.log(Level.WARNING, e.toString(), e);

      return false;
    }
  }

  /**
   * Returns the XA resource.
   */
  @Override
  public void enableLocalTransactionOptimization(boolean enableOptimization)
  {
    if (_xaResource == null)
      _isXATransaction = false;
    else if (_localTransaction == null)
      _isXATransaction = true;
    else if (! _cm.isLocalTransactionOptimization())
      _isXATransaction = true;
    else if (! _cm.isShareable())
      _isXATransaction = true;
    else
      _isXATransaction = ! enableOptimization;
  }

  /**
   * Returns true if the pooled connection supports transactions.
   */
  @Override
  public boolean supportsTransaction()
  {
    // server/164j
    return _xaResource != null || _localTransaction != null;
  }

  /**
   * Returns the XA resource.
   */
  XAResource getXAResource()
  {
    return _xaResource;
  }

  /**
   * Returns the Xid resource.
   */
  @Override
  public Xid getXid()
  {
    return _xid;
  }

  /**
   * Notifies that an application has closed the connection.
   *
   * Remove and close the UserPoolItem associated with the PoolItem. If it's
   * the last UserPoolItem, move to the idle state.
   */
  @Override
  public void connectionClosed(ConnectionEvent event)
  {
    Object handle = event.getConnectionHandle();

    if (! _hasConnectionError && handle == null && _shareHead != null) {
      log.fine(L.l("JCA close event '{0}' for {1} did not have a connection handle.  Please notify the JCA resource provider.",
                  event, _mConn));
    }

    UserPoolItem ptr = _shareHead;
    UserPoolItem prev = null;

    while (ptr != null) {
      UserPoolItem next = ptr.getShareNext();

      Object userConn = ptr.getUserConnection();

      if (userConn == handle || handle == null) {
        if (prev != null)
          prev.setShareNext(next);
        else
          _shareHead = next;

        ptr.close();
      }
      else
        prev = ptr;

      ptr = next;
    }

    if (_shareHead == null) {
      toIdle();
      return;
    }
  }

  /**
   * Notifies that a local transaction has started.
   */
  @Override
  public void localTransactionStarted(ConnectionEvent event)
  {
    if (_isLocalTransaction || _xid != null)
      throw new IllegalStateException(L.l("attempted to start local transaction while transaction is in progress."));

    // server/30c4, #2627
    _isLocalTransaction = true;
  }

  /**
   * Notifies that a local transaction has committed.
   */
  @Override
  public void localTransactionCommitted(ConnectionEvent event)
  {
    if (_isLocalTransaction) {
    }
    else if (_xid != null)
      throw new IllegalStateException(L.l("attempted to commit() local transaction from an active XA transaction."));
    else
      throw new IllegalStateException(L.l("attempted to commit() with no active local transaction."));

    // #2627, server/30c4
    _isLocalTransaction = false;
  }

  /**
   * Notifies that a local transaction has rolled back.
   */
  @Override
  public void localTransactionRolledback(ConnectionEvent event)
  {
    if (_xid != null)
      throw new IllegalStateException(L.l("attempted to rollback() local transaction from an active XA transaction."));
    else if (! _isLocalTransaction)
      throw new IllegalStateException(L.l("attempted to rollback() with no active local transaction."));

    // #2627, server/30c4
    _isLocalTransaction = false;
  }

  /**
   * Notifies that a connection error has occurred.
   */
  @Override
  public void connectionErrorOccurred(ConnectionEvent event)
  {
    _hasConnectionError = true;
  }

  /**
   * Notifies that a connection error has occurred.
   */
  public void setConnectionError()
  {
    _hasConnectionError = true;
  }

  /**
   * Returns true if there was a connection error.
   */
  public boolean isConnectionError()
  {
    return _hasConnectionError;
  }

  /**
   * Returns true if connection exceeded max-active-time
   */
  public boolean isPastActiveTime()
  {
    return _isPastActiveTime;
  }

  /**
   * Returns the allocation stack trace.
   */
  public IllegalStateException getAllocationStackTrace()
  {
    return _allocationStackTrace;
  }

  /**
   * Returns true if there is a connection error.
   */

  // XAResource stuff

  /**
   * identity of resources
   */
  @Override
  public boolean isSameRM(XAResource resource)
    throws XAException
  {
    if (! (resource instanceof ManagedPoolItem))
      return false;

    ManagedPoolItem poolItem = (ManagedPoolItem) resource;

    //if (_cm == poolItem._cm)
    //  return true;

    if (_xaResource == null)
      return false;

    boolean isSameRM = _xaResource.isSameRM(poolItem._xaResource);

    if (log.isLoggable(Level.FINER))
      log.finer("isSameRM->" + isSameRM + " " + _xaResource);

    return isSameRM;
  }

  /**
   * starts work on a transaction branch
   */
  @Override
  public void start(Xid xid, int flags)
    throws XAException
  {
    if (_xid != null) {
      if (log.isLoggable(Level.FINER))
        log.finer("connection pool start XA: rejoin " + this);

      return;
    }

    if (flags == TMJOIN && _xid == null) {
      // TMJOIN means the resource manager is managing more than one
      // connection.  The delegates tie the PoolItems managed by
      // the same resource manager together.

      _xid = xid;

      UserTransactionImpl trans = _cm.getTransaction();

      if (trans != null) {
        ManagedPoolItem xaHead = _cm.findJoin(trans, this);

        if (xaHead != null) {
          _xaNext = xaHead._xaNext;
          _xaHead = xaHead;
          xaHead._xaNext = this;
        }
      }
    }

    // local transaction optimization
    if (! _isXATransaction
        && flags != TMJOIN
        && _localTransaction != null) {
      try {
        if (log.isLoggable(Level.FINER))
          log.finer("begin-local-XA: " + xid + " " + _localTransaction);

        _localTransaction.begin();
      } catch (ResourceException e) {
        throw new XAExceptionWrapper(e);
      }

      _xid = xid;

      return;
    }

    if (_xaResource != null) {
      if (log.isLoggable(Level.FINER))
        log.finer("start-XA: " + xid + " " + _xaResource);

      _xaResource.start(xid, flags);
      _isXATransaction = true;
    }
    else {
      if (log.isLoggable(Level.FINER))
        log.finer("start-XA with non XA resource: " + xid + " " + _xaResource);
    }

    _xid = xid;
  }

  /**
   * Sets the transaction timeout
   */
  @Override
  public boolean setTransactionTimeout(int seconds)
    throws XAException
  {
    if (seconds == _transactionTimeout)
      return true;

    XAResource xaResource = _xaResource;

    _transactionTimeout = seconds;

    if (xaResource == null)
      return true;
    else if (seconds == 0)
      return xaResource.setTransactionTimeout(_defaultTransactionTimeout);
    else
      return xaResource.setTransactionTimeout(seconds);
  }

  /**
   * Returns the timeout of the underlying resource.
   */
  @Override
  public int getTransactionTimeout()
    throws XAException
  {
    return _transactionTimeout;
  }

  /**
   * forget about the transaction
   */
  @Override
  public void forget(Xid xid)
    throws XAException
  {
    try {
      if (_isXATransaction)
        _xaResource.forget(xid);
    } finally {
      clearXid();
    }
  }

  /**
   * Vote using phase-1 of the 2-phase commit.
   */
  @Override
  public int prepare(Xid xid)
    throws XAException
  {
    if (_endFlags != -1) {
      int endFlags = _endFlags;
      _endFlags = -1;

      if (_isXATransaction)
        endResource(xid, endFlags);
    }

    if (_isXATransaction) {
      try {
        if (log.isLoggable(Level.FINER))
          log.finer("prepare-XA: " + xid + " " + _xaResource);

        int result = _xaResource.prepare(xid);

        if (result == XA_RDONLY) {
          if (_xaResource != null)
            _isXATransaction = true;

          clearXid();
        }

        return result;
      } catch (XAException e) {
        if (log.isLoggable(Level.FINER))
          log.finer("failed prepare-XA: " + xid + " " + _xaResource + " " + e);

        throw e;
      }
    }
    else
      return XA_OK;
  }

  /**
   * recover the transaction
   */
  @Override
  public Xid[]recover(int flag)
    throws XAException
  {
    if (_isXATransaction)
      return _xaResource.recover(flag);
    else
      return null;
  }

  /**
   * Ends work with the resource.  Called before commit/rollback.
   */
  @Override
  public void end(Xid xid, int flags)
    throws XAException
  {
    _endFlags = flags;

    // XXX: In theory, drop the _xid.  The underlying XADataSource
    // can handle combining the connections itself.

    // Don't call the underlying _xaResource.end.  The commit or rollback
    // will handle that automatically.
  }

  /**
   * rollback the resource
   */
  @Override
  public void rollback(Xid xid)
    throws XAException
  {
    try {
      int endFlags = _endFlags;
      _endFlags = -1;

      if (endFlags != -1 && _isXATransaction) {
        boolean isValid = false;

        try {
          endResource(xid, endFlags);
          isValid = true;
        } finally {
          if (! isValid)
            _xaResource.rollback(xid);
        }
      }

      if (log.isLoggable(Level.FINER))
        log.finer("connection pool rollback XA: " + this);

      if (_isXATransaction)
        _xaResource.rollback(xid);
      else if (_localTransaction != null) {
        try {
          _isLocalTransaction = false;
          _localTransaction.rollback();
        } catch (ResourceException e) {
          throw new XAExceptionWrapper(e);
        }
      }
    } finally {
      if (_xaResource != null)
        _isXATransaction = true;

      clearXid();
    }
  }

  /**
   * commit the resource
   */
  @Override
  public void commit(Xid xid, boolean onePhase)
    throws XAException
  {
    boolean logFiner = log.isLoggable(Level.FINER);

    try {
      int endFlags = _endFlags;
      _endFlags = -1;

      if (endFlags != -1 && _isXATransaction) {
        boolean isValid = false;

        try {
          endResource(xid, endFlags);
          isValid = true;
        } finally {
          if (! isValid)
            _xaResource.rollback(xid);
        }
      }

      if (_isXATransaction) {
        if (logFiner) {
          log.finer("commit-XA" + (onePhase ? "-1p: " : ": ")
                    + xid + " " + _xaResource);
        }

        try {
          _xaResource.commit(xid, onePhase);
        } catch (XAException e) {
          if (logFiner)
            log.finer("commit-XA failed: " + _xaResource + " " + e);

          throw e;
        }
      }
      else if (_localTransaction != null) {
        if (logFiner)
          log.finer("commit-local: " + _localTransaction);

        try {
          _localTransaction.commit();
        } catch (ResourceException e) {
          if (logFiner)
            log.finer("commit failed: " + _localTransaction + " " + e);

          throw new XAExceptionWrapper(e);
        } finally {
          _isLocalTransaction = false;
        }
      }
      else {
        if (logFiner)
          log.finer("commit for resource with no XA support: " + this);
      }
    } finally {
      if (_xaResource != null)
        _isXATransaction = true;

      clearXid();
    }
  }

  /**
   * Ends the resource.
   */
  private void endResource(Xid xid, int flags)
    throws XAException
  {
    ManagedPoolItem xaPtr = this;

    for (; xaPtr != null; xaPtr = xaPtr._xaNext) {
      if (xaPtr._xaResource != null)
        xaPtr._xaResource.end(xid, flags);
    }
  }

  /**
   * Restores the delegation for the entire chain.
   */
  private void clearXid()
  {
    _xid = null;

    UserPoolItem shareHead = _shareHead;
    _shareHead = null;

    ManagedPoolItem xaPtr = _xaNext;
    _xaHead = null;
    _xaNext = null;

    UserPoolItem assignedUserItem = null;
    for (UserPoolItem ptr = shareHead; ptr != null; ptr = ptr.getShareNext()) {
      if (ptr.getOwnPoolItem() == this) {
        assignedUserItem = ptr;
        break;
      }
    }

    for (UserPoolItem ptr = shareHead; ptr != null; ptr = ptr.getShareNext()) {
      if (assignedUserItem == null && ptr.getOwnPoolItem() == null) {
        ptr.associatePoolItem(this);
        assignedUserItem = ptr;
      }

      try {
        ptr.reassociatePoolItem();
      } catch (Throwable e) {
        log.log(Level.WARNING, e.toString(), e);
      }
    }

    while (xaPtr != null) {
      ManagedPoolItem next = xaPtr._xaNext;
      xaPtr._xaNext = null;
      xaPtr._xaHead = null;

      xaPtr.clearXid();

      xaPtr = next;
    }

    if (assignedUserItem != null) {
      //_shareHead = assignedUserItem;
      //_shareHead.setShareNext(null);
    }
    else if (_hasConnectionError) {
      destroy();
    }
    else {
      toIdle();
    }
  }

  /**
   * Changes the state to idle.
   */
  void toIdle()
  {
    if (_shareHead != null)
      return;
    else if (_xid != null || _isLocalTransaction)
      return;
    else if (_hasConnectionError) {
      destroy();
      return;
    }

    /*
    UserTransactionImpl transaction = _transaction;
    _transaction = null;

    if (transaction != null) {
      try {
        transaction.delistXaResource(this, XAResource.TMSUCCESS);
      } catch (Throwable e) {
        log.log(Level.FINE, e.toString(), e);
      }
    }
    */

    _isLocalTransaction = false;

    if (log.isLoggable(Level.FINE))
      log.fine("idle " + this);

    _poolEventTime = CurrentTime.getCurrentTime();
    _cm.toIdle(this);
  }

  /**
   * Closes the connection.
   */
  @Override
  public void abortConnection()
  {
    destroy();
  }

  /**
   * Closes the connection.
   */
  @Override
  public void destroy()
  {
    ManagedConnection mConn = _mConn;
    _mConn = null;

    // _transaction = null;

    if (mConn == null)
      return;

    if (!_isPastActiveTime) {
    }
    else if (ManagedConnectionImpl.class.isAssignableFrom(mConn.getClass())) {
      ((ManagedConnectionImpl) mConn).setPastActiveTime(_isPastActiveTime);
    }

    _cm.removeItem(this, mConn);

    UserPoolItem userItem = _shareHead;

    if (log.isLoggable(Level.FINE))
      log.fine("connection pool destroy " + this);

    try {
      while (userItem != null) {
        UserPoolItem next = userItem.getShareNext();

        userItem.close();

        userItem = next;
      }

      /*
       * XXX: transaction.delistXaResource()
       */
    } catch (Throwable e) {
      log.log(Level.FINE, e.toString(), e);
    }

    try {
      mConn.destroy();
    } catch (Exception e) {
      log.log(Level.FINE, e.toString(), e);
    } finally {
      _cm.getConnectionTimeProbe().end(_connectionStartTime);
    }
  }

  @Override
  public String toString()
  {
    if (_mConn != null) {
      return (getClass().getSimpleName() + "[" + _cm.getName() + "," + _id
              + "," + _mConn.getClass().getSimpleName() + "]");
    }
    else {
      return (getClass().getSimpleName() + "[" + _cm.getName()
              + "," + _id + ",null]");
    }
  }
}
