/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2009 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id: ConnectionManagerImpl.java 21171 2011-04-18 16:12:53Z benoitf $
 * --------------------------------------------------------------------------
 */


package org.ow2.jonas.resource.internal.cm;

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Vector;

import javax.naming.Context;
import javax.naming.NamingException;
import javax.resource.NotSupportedException;
import javax.resource.ResourceException;
import javax.resource.spi.ConnectionEvent;
import javax.resource.spi.ConnectionEventListener;
import javax.resource.spi.ConnectionManager;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionFactory;
import javax.resource.spi.ResourceAllocationException;
import javax.resource.spi.ValidatingManagedConnectionFactory;
import javax.security.auth.Subject;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.Transaction;
import javax.transaction.xa.XAResource;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.objectweb.util.monolog.api.LoggerFactory;
import org.objectweb.util.monolog.wrapper.printwriter.LoggerImpl;
import org.ow2.jonas.resource.internal.IJDBCConnection;
import org.ow2.jonas.resource.internal.SQLManager;
import org.ow2.jonas.resource.internal.cm.jta.JResourceManagerEvent;
import org.ow2.jonas.resource.internal.cm.jta.JSynchronization;
import org.ow2.jonas.resource.internal.cm.jta.LocalXAResource;
import org.ow2.jonas.resource.internal.cm.sql.PreparedStatementWrapper;
import org.ow2.jonas.resource.internal.cm.sql.SQLConnectionInvocationHandler;
import org.ow2.jonas.resource.internal.pool.Pool;
import org.ow2.jonas.resource.internal.pool.PoolMatchFactory;
import org.ow2.jonas.resource.internal.pool.lib.HArrayPool;
import org.ow2.jonas.tm.TransactionManager;
import org.ow2.jonas.tm.TxResourceManager;

/**
 *  Description of the ConnectionManagerImpl
 *
 *@author     chassand
 *created    15 novembre 2001
 */
public class ConnectionManagerImpl implements ConnectionEventListener, ConnectionManager,
                                              PoolMatchFactory, SQLManager,
                                              TxResourceManager {

    /**
     * Main logger
     */
    public static Logger trace = null;
    /**
     * Pool infomation logger
     */
    public static Logger poolTrace = null;

    /**
     * True if the ConnectionManager pool is dummy.
     */
    protected boolean dummyPool = false;

    /**
     *  The transaction manager in server
     */
    protected TransactionManager tm;

    /**
     *  This hashtable allows to find the list of connection handle associated to a
     *  ManagedConnection
     */
    protected Hashtable mc2mci = null;

    /**
     *  The jndiname of the associated factory
     */
    protected String jndiname = null;

    /**
     *  The max pool size of ManagedConnection. The default value is -1.
     */
    private int mcMaxPoolSize = -1;

    /**
     *  The min pool size of ManagedConnection. The default value is 0
     */
    private int mcMinPoolSize = 0;

    /**
     *  The ManagedConnectionFactory instance which represents the resource
     *  adapter.
     */
    private ManagedConnectionFactory mcf;

    /**
     *  The ValidatingManagedConnectionFactory instance which represents the resource
     *  adapter.
     */
    private ValidatingManagedConnectionFactory vmcf = null;

    /**
     *  The pool of ManagedConnections associated to a ManagedConnectionFactory
     *  There's one instance of pool by instance for one instance of this class (ConnectionManagerImpl).
     *  The synchronization of the methods of this are ensured through synchronized
     *  blocks on the poolMCs instance. Thus, the lock are shared between the two objects and that
     *  avoids deadlock issues when the poolMCs.getResource() is waiting for a connection releasing
     *  (max nb of instances is reached in the pool).
     */
    private Pool poolMCs = null;

    /**
     *  The list of used ManagedConnections key = transaction reference value =
     *  ManagedConnectionInfo
     */
    private Map usedMCs = null;

    /**
     *  The default max pool size of pstmts per ManagedConnection.
     */
    static final int MAX_PSTMT_SIZE = 20;

    /**
     * PrepareStatement cache max pool size
     */
    private int maxPstmtPoolSize = MAX_PSTMT_SIZE;

    /**
     *  JDBC connection level value
     */
    private int jdbcConnLevel = 0;
    /**
     *  JDBC connection level value
     */
    private String jdbcConnTestStmt = "";

    /**
     * True if there is a JDBC connection to manage
     */
    private boolean jdbcConnSetUp;

    /**
     *  The list of managedConnection used without transaction
     */
    private List mcs = new Vector();

    /**
     *  The list of Synchronization instances managed in this ConnectionManager
     */
    private List synchros = new Vector();

    /**
     *  This is a cache to the last instance of ConnectionResourceHint used by the
     *  ConnectionManager. Indeed this instance is always the same.
     */
    private ConnectionResourceHint connectionResourceHint = null;

    /**
     *  The holds the transaction support level for the associated RAR file
     */
    private String transSupport = null;

    /**
     * This constant is used in the by the init method
     */
    public final static String RESOURCE_BUNDLE_NAME = "resourceBundleName";

    /**
     * This constant is used in the by the init method
     */
    public final static String LOGGER = "org.objectweb.util.monolog.logger";

    /**
     * This constant is used in the by the init method
     */
    public final static String POOL_LOGGER = "org.objectweb.util.monolog.logger_pool";

    /**
     * This constant is used by the init method
     */
    public final static String JNDINAME = "jndiname";

    /**
     * This constant is used in the by the init method
     */
    public final static String LOGGER_FACTORY = "org.objectweb.util.monolog.loggerFactory";

    /**
     * This constant is used in the by the init method
     */
    public final static String TRANSACTION_MANAGER = "transactionManager";

    /**
     * This constant is used in the by the init method
     */
    public final static String RESOURCE_ADAPTER = "resourceAdapter";

    /**
     * This constant is used in the by the init method
     */
    public final static String PRINT_WRITER = "printWriter";

    /*
     * These constants define the different transaction support values
     */
    /**
     * Rar doesn't support transactions
     */
    public final static String NO_TRANS_SUPPORT = "NoTransaction";
    /**
     * Rar supports local transactions
     */
    public final static String LOCAL_TRANS_SUPPORT = "LocalTransaction";
    /**
     * Rar supports XA transactions
     */
    public final static String XA_TRANS_SUPPORT = "XATransaction";

    /**
     * Constants to determine which PreparedStatement types to call
     *
     */
    public final static int PSWRAP_1 = 1;
    public final static int PSWRAP_2 = 2;
    public final static int PSWRAP_3 = 3;
    public final static int PSWRAP_4 = 4;
    public final static int PSWRAP_5 = 5;

    /**
     * Constants for use with JDBC connection level
     */
    public final static int JDBC_NO_TEST = 0;
    public final static int JDBC_CHECK_CONNECTION = 1;
    public final static int JDBC_SEND_STATEMENT = 2;
    public final static int JDBC_KEEP_ALIVE = 3;

    // JOTM variables
    /**
     * ManagedConnection used with JOTM recovery
     */
    private ManagedConnection jotmMc = null;
    /**
     * XAResource used with JOTM recovery
     */
    private XAResource jotmXar = null;
    /**
     * XA Name used with JOTM recovery
     */
    private String xaName = null;

    /**
     * Debug is enabled ?
     */
    private boolean isEnabledDebug =  false;

    /**
     * Is this DataSource observable ?
     */
    private boolean observable = false;

    /**
     * Pstmt cache statistic : Pstmt found in cache counter
     */
    private long totalPstmtFoundInCache = 0L;

    /**
     * Pstmt cache statistic : Pstmt put in cache counter
     */
    private long  totalPstmtPutInCache= 0L;

    /**
     * Pstmt cache statistic : max nb of jump in cache
     */
    private long  maxPstmtJumpCache= 0L;

    /**
     * Pstmt cache statistic : Pstmt freed in cache counter
     */
    private long  totalPstmtFreedInCache= 0L;

    /**
     * Pstmt cache statistic : Pstmt created out of cache counter
     */
    private long  totalPstmtCreatedOutOfCache= 0L;


    /**
     * ConnectionManagerImpl constructor
     * @param transSupport String defining level of support needed
     */
    public ConnectionManagerImpl(final String transSupport) {
        if (transSupport.length() == 0) {
            this.transSupport = NO_TRANS_SUPPORT;
        } else {
            this.transSupport = transSupport;
        }
    }

    /**
     *  Setters method to initialize the ConnectionManager The logger instance
     *  where events are logged
     *
     *@param  l  The new Logger value
     */
    public void setLogger(final Logger l) {
        trace = l;
        isEnabledDebug = trace.isLoggable(BasicLevel.DEBUG);
    }

    /**
     *  Setters method to initialize the ConnectionManager A logger factory to
     *  obtain a logger
     *
     *@param  lf  The new LoggerFactory value
     */
    public void setLoggerFactory(final LoggerFactory lf) {
        trace = lf.getLogger("org.objectweb.resource.server");
        isEnabledDebug = trace.isLoggable(BasicLevel.DEBUG);
    }

    /**
     *  Setters method to initialize the ConnectionManager The printwriter where
     *  event are logged
     *
     *@param  pw  The new PrintWriter value
     */
    public void setPrintWriter(final PrintWriter pw) {
        trace = new LoggerImpl(pw);
    }

    /**
     *  Setters method to initialize the ConnectionManager The Transaction manager
     *  linked to this resource managed
     *
     *@param  tm  TransactionManager value
     */
    public void setTransactionManager(final TransactionManager tm) {
        this.tm = tm;
    }

    /**
     *  Setters method to initialize the ConnectionManager The
     *  managedConnectionFactory instance of the resource which must be managed by
     *  this connectionManager
     *
     *@param  tmcf           The new ResourceAdapter value
     *@exception  Exception  Description of Exception
     */
    public void setResourceAdapter(final ManagedConnectionFactory tmcf)
            throws Exception {
        setResourceAdapter(tmcf, new ConnectionManagerPoolParams());
    }

    /**
     *  Setters method to initialize the ConnectionManager The
     *  managedConnectionFactory instance of the resource which must be maneged by
     *  this connectionManager
     *
     *@param  tmcf  The ManagedConnectionFactory object
     *@param  cmpp  The pool parameters
     *@exception  Exception  Description of Exception
     */
    public void setResourceAdapter(final ManagedConnectionFactory tmcf,
                                   final ConnectionManagerPoolParams cmpp)
            throws Exception {

        // set the max/min pool values
        if (cmpp.getPoolMax() != 0) {
            mcMaxPoolSize = cmpp.getPoolMax();
        }
        if (cmpp.getPoolMin() > 0) {
            mcMinPoolSize = cmpp.getPoolMin();
        }

        // set the jdbc connection info
        // TODO should not be here (must be jdbc independant)
        jdbcConnLevel = cmpp.getJdbcConnLevel();
        jdbcConnTestStmt = cmpp.getJdbcConnTestStmt();
        jdbcConnSetUp = cmpp.isJdbcConnSetUp();

        mcf = tmcf;
        if (mcf instanceof ValidatingManagedConnectionFactory) {
            vmcf = (ValidatingManagedConnectionFactory) mcf;
        }

        poolMCs = new HArrayPool(poolTrace, jndiname);
        poolMCs.setMatchFactory(this);

        if (! dummyPool) {
            if (cmpp.getPoolMaxAge() > 0) {
                int min = (int) (cmpp.getPoolMaxAge() / 60);
                poolMCs.setMaxAge(min);
            } else {
                poolMCs.setMaxAge(cmpp.getPoolMaxAgeMinutes());
            }

            if (cmpp.getPoolMaxOpentime() > 0) {
                poolMCs.setMaxOpentime(cmpp.getPoolMaxOpentime());
            }
            poolMCs.setMaxWaiters(cmpp.getPoolMaxWaiters());
            if (cmpp.getPoolMaxWaittime() > 0) {
                poolMCs.setMaxWaitTime(cmpp.getPoolMaxWaittime());
            }

            poolMCs.setMaxSize(mcMaxPoolSize);
            poolMCs.setInitSize(cmpp.getPoolInit());
            poolMCs.setMinSize(mcMinPoolSize);
            poolMCs.setJdbcConnLevel(jdbcConnLevel);
            poolMCs.setJdbcTestStatement(jdbcConnTestStmt);
            poolMCs.startMonitor();
            poolMCs.setSamplingPeriod(cmpp.getPoolSamplingPeriod());
        }
        maxPstmtPoolSize = cmpp.getPstmtMax();

        usedMCs = new Hashtable();
        connectionResourceHint = new ConnectionResourceHint(null, null);

        if (isEnabledDebug) {
            trace.log(BasicLevel.DEBUG, "");
        }
    }


    /**
     * Called by ResourceUtility.createConnectionManager
     * to initialize the ConnectionManager with the following parameters:
     * RESOURCE_BUNDLE_NAME: Name of the resource bundle to internationalize the logging
     * LOGGER: The logger instance where events are logged
     * LOGGER_FACTORY: A logger factory to obtain a logger
     * PRINT_WRITER: The printwriter where event are logged
     * TRANSACTION_MANAGER: The Transaction manager linked to this resource manager
     * RESOURCE_MANAGER_EVENT_LISTENER: for later connection enlistement.
     * RESOURCE_ADAPTER: The managedConnectionFactory instance of the resource to be managed
     * JNDINAME: JndiName from the factory associated with this ConnectionManager
     * @param  ctx            Description of Parameter
     * @param generic RA is generic: No actual pool of Connections
     * @exception  Exception  Description of Exception
     */
    public void init(final Context ctx, final boolean generic) throws Exception {
        mc2mci = new Hashtable();

        // Get the resource bundle name to internationalise the log
        // Optional
        String resourceBundleName = null;
        try {
            resourceBundleName = (String) ctx.lookup(RESOURCE_BUNDLE_NAME);
        } catch (NamingException e) {
        }

        // Get the logger or the logger factory or the printwriter
        try {
            // logger should be: Log.JONAS_JCA_PREFIX+".process"
            trace = (Logger) ctx.lookup(LOGGER);
            poolTrace = (Logger) ctx.lookup(POOL_LOGGER);
        } catch (NamingException e) {
        }

        if (trace == null) {
            try {
                setLoggerFactory((LoggerFactory) ctx.lookup(LOGGER_FACTORY));
            } catch (NamingException e2) {
            }
        }
        if (trace == null) {
            PrintWriter pw = null;
            try {
                pw = (PrintWriter) ctx.lookup(PRINT_WRITER);
            } catch (NamingException e3) {
            }
            setPrintWriter(pw);
        }
        if (trace != null) {
            isEnabledDebug = trace.isLoggable(BasicLevel.DEBUG);
        }

        if (!transSupport.equalsIgnoreCase(NO_TRANS_SUPPORT)) {
            // Get the transaction manager
            tm = (TransactionManager) ctx.lookup(TRANSACTION_MANAGER);
        }

        // A generic RA has no actual pool of Connections
        dummyPool = generic;

        // Get the managedConnectionFactory instance which represents the resource
        // adapter
        try {
            setResourceAdapter(
                    (ManagedConnectionFactory) ctx.lookup(RESOURCE_ADAPTER));
        } catch (NamingException ne) {
        }

        // Get the jndiname
        try {
            jndiname = (String) ctx.lookup(JNDINAME);
        } catch (NamingException ne) {
        }

    }


    /**
     *  Description of the Method
     *
     *@exception  ResourceException  Description of Exception
     */
    public void cleanResourceAdapter() throws ResourceException {

        synchronized (poolMCs) {

            // Delete the PreparedStatements
            PreparedStatementWrapper pw = null;
            while (mcs != null && mcs.size() > 0) {
                ManagedConnectionInfo mci = (ManagedConnectionInfo) mcs.remove(0);
                mci.usedCs.clear();
                synchronized (mci.pStmts) {
                    // Remove the PreparedStatements on the MC
                    while (mci.pStmts != null && mci.pStmts.size() > 0) {
                        pw = (PreparedStatementWrapper) mci.pStmts.remove(0);
                        try {
                            pw.destroy();
                        } catch (Exception ex) {
                        }
                    }
                }
                try {
                    mc2mci.remove(mci.mc);
                } catch (Exception ex) {
                }
            }
            if (usedMCs != null) {
                for (Iterator i = usedMCs.keySet().iterator(); i.hasNext();) {
                    Transaction tx = (Transaction) i.next();
                    ManagedConnectionInfo mci = (ManagedConnectionInfo) usedMCs.get(tx);
                    if (mci == null) {
                        continue;
                    }
                    if (mci.rmeCalled) {
                        mci.rme.setValid(false);
                        tm.notifyConnectionClose(mci.rme);
                        mci.rmeCalled = false;
                    }
                    mci.usedCs.clear();
                    // Remove the PreparedStatements on the MC
                    synchronized (mci.pStmts) {
                        while (mci.pStmts != null && mci.pStmts.size() > 0) {
                            pw = (PreparedStatementWrapper) mci.pStmts.remove(0);
                            try {
                                pw.destroy();
                            } catch (Exception ex) {
                            }
                        }
                    }
                    try {
                        mc2mci.remove(mci.mc);
                    } catch (Exception ex) {
                    }
                }
            }
            while (synchros != null && synchros.size() > 0) {
                ManagedConnectionInfo mci = (ManagedConnectionInfo) synchros.remove(0);
                mci.usedCs.clear();
                // Remove the PreparedStatements on the MC
                synchronized (mci.pStmts) {
                    while (mci.pStmts != null && mci.pStmts.size() > 0) {
                        pw = (PreparedStatementWrapper) mci.pStmts.remove(0);
                        try {
                            pw.destroy();
                        } catch (Exception ex) {
                        }
                    }
                }
                try {
                    mc2mci.remove(mci.mc);
                } catch (Exception ex) {
                }
            }
        }
        if (! dummyPool) {
            poolMCs.closeAllConnections();
        }
    }


    /**
     *
     *  The method allocateConnection gets called by the resource adapter's
     *  connection factory instance.
     *
     * @see javax.resource.cci.ConnectionManager
     */
    public Object allocateConnection(final ManagedConnectionFactory pMcf, final ConnectionRequestInfo cxRequestInfo)
            throws ResourceException {


        ManagedConnectionInfo mci = null;
        Transaction currentTx = null;
        Object connection = null;
        int retries = 0;
        Subject subject = null;

        trace.log(BasicLevel.DEBUG, "");
        while (connection == null && retries < 20) {
            synchronized (poolMCs) {
                if (mcf != pMcf && !pMcf.equals(mcf)) {
                    throw new ResourceException(
                            "This ConnectionManager doesn't manage this RA:" + mcf);
                }

                currentTx = null;
                try {
                    if (tm != null) {
                        currentTx = tm.getTransaction();
                    }
                } catch (Exception e) {
                    trace.log(BasicLevel.ERROR,
                            "Impossible to get the current transaction", e,
                            "ConnectionManagerImpl", "allocateConnection");
                }

                //if there is a transaction check if a MC is already associated
                mci = (currentTx == null ? null : (ManagedConnectionInfo) usedMCs.get(currentTx));
                if (mci != null) {
                    if (mci.mc != null) {
                        // There are connections, try to match with the
                        // ManagedConnectionFactory
                        if (isEnabledDebug) {
                            trace.log(BasicLevel.DEBUG, "MC (" + mci.mc + ") associated to the current Tx (" + currentTx + ") found");
                        }
                        Set s = new HashSet();
                        s.add(mci.mc);
                        if (mci.mc != mcf.matchManagedConnections(s, null, cxRequestInfo)) {
                            throw new ResourceException(
                            "ConnectionManagerImpl.allocateConnection: illegal state : no mc is matched by mcf");
                        }
                        if (isEnabledDebug) {
                            trace.log(BasicLevel.DEBUG, "XA Resource " + mci.getXAResource()
                                    + " is already enlisted in Tx:" + mci.getGlobalTx());
                        }
                    } else {
                        // This connection occurred an error before
                        trace.log(BasicLevel.INFO, "remnant of an old failed connection");
                        mci.setGlobalTx(null);
                        mci = null;
                        usedMCs.remove(currentTx);
                    }
                }

                if (mci == null) {
                    // No managed connection found => get a free ManagedConnection
                    // from the right pool

                    //ri.rs.subject = null; // Never set => already at null
                    connectionResourceHint.cxRequestInfo = cxRequestInfo;
                    if (subject == null && cxRequestInfo != null) {
                        // Create a subject to pass on
                    }
                    try {
                        ManagedConnection mc = (ManagedConnection) poolMCs.getResource(connectionResourceHint);
                        if (mc == null) {
                            throw new ResourceException("ConnectionManagerImpl.allocateConnection: cannot allocate a ManagedConnection");
                        }
                        mci = (ManagedConnectionInfo) mc2mci.get(mc);
                        if (mci == null) {
                            mci = new ManagedConnectionInfo(mc);
                            mc2mci.put(mc, mci);
                        }
                        if (isEnabledDebug) {
                            trace.log(BasicLevel.DEBUG, "get a MC from the ra pool, mc=" + mci.mc);
                        }
                        if (transSupport.equalsIgnoreCase(LOCAL_TRANS_SUPPORT)) {
                            if (mci.lw == null) {
                                mci.lw = new LocalXAResource(mci.mc.getLocalTransaction(), trace);
                            }
                        } else if (mci.lw != null) {
                            mci.lw = null;
                        }
                        if (!mci.connectionEventListener) {
                            mci.mc.addConnectionEventListener(this);
                            mci.connectionEventListener = true;
                        }
                        mci.synchro = null;
                        // If a global transaction is already started then enlist the
                        // ManagedConnection instance
                        if (currentTx != null) {
                            if (isEnabledDebug) {
                                trace.log(BasicLevel.DEBUG, "Enlist the XA Resource "
                                        + mci.getXAResource() + " in Tx:"
                                        + currentTx);
                            }
                            currentTx.enlistResource(mci.getXAResource());
                            usedMCs.put(currentTx, mci);
                            mci.setGlobalTx(currentTx);
                        } else {
                            // There are not Transaction at the moment but the user can
                            // start a transaction after the getConnection call.
                            mci.setGlobalTx(null);
                            // must be clean
                            mcs.add(mci);
                            // NoTransaction is specified, so don't register the MC
                            if (!transSupport.equalsIgnoreCase(NO_TRANS_SUPPORT)) {
                                mci.rme = new JResourceManagerEvent(mci, trace);
                                if (isEnabledDebug) {
                                    trace.log(BasicLevel.DEBUG, "Register the managed connection (no tx)");
                                }
                                // Always put in list, fix bug on connection late enlistment
                                if (!mci.rmeCalled) {
                                    mci.rme.setValid(true);
                                    tm.notifyConnectionOpen(mci.rme);
                                    mci.rmeCalled = true;
                                }
                            }
                        }
                    } catch (ResourceException re) {
                        trace.log(BasicLevel.ERROR, re.getMessage(), re);
                        throw re;
                    } catch (Exception e) {
                        String err = "Error related allocation of ManagedConnection";
                        trace.log(BasicLevel.ERROR, err, e);
                        throw new ResourceException(err, e);
                    }

                }

                //Fetch a free Connection from the ManagedConnection
                connection = mci.mc.getConnection(null, cxRequestInfo);
                // Want to add the non-wrapped Connection object
                mci.usedCs.add(connection);
            }

            if (connection instanceof java.sql.Connection) {
                try {
                    if (connection instanceof IJDBCConnection) {
                        IJDBCConnection jdbcConnection = (IJDBCConnection) connection;
                        jdbcConnection.setJonasInfo(mci, this);
                        // This used to be done in the ConnectionImpl constructor, but a synchronized block
                        //  is needed and we don't want any DB I/O within the block
                        jdbcConnection.setUser();
                    } else {
                        // Need a wrapper for non JOnAS jdbc ConnectionImpl
                        connection = SQLConnectionInvocationHandler.createSQLWrapper(connection, mci, this, trace);
                    }
                    // Check the connection before reusing it
                    if (jdbcConnLevel > JDBC_NO_TEST) {
                        try {
                            if (isEnabledDebug) {
                                trace.log(BasicLevel.DEBUG, "Check the JDBC connection");
                            }
                            boolean isClosed = true;
                            // Used for logical testing of connection
                            if (connection instanceof IJDBCConnection) {
                                isClosed = ((IJDBCConnection) connection).isPhysicallyClosed();
                            } else {
                                isClosed = ((Connection) connection).isClosed();
                            }
                            if (isClosed) {
                                connectionErrorOccurred(new ConnectionEvent(mci.mc,
                                        ConnectionEvent.CONNECTION_ERROR_OCCURRED));
                                try {
                                    mci.usedCs.remove(connection);
                                } catch (Exception ex) {
                                }
                                connection = null;
                                retries++;
                                continue;
                            }
                            if (jdbcConnLevel > JDBC_CHECK_CONNECTION && jdbcConnTestStmt != null
                                    && jdbcConnTestStmt.length() > 0) {
                                if (isEnabledDebug) {
                                    trace.log(BasicLevel.DEBUG, "retrying connection: " + jdbcConnTestStmt);
                                }
                                java.sql.Statement stmt = ((Connection) connection).createStatement();
                                stmt.execute(jdbcConnTestStmt);
                                stmt.close();
                            }
                        } catch (Exception e) {
                            trace.log(BasicLevel.ERROR, "Error on connection: removing invalid managed connection " + mci.mc + ": ", e);
                            connectionErrorOccurred(new ConnectionEvent(mci.mc,
                                    ConnectionEvent.CONNECTION_ERROR_OCCURRED));
                            try {
                                mci.usedCs.remove(connection);
                            } catch (Exception ex) {
                            }
                            connection = null;
                            retries++;
                            continue;
                        }
                    }
                } catch (Exception ex) {
                    trace.log(BasicLevel.ERROR, ex.getMessage(), ex);
                    throw new ResourceException(ex);
                }
            }
        }
        if (isEnabledDebug) {
            trace.log(BasicLevel.DEBUG, "get a logical connection on MC:" + connection);
        }

        if (connection == null) {
            if (retries > 0) {
                throw new ResourceAllocationException(
                "Unable to obtain a connection object.  Check the validity of the jdbc-test-statement");
            } else {
                throw new ResourceAllocationException("Unable to obtain a connection object");
            }
        }

        return connection;
    }


    /**
     *  All method of the pool match to the right type of ManagedConnection because
     *  there is one pool by ManagedConnectionFactory.
     *
     * @see org.objectweb.util.pool.api.PoolMatchFactory
     */
    public boolean matchResource(final Object res, final Object hints) {
        return true;
    }

    /**
     *  All method of the pool match to the right type of ManagedConnection because
     *  there is one pool by ManagedConnectionFactory.
     *
     * @see org.objectweb.util.pool.api.PoolMatchFactory
     */
    public Object matchResource(final Set res, final Object hints) throws Exception {
        ConnectionResourceHint spec = (hints != null) ? (ConnectionResourceHint) hints : new ConnectionResourceHint(null, null);
        // If not supported, then just return null so another one will be created
        Object con = null;
        try {
            con = mcf.matchManagedConnections(res, null, spec.cxRequestInfo);
        } catch (NotSupportedException nse) {
        }
        return con;
    }


    /**
     *  Call the ManagedConnectionFactory in order to create a new instance. of
     *  ManagedConnection. The Object is a ManagedConnection instance. The hints is a
     *  local structure: ConnectionResourceHint
     *
     * @see org.objectweb.util.pool.api.PoolMatchFactory
     */
    public Object createResource(final Object hints) throws Exception {
        ConnectionResourceHint spec = (hints != null) ? (ConnectionResourceHint) hints : new ConnectionResourceHint(null, null);
        ManagedConnection mc = mcf.createManagedConnection(spec.subject,
                spec.cxRequestInfo);
        if (isEnabledDebug) {
            trace.log(BasicLevel.DEBUG, "Created MC: " + mc);
        }
        return mc;
    }

    /**
     * @return the ValidatingManagedConnectionFactory if the RA implements it
     */
    public ValidatingManagedConnectionFactory getValidatingMCFactory() {
        return vmcf;
    }

    // ---------------------------------------------------------
    // IMPLEMENTATION OF INTERFACE SQLManager
    // ---------------------------------------------------------

    public PreparedStatement getPStatement(final ManagedConnectionInfo mcinfo, final Object conn, final String user, final String sql)
            throws SQLException {
        return getPStatement(mcinfo, conn, user, sql, ResultSet.TYPE_FORWARD_ONLY,
                             ResultSet.CONCUR_READ_ONLY, -1, -1, null, null, PSWRAP_1);

    }

    public PreparedStatement getPStatement(final ManagedConnectionInfo mcinfo, final Object conn, final String user, final String sql,
                                           final int resultSetType, final int resultSetConcurrency)
            throws SQLException {
        return getPStatement(mcinfo, conn, user, sql, resultSetType, resultSetConcurrency,
                             -1, -1, null, null, PSWRAP_1);
    }

    // JDBC 3.0
    public PreparedStatement getPStatement(final ManagedConnectionInfo mcinfo,
                                           final Object conn,
                                           final String user, final String sql,
                                           final int resultSetType,
                                           final int resultSetConcurrency,
                                           final int resultSetHoldability)
            throws SQLException {
              return getPStatement(mcinfo, conn, user, sql, resultSetType, resultSetConcurrency,
                      resultSetHoldability, -1, null, null, PSWRAP_2);
    }

    // JDBC 3.0
    public PreparedStatement getPStatement(final ManagedConnectionInfo mcinfo,
                                           final Object conn,
                                           final String user, final String sql,
                                           final int autoGeneratedKeys)
            throws SQLException {
                return getPStatement(mcinfo, conn, user, sql, -1, -1, -1,
                        autoGeneratedKeys, null, null, PSWRAP_3);
    }

    // JDBC 3.0
    public PreparedStatement getPStatement(final ManagedConnectionInfo mcinfo,
                                           final Object conn,
                                           final String user, final String sql,
                                           final int[] columnIndexes)
            throws SQLException {
              return getPStatement(mcinfo, conn, user, sql, -1, -1, -1, -1,
                      columnIndexes, null, PSWRAP_4);
    }

    // JDBC 3.0
    public PreparedStatement getPStatement(final ManagedConnectionInfo mcinfo,
                                           final Object conn,
                                           final String user, final String sql,
                                           final String[] columnNames)
            throws SQLException {
              return getPStatement(mcinfo, conn, user, sql, -1, -1, -1, -1,
                      null, columnNames, PSWRAP_5);
    }


    private PreparedStatement getPStatement(final ManagedConnectionInfo mcinfo,
                                            final Object conn,
                                            final String user,
                                            final String sql,
                                            final int resultSetType,
                                            final int resultSetConcurrency,
                                            final int resultSetHoldability,
                                            final int autoGeneratedKeys,
                                            final int [] columnIndexes,
                                            final String [] columnNames,
                                            final int pswrapType)
            throws SQLException {

        if (isEnabledDebug) {
            trace.log(BasicLevel.DEBUG, "Sql: " + sql + " User: " + user);
        }
        // Use for the equals test
        PreparedStatementWrapper psw = null;
        switch (pswrapType) {
            case PSWRAP_1:
                psw = new PreparedStatementWrapper(user, sql,
                                                   resultSetType,
                                                   resultSetConcurrency,
                                                   trace,
                                                   isEnabledDebug);
                break;
            case PSWRAP_2:
                psw = new PreparedStatementWrapper(user, sql,
                                                   resultSetType,
                                                   resultSetConcurrency,
                                                   resultSetHoldability,
                                                   trace,
                                                   isEnabledDebug);
                break;
            case PSWRAP_3:
                psw = new PreparedStatementWrapper(user, sql,
                                                   autoGeneratedKeys,
                                                   trace,
                                                   isEnabledDebug);
                break;
            case PSWRAP_4:
                psw = new PreparedStatementWrapper(user, sql,
                                                   columnIndexes,
                                                   trace,
                                                   isEnabledDebug);
                break;
            case PSWRAP_5:
                psw = new PreparedStatementWrapper(user, sql,
                                                   columnNames,
                                                   trace,
                                                   isEnabledDebug);
                break;
            default:
                break;
        }

        // counter for statistics
        int found = 0; // found in cache
        int cached = 0; // put in cache
        int jumpNb = 0; // number of jumper to find or not
        int freed = 0; // an entry was freed
        int created = 0; // an entry was created out of the cache

        try {
        synchronized (mcinfo.pStmts) {
            if (isEnabledDebug) {
                trace.log(BasicLevel.DEBUG, "MC pStmts: " + mcinfo.pStmts);
            }

            //Check if statement matches ManagedConnection list & valid
            // use lastIndexOf as when a statement is found, it is put at the end.
            int indexPstmt = mcinfo.pStmts.lastIndexOf(psw);

            while (indexPstmt != -1) {

                PreparedStatementWrapper ps = (PreparedStatementWrapper) mcinfo.pStmts.get(indexPstmt);
                if (ps.isClosed()) {
                    ps = (PreparedStatementWrapper) mcinfo.pStmts.remove(indexPstmt);
                    ps.clearPstmtValues();
                    mcinfo.pStmts.add(ps);  // add last
                    found++;
                    return ps;
                } else {
                    jumpNb++;
                    if (isEnabledDebug) {
                        trace.log(BasicLevel.DEBUG, "Statement in cache but already used, search another one");
                        trace.log(BasicLevel.DEBUG, "sql=" + sql + ", count=" + jumpNb);
                    }
                    // next element
                    indexPstmt = mcinfo.pStmts.subList(0,indexPstmt).lastIndexOf(psw);
                }
            }

            if (isEnabledDebug) {
                trace.log(BasicLevel.DEBUG, "No statement in cache (or not closed), need to build a new one");
            }

            //If not in either, call con.prepareStatement, wrap the returned one
            // and add it to MC pool
            PreparedStatement ps = null;
            switch (pswrapType) {
                case PSWRAP_1:
                    ps = ((java.sql.Connection) conn).
                           prepareStatement(sql, resultSetType, resultSetConcurrency);
                    break;
                case PSWRAP_2:
                    ps = ((java.sql.Connection) conn).
                           prepareStatement(sql, resultSetType, resultSetConcurrency,
                                            resultSetHoldability);
                    break;
                case PSWRAP_3:
                    ps = ((java.sql.Connection) conn).
                           prepareStatement(sql, autoGeneratedKeys);
                    break;
                case PSWRAP_4:
                    ps = ((java.sql.Connection) conn).
                           prepareStatement(sql, columnIndexes);
                    break;
                case PSWRAP_5:
                    ps = ((java.sql.Connection) conn).
                           prepareStatement(sql, columnNames);
                    break;
                default:
                    break;
            }
            if (maxPstmtPoolSize < 0) {
                created++;
                // No prepared statement pool
                if (isEnabledDebug) {
                    trace.log(BasicLevel.DEBUG, "Pooling is disabled");
                }
                return ps;
            } else if (maxPstmtPoolSize == 0 || mcinfo.pStmts.size() < maxPstmtPoolSize) {
                psw.setPreparedStatement(ps);
                mcinfo.pStmts.add(psw);
                cached++;
                if (isEnabledDebug) {
                    trace.log(BasicLevel.DEBUG, "Adding PStmt: " + psw);
                }
                return psw;
            } else {
                int offset = mcinfo.findFreeStmt();
                if (offset >= 0) {
                    // Remove that entry from the current pool and add it at the end of the list
                    PreparedStatementWrapper pw = (PreparedStatementWrapper) mcinfo.pStmts.remove(offset);
                    pw.destroy();
                    freed++;
                    psw.setPreparedStatement(ps);
                    mcinfo.pStmts.add(psw);
                    cached++;
                    if (isEnabledDebug) {
                        trace.log(BasicLevel.DEBUG, "Replacing " + pw + " with " + psw);
                    }
                    return psw;
                } else {
                    // No room for the Wrapped statement just return the PreparedStatement
                    if (isEnabledDebug) {
                        trace.log(BasicLevel.DEBUG, "No room in pool");
                    }
                    created++;
                    return ps;
                }
            }
        }
        } finally {
            updatePstmtCacheStatistics(found,cached,jumpNb,freed,created);
        }
    }


    /**
     * Release the ManagedConnection object resource
     * @param rMc Object to release
     * @throws Exception if an Exception occurs
     */
    public void releaseResource(final Object rMc) throws Exception {
        if (isEnabledDebug) {
            trace.log(BasicLevel.DEBUG, "MC: " + rMc);
        }
        if (rMc instanceof ManagedConnection) {
            synchronized (poolMCs) {
                ManagedConnection mc = (ManagedConnection) rMc;
                ManagedConnectionInfo mcinfo = (ManagedConnectionInfo) mc2mci.remove(mc);
                if (mcinfo != null) {
                    destroyPStmts(mcinfo);
                }
            }
        }
    }

    /**
     * Destroying of the PreparedStatement objects of the Pool
     * @param mcinfo ManagedConnection information
     * @throws Exception Exception
     */
    public void destroyPStmts(final ManagedConnectionInfo mcinfo) throws Exception {
        if (isEnabledDebug) {
            trace.log(BasicLevel.DEBUG, "ManagedConnectionInfo: " + mcinfo);
        }

        int freed = 0;
        try {
            synchronized (mcinfo.pStmts) {
                if (mcinfo.pStmts.size() <= 0) {
                    return;
                }
                int stmtSize = mcinfo.pStmts.size();
                try {
                    for (int i = 0; i < stmtSize; i++) {
                        PreparedStatementWrapper psw = (PreparedStatementWrapper) mcinfo.pStmts.remove(0);
                        freed++;
                        psw.closePstmt();
                    }
                } catch (Exception ex) {
                    throw ex;
                }
            }
        } finally {
            updatePstmtCacheStatistics(0,0,0,freed,0);
        }
    }

    /**
     * Closing of the PreparedStatement objects of the Pool
     * @param mcinfo ManagedConnection information
     * @throws Exception Exception
     */
    public void closePStmts(final ManagedConnectionInfo mcinfo) throws Exception {
        if (isEnabledDebug) {
            trace.log(BasicLevel.DEBUG, "ManagedConnectionInfo: " + mcinfo);
        }
        synchronized (mcinfo.pStmts) {
            if (mcinfo.pStmts.size() <= 0) {
                return;
            }
            int stmtSize = mcinfo.pStmts.size();
            try {
                for (int i = 0; i < stmtSize; i++) {
                    PreparedStatementWrapper psw = (PreparedStatementWrapper)
                            mcinfo.pStmts.get(i);
                    psw.close();
                }
            } catch (Exception ex) {
                throw ex;
            }
        }
    }


    /**
     *  IMPLEMENTATION OF INTERFACE ConnectionEventListener *
     *
     *  A ManagedConnection instance calls the connectionClosed method to notify
     *  its registered set of listeners when an application component closes a
     *  connection handle. The application server uses this connection close event
     *  to make a decision on whether or not to put the ManagedConnection instance
     *  back into the connection pool.
     *
     * @see javax.resource.spi.ConnectionEventListener
     */
    public void connectionClosed(final ConnectionEvent event) {

        synchronized (poolMCs) {

            ManagedConnection mc = (ManagedConnection) event.getSource();
            if (mc == null) {
                trace.log(BasicLevel.ERROR, "no mc found in Event!");
            }

            ManagedConnectionInfo mci = (ManagedConnectionInfo) mc2mci.get(mc);
            if (mci == null) {
                trace.log(BasicLevel.ERROR, "no mci found!");
                return;
            }

            if (isEnabledDebug) {
                trace.log(BasicLevel.DEBUG, "enter\n" + getState("\t"));
            }

            mci.usedCs.remove(event.getConnectionHandle());

            if (mci.usedCs.isEmpty()) {
                if (isEnabledDebug) {
                    trace.log(BasicLevel.DEBUG, "Last Connection has just been removed");
                }

                try {
                    Transaction currentTx = null;
                    if (tm != null) {
                        currentTx = tm.getTransaction();
                    }

                    if (mci.localTransaction && currentTx == null) {
                        trace.log(BasicLevel.ERROR, "The managed connection is being closed while a localtransaction is not finished");
                    }

                    if (currentTx != null) {
                        if (isEnabledDebug) {
                            trace.log(BasicLevel.DEBUG, "currentTx=" + currentTx);
                        }

                        Transaction oldtx = (Transaction) mci.getGlobalTx();
                        if (oldtx != null && oldtx != currentTx) {
                            trace.log(BasicLevel.DEBUG, "This connection was used by another tx");
                            trace.log(BasicLevel.DEBUG, "old tx = " +  oldtx);
                            trace.log(BasicLevel.DEBUG, "cur tx = " +  currentTx);
                            usedMCs.remove(oldtx);
                        }
                        mci.setGlobalTx(currentTx);

                        if (mci.synchro == null) {
                            try {
                                // Register synchro object only if not already committed
                                if (currentTx.getStatus() != Status.STATUS_COMMITTED) {
                                    currentTx.registerSynchronization(
                                            new JSynchronization(this, mci));
                                    if (isEnabledDebug) {
                                        trace.log(BasicLevel.DEBUG, "registerSynchro mc=" + mci.mc);
                                    }
                                }
                            } catch (RollbackException e) {
                                // The transaction has been marked as rollbackOnly
                                // but the synchronization is registered.
                                trace.log(BasicLevel.INFO, "registerSynchronization on transaction marked as Rollback only, mc=" + mci.mc);
                            }
                        } else {
                            if (isEnabledDebug) {
                                trace.log(BasicLevel.DEBUG, "synchro mc=" + mci.mc);
                            }
                        }
                    } else if (mci.localTransaction) {
                        if (isEnabledDebug) {
                            trace.log(BasicLevel.DEBUG, "MC isn't released because local transaction is not finished");
                        }

                    } else {
                        if (isEnabledDebug) {
                            trace.log(BasicLevel.DEBUG, "no currentTx");
                        }

                        mcs.remove(mci);

                        if (mci.getGlobalTx() != null) {
                            usedMCs.remove(mci.getGlobalTx());
                            mci.setGlobalTx(null);
                        }

                        // force close pstmt
                        closePStmts(mci);

                        mci.mc.cleanup();

                        // Release the MC from its pool
                        poolMCs.releaseResource(mci.mc, false);

                    }
                    if (mci.rmeCalled) {
                        // Signal to the RessourceManagerEventListener mc is released
                        mci.rme.setValid(false);
                        tm.notifyConnectionClose(mci.rme);
                        mci.rmeCalled = false;
                    }
                } catch (Exception e) {
                    trace.log(BasicLevel.ERROR,
                            "an error during delisting of ManagedConection: ", e);
                }
                if (isEnabledDebug) {
                    trace.log(BasicLevel.DEBUG, "exit\n" + getState("\t"));
                }
            }
        }
    }


    /**
     *  The connectionErrorOccurred method indicates that the associated
     *  ManagedConnection instance is now invalid and unusable. The application
     *  server handles the connection error event notification by initiating
     *  application server-specific cleanup (for example, removing
     *  ManagedConnection instance from the connection pool) and then calling
     *  ManagedConnection.destroy method to destroy the physical connection..
     *
     * @see javax.resource.spi.ConnectionEventListener
     */
    public void connectionErrorOccurred(final ConnectionEvent event) {

        synchronized (poolMCs) {

            ManagedConnection mc = (ManagedConnection) event.getSource();
            if (mc == null) {
                trace.log(BasicLevel.ERROR, "no mc found in Event!");
            }

            ManagedConnectionInfo mci = (ManagedConnectionInfo) mc2mci.get(mc);
            if (mci == null) {
                trace.log(BasicLevel.ERROR, "no mci found!");
                return;
            }

            if (poolTrace.isLoggable(BasicLevel.DEBUG)) {
                poolTrace.log(BasicLevel.DEBUG, "enter\n" + getState("\t"));
            }

            mci.usedCs.clear();

            try {
                if (mci.rmeCalled) {
                    //Signal to the RessourceManagerEventListener mc is released
                    mci.rme.setValid(false);
                    tm.notifyConnectionError(mci.rme);
                    mci.rmeCalled = false;
                }
                // Detach the connectionManager from the mc
                mc.removeConnectionEventListener(this);
                // Remove the association (transaction ctx / mc)
                if (mci.getGlobalTx() != null) {
                    usedMCs.remove(mci.getGlobalTx());
                    mci.setGlobalTx(null);
                } else {
                    // TODO why not always ?
                    mcs.remove(mci);
                }

                if (isEnabledDebug) {
                    trace.log(BasicLevel.DEBUG, "Destroying managed connection (" + mc + ")");
                }
                // Destroy the PreparedStatements
                destroyPStmts(mci);

                poolMCs.releaseResource(mc, true);
                if (poolTrace.isLoggable(BasicLevel.DEBUG)) {
                    poolTrace.log(BasicLevel.DEBUG, "enter\n" + getState("\t"));
                }
            } catch (Exception e) {
                trace.log(BasicLevel.ERROR, "Error when destroying connection: " + e);
            } finally {
                mc2mci.remove(mc);
            }
        }
    }


    /**
     *  Notifies that a Resource Manager Local Transaction was committed on the
     *  ManagedConnection instance.
     *
     * @see javax.resource.spi.ConnectionEventListener
     */
    public void localTransactionCommitted(final ConnectionEvent event) {

        synchronized (poolMCs) {

            ManagedConnection mc = (ManagedConnection) event.getSource();
            if (mc == null) {
                trace.log(BasicLevel.ERROR, "no mc found in Event!");
            }

            ManagedConnectionInfo mci = (ManagedConnectionInfo) mc2mci.get(mc);
            if (mci == null) {
                trace.log(BasicLevel.ERROR, "no mci found!");
                return;
            }
            mci.localTransaction = false;

            if (mci.usedCs.isEmpty()) {
                if (isEnabledDebug) {
                    trace.log(BasicLevel.DEBUG, "Close the managed connection");
                }

                if (mci.rmeCalled) {
                    //Signal to the RessourceManagerEventListener mc is released
                    mci.rme.setValid(false);
                    tm.notifyConnectionClose(mci.rme);
                    mci.rmeCalled = false;
                }

                if (mci.synchro == null) {
                    mcs.remove(mci);
                    if (mci.getGlobalTx() != null) {
                        usedMCs.remove(mci.getGlobalTx());
                        mci.setGlobalTx(null);
                    }

                    try {

                        // force close pstmt
                        closePStmts(mci);

                        mci.mc.cleanup();

                        // Release the MC from its pool
                        poolMCs.releaseResource(mci.mc, false);
                    } catch (Exception e) {
                        trace.log(BasicLevel.ERROR,
                                "an error related ManagedConection release",
                                e, "ConnectionManagerImpl", "localTransactionCommitted");
                    }
                }
            }
        }

    }


    /**
     *  Notifies that a Resource Manager Local Transaction was rolled back on the
     *  ManagedConnection instance.
     *
     * @see javax.resource.spi.ConnectionEventListener
     */
    public void localTransactionRolledback(final ConnectionEvent event) {

        synchronized (poolMCs) {

            ManagedConnection mc = (ManagedConnection) event.getSource();
            if (mc == null) {
                trace.log(BasicLevel.ERROR, "no mc found in Event!");
            }

            ManagedConnectionInfo mci = (ManagedConnectionInfo) mc2mci.get(mc);
            if (mci == null) {
                trace.log(BasicLevel.ERROR, "no mci found!");
                return;
            }
            mci.localTransaction = false;

            if (mci.usedCs.isEmpty()) {
                if (isEnabledDebug) {
                    trace.log(BasicLevel.DEBUG, "Close the managed connection");
                }

                if (mci.rmeCalled) {
                    mci.rme.setValid(false);
                    tm.notifyConnectionClose(mci.rme);
                    mci.rmeCalled = false;
                }

                if (mci.synchro == null) {
                    mcs.remove(mci);
                    if (mci.getGlobalTx() != null) {
                        usedMCs.remove(mci.getGlobalTx());
                        mci.setGlobalTx(null);
                    }

                    try {
                        // force close pstmt
                        closePStmts(mci);

                        mci.mc.cleanup();

                        // Release the MC from its pool
                        poolMCs.releaseResource(mci.mc, false);
                    } catch (Exception e) {
                        trace.log(BasicLevel.ERROR,
                                "an error related during ManagedConection release:",
                                e, "ConnectionManagerImpl", "localTransactionRolledback");
                    }
                }
            }
        }
    }


    /**
     *  Notifies that a Resource Manager Local Transaction was started on the
     *  ManagedConnection instance.
     *
     * @see javax.resource.spi.ConnectionEventListener
     */
    public void localTransactionStarted(final ConnectionEvent event) {

        synchronized (poolMCs) {

            ManagedConnection mc = (ManagedConnection) event.getSource();
            if (mc == null) {
                trace.log(BasicLevel.ERROR, "no mc found in Event!");
            }

            ManagedConnectionInfo mci = (ManagedConnectionInfo) mc2mci.get(mc);
            if (mci == null) {
                trace.log(BasicLevel.ERROR, "no mci found!");
                return;
            }
            mci.localTransaction = true;

        }
    }


    /**
     *  Description of the Method
     *
     *@return    Description of the Returned Value
     */
    @Override
    public String toString() {
        String m = super.toString();
        // remove package name
        int c1 = 0;
        int current = m.indexOf(".");
        while (current != -1) {
            c1 = current;
            current = m.indexOf(".", current + 1);
        }
        return m.substring(c1 + 1, m.length());
    }


    /**
     *  Gets the State attribute of the ConnectionManagerImpl object
     *
     *@param  prefix  Description of Parameter
     *@return         The State value
     */
    public String getState(final String prefix) {
        String res = null;
        synchronized(poolMCs) {
            res = prefix + "mcf=" + mcf + "\n";
            res += prefix + "ConnectionResourceHint=" + connectionResourceHint.toString() + "\n";
            res += prefix + "size of MC pool:" + poolMCs.getSize() + "\n";
            res += prefix + "size of usedMCs:" + usedMCs.size() + "\n";
            res += prefix + "mcs attached to a tx:\n";
            for (Iterator i = usedMCs.keySet().iterator(); i.hasNext();) {
                Object tx = i.next();
                ManagedConnectionInfo mci = (ManagedConnectionInfo) usedMCs.get(tx);
                res += prefix + "MCI : tx=" + tx + "\n";
                res += mci.getState(prefix + "\t");
            }
            res += prefix + "mcs not attached to a tx:\n";
            for (Iterator i = mcs.iterator(); i.hasNext();) {
                ManagedConnectionInfo mci = (ManagedConnectionInfo) i.next();
                res += mci.getState(prefix + "\t");
            }
            res += prefix + "mcs waiting for tx commit or rollback:\n";
            for (Iterator i = synchros.iterator(); i.hasNext();) {
                ManagedConnectionInfo mci = (ManagedConnectionInfo) i.next();
                res += mci.getState(prefix + "\t");
            }
        }
        return res;
    }

    /**
     * Set the XAName to use
     * @param xanm String of XA Name
     */
    public void setXAName(final String xanm) {
        xaName = xanm;
    }

    /**
     * Get the XAName to use
     * @return String of XA Name
     */
    public String getXAName() {
        return (xaName);
    }

    /**
     * Register an XAResource with JOTM for recovery
     */
    public void registerXAResource(final Properties tmProp) {

        // If no RM or the RAR doesn't support XA, then just return
        if (tm == null) {
            trace.log(BasicLevel.DEBUG, "No tm");
            return;
        }
        if (!transSupport.equalsIgnoreCase(XA_TRANS_SUPPORT)) {
            trace.log(BasicLevel.DEBUG, "No XA Support");
            return;
        }
        // If no recovery enabled, just return
        if (! tm.isRecoveryEnabled()) {
            trace.log(BasicLevel.DEBUG, "No transaction recovery enabled");
            return;
        }

        synchronized (poolMCs) {

            // Find an entry in free pool
            // If none, then create one
            ManagedConnectionInfo mci = null;
            XAResource xar = null;
            try {
                ManagedConnection mc = (ManagedConnection) poolMCs.getResource(connectionResourceHint);
                if (mc == null) {
                    if (isEnabledDebug) {
                        trace.log(BasicLevel.DEBUG, "Cannot allocate a ManagedConnection for registerXAResource");
                    }
                    return;
                }

                mci = (ManagedConnectionInfo) mc2mci.get(mc);
                if (mci == null) {
                    mci = new ManagedConnectionInfo(mc);
                    mc2mci.put(mc, mci);
                }

                xar = mc.getXAResource();
                if (isEnabledDebug) {
                    trace.log(BasicLevel.DEBUG, "got a MC from the ra pool, mc=" + mci.mc
                            + " xar=" + xar);
                }

                if (!mci.connectionEventListener) {
                    mci.mc.addConnectionEventListener(this);
                    mci.connectionEventListener = true;
                }

                mci.synchro = null;

            } catch (ResourceException re) {
                return;
            } catch (Exception e) {
                trace.log(BasicLevel.ERROR, e.getMessage(), e);
                return;
            }

            // Setup globals so it can be returned to the pool
            jotmMc = mci.mc;
            jotmXar = xar;

            // Call to register it
            try {
                if (isEnabledDebug) {
                    trace.log(BasicLevel.DEBUG, "Registering name = " + xaName + " xar = " + jotmXar);
                }
                tm.registerResourceManager(xaName, jotmXar, "", tmProp, this);
            } catch (Exception ex) {
                trace.log(BasicLevel.ERROR, ex.getMessage(), ex);
                freeXAResource(jotmXar);
            }
        }
    }

    /**
     * Called from JOTM to free the XAResource and associated Managed Connection
     * when recovery is complete
     *
     * @param rmName The Resource Manager to be unregistered.
     * @param rmXares XAResource to be returned
     */
    public void freeXAResource(final XAResource rmXares) {

        synchronized (poolMCs) {
            // Get the associated MC
            // Clean it up and return it to the free pool
            if (isEnabledDebug) {
                trace.log(BasicLevel.DEBUG, "Removing name = " + xaName + " xar = " + jotmXar);
            }
            if (jotmXar == null) {
                return;
            }
            if (!rmXares.equals(jotmXar)) {
                trace.log(BasicLevel.ERROR, "XAResource of " + rmXares + " and "
                        + jotmXar + " not equal!");
                return;
            }

            ManagedConnectionInfo mci = (ManagedConnectionInfo) mc2mci.get(jotmMc);
            if (mci == null) {
                trace.log(BasicLevel.ERROR, "no mci found for " + jotmMc);
                return;
            }

            try {
                // force close pstmt
                closePStmts(mci);

                mci.mc.cleanup();

                // Release the MC from its pool
                poolMCs.releaseResource(mci.mc, false);
            } catch (Exception ex) {
                trace.log(BasicLevel.ERROR, ex.getMessage(), ex);
            }

            jotmMc = null;
            jotmXar = null;
        }
    }

    // JSR 77 (JCAConnectionFactory methods)

    public Pool getPool() {
        return poolMCs;
    }

    public boolean isJdbcConnSetUp() {
        return jdbcConnSetUp;
    }

    public int getCheckLevel() {
        return jdbcConnLevel;
    }

    public void setCheckLevel(final int level) {
        jdbcConnLevel = level;
    }

    public String getTestStatement() {
        return jdbcConnTestStmt;
    }

    public void setTestStatement(final String stmt) {
        jdbcConnTestStmt = stmt;
    }

    /**
     * @return the observable
     */
    public boolean isObservable() {
        return observable;
    }

    /**
     * @param observable the observable to set
     */
    public void setObservable(final boolean observable) {
        this.observable = observable;
        // must propagate to the pool
        if (poolMCs != null) {
            poolMCs.setObservable(observable);
        }
    }

    public int getCurrentInTx() {
        return usedMCs.size();
    }

    /**
     * @return Returns the maxPstmtPoolSize.
     */
    public int getMaxPstmtPoolSize() {
        return maxPstmtPoolSize;
    }

    /**
     * @param maxPstmtPoolSize The maxPstmtPoolSize to set.
     */
    public void setMaxPstmtPoolSize(final int maxPstmtPoolSize) {
        this.maxPstmtPoolSize = maxPstmtPoolSize;
    }


    /**
     * return a list of idents that represent the connections
     * opened for a given nb of seconds
     * @param usedTimeSec nb of seconds the Connection has been opened
     * @return array of idents representing the Connections
     */
    public int[] getOpenedConnections(final int usedTimeSec) {
        long millis = usedTimeSec * 1000L;
        return poolMCs.getOpenedConnections(millis);
    }

    /**
     * return a list of idents that represent the connections
     * opened for a given nb of seconds
     * @return array of idents representing the Connections
     */
    public int[] getOpenedConnections() {
        return poolMCs.getOpenedConnections(5000L); // 5 sec.
    }

    /**
     * force the close of the Connection identified by ots Id
     * @param connectionId int that represent the Connection
     */
    public void forceCloseConnection(final int connectionId) {
        poolMCs.forceCloseConnection(connectionId);
    }

    /**
     * Return a Map with details about a Connection
     * @param connectionId Ident that represent the connection
     * @return map given the details about this connection.
     */
    public Map getConnectionDetails(final int connectionId) {
        ManagedConnection mc = poolMCs.getConnectionById(connectionId);
        // Look if used in a transaction
        Transaction mytx = null;
        if (usedMCs != null) {
            Set<Map.Entry> entrySet = usedMCs.entrySet();
            Iterator<Map.Entry> it = entrySet.iterator();
            while (it.hasNext()) {
                Transaction tx = (Transaction) it.next().getKey();
                ManagedConnectionInfo mci = (ManagedConnectionInfo) usedMCs.get(tx);
                if (mci != null && mci.mc.equals(mc)) {
                    mytx = tx;
                    break;
                }
            }
        }
        return poolMCs.getConnectionDetails(mc, mytx);
    }

    /**
     * @return the synchros
     */
    public List getSynchros() {
        return synchros;
    }

    /**
     * @return the used (within Transaction) MC ({@link Transaction})
     */
    public Map getUsedManagedConnections() {
        return usedMCs;
    }

    /**
     * @return the mcs
     */
    public List getManagedConnectionsWithoutTransaction() {
        return mcs;
    }

    /**
     * Update statistics about pstmt cache (synchronized)
     * @param found : nb of element found in cache
     * @param cached : nb of elements put in cache
     * @param jumpNb : number of jump to find an element in the cache
     * @param freed : number of element freed in cache
     * @param created : number of element created out of the cache
     */
    public synchronized void updatePstmtCacheStatistics(final int found, final int cached, final int jumpNb, final int freed, final int created) {
        totalPstmtFoundInCache += found;
        totalPstmtPutInCache += cached;
        if (jumpNb > maxPstmtJumpCache) {
            maxPstmtJumpCache = jumpNb;
        }
        totalPstmtFreedInCache += freed;
        totalPstmtCreatedOutOfCache += created;
    }

    /**
     * @return total number of pstmt found in cache
     *
     */
    public long getTotalPstmtFoundInCache() {
        return totalPstmtFoundInCache;
    }

    /**
     * @return total number of pstmt put in cache
     *
     */
    public long getTotalPstmtPutInCache() {
        return totalPstmtPutInCache;
    }

    /**
     * @return total number of jump in cache to found a pstmt
     *
     */
    public long getMaxPstmtJumpCache() {
        return maxPstmtJumpCache;
    }

    /**
     * @return total number of pstmt freed in cache
     *
     */
    public long getTotalPstmtFreedInCache() {
        return totalPstmtFreedInCache;
    }

    /**
     * @return total number of pstmt created out of the cache
     *
     */
    public long getTotalPstmtCreatedOutOfCache() {
        return totalPstmtCreatedOutOfCache;
    }


}
