/**
 * 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$
 * --------------------------------------------------------------------------
 */


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;

    /**
     *  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 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 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;

    /**
     * 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
        jdbcConnLevel = cmpp.getJdbcConnLevel();
        jdbcConnTestStmt = cmpp.getJdbcConnTestStmt();
        jdbcConnSetUp = cmpp.isJdbcConnSetUp();

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

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

        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, "");
        }
    }


    /**
     *  This method permits to initialize the ConnectionManager with the following
     *  parameter: RESOURCE_BUNDLE_NAME: The name of the resource bundle in order
     *  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: The resource manage event listener which
     *  subscribed to later connection enlistement. RESOURCE_ADAPTER: The
     *  managedConnectionFactory instance of the resource which must be maneged by
     *  this connectionManager
     *
     *@param  ctx            Description of Parameter
     *@exception  Exception  Description of Exception
     */
    public void init(final Context ctx) 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 {
            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);
        }

        // Get the managedConnectionFactory instance which represents the resource
        // adapter
        try {
            setResourceAdapter(
                    (ManagedConnectionFactory) ctx.lookup(RESOURCE_ADAPTER));
        } 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);
//                    mci.mc.destroy();
                } 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);
//                        mci.mc.destroy();
                    } 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);
//                    mci.mc.destroy();
                } catch (Exception ex) {
                }
            }
        }
        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.ctx);
                        }
                    } else {
                        // This connection occurred an error before
                        trace.log(BasicLevel.INFO, "remnant of an old failed connection");
                        mci.ctx = 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.ctx = currentTx;
                        } else {
                            // There are not Transaction at the moment but the user can
                            // start a transaction after the getConnection call.
                            mci.ctx = 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 {
                    // Need a wrapper for non JOnAS jdbc ConnectionImpl
                    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();
                        // in case we do not start a Tx
                        if (currentTx == null) {
                            // TODO setAutoCommit is part of javax.sql.Connection, not IJDBCConnection
                            jdbcConnection.setAutoCommit(true);
                        }
                    } else {
                        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;
    }

    /**
     *  If supported, call the ManagedConnectionFactory to validate the ManagedConnections
     *
     * @see org.objectweb.util.pool.api.PoolMatchFactory
     */
    public void validateResource(final Set res) throws Exception {
        if (vmcf == null) {
            return;
        }
        try {
            Set invMcs = vmcf.getInvalidConnections(res);
            Iterator it = invMcs.iterator();
            while (it.hasNext()) {
                Object obj = it.next();
                poolMCs.releaseResource(obj, true, true);
            }
        } catch (Exception ex) {
            if (isEnabledDebug) {
                trace.log(BasicLevel.DEBUG, "Error trying to validate Connections for " + mcf, ex);
            }
        }
    }


    /** 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;
        }

        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);
            if (indexPstmt != -1) {
                PreparedStatementWrapper ps = (PreparedStatementWrapper) mcinfo.pStmts.remove(indexPstmt);
                ps.clearPstmtValues();
                mcinfo.pStmts.add(ps);  // add last
                return ps;
            } else if (isEnabledDebug) {
                trace.log(BasicLevel.DEBUG, "No statement in cache, 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) {
                // 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);
                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
                    PreparedStatementWrapper pw = (PreparedStatementWrapper) mcinfo.pStmts.remove(offset);
                    pw.destroy();
                    psw.setPreparedStatement(ps);
                    mcinfo.pStmts.add(psw);
                    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");
                    }
                    return ps;
                }
            }
        }
    }


    /**
     * Release the ManagedConnection object resource
     * @param rMc Object to release
     * @throws Exception if an Exception occurs
     */
    public void releaseResource(final Object rMc) throws Exception {

        synchronized (poolMCs) {

            if (isEnabledDebug) {
                trace.log(BasicLevel.DEBUG, "MC: " + rMc);
            }
            if (rMc instanceof ManagedConnection) {
                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);
        }
        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);
                    psw.closePstmt();
                }
            } 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) {
                        mci.ctx = 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 (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.ctx != null) {
                            usedMCs.remove(mci.ctx);
                            mci.ctx = null;
                        }

                        mci.mc.cleanup();

                        // Free up the PreparedStatements
//                      mci.stmtHash.clear();

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

                    }
                    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.ctx != null) {
                    usedMCs.remove(mci.ctx);
                    mci.ctx = null;
                } else {
                    mcs.remove(mci);
                }

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

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

                mc2mci.remove(mc);

            } catch (Exception e) {
                trace.log(BasicLevel.ERROR,
                "an error related during delisting of ManagedConection");
            }
        }
    }


    /**
     *  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.ctx != null) {
                        usedMCs.remove(mci.ctx);
                        mci.ctx = null;
                    }

                    try {
                        mci.mc.cleanup();

                        // Release the MC from its pool
                        poolMCs.releaseResource(mci.mc, false, true);
                    } 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.ctx != null) {
                        usedMCs.remove(mci.ctx);
                        mci.ctx = null;
                    }

                    try {
                        mci.mc.cleanup();

                        // Release the MC from its pool
                        poolMCs.releaseResource(mci.mc, false, true);
                    } 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) {

        synchronized (poolMCs) {

            // If no RM or the RAR doesn't support XA, then just return
            if (tm == null) {
                return;
            }
            if (!transSupport.equalsIgnoreCase(XA_TRANS_SUPPORT)) {
                return;
            }

            // 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 {
                mci.mc.cleanup();

                // Release the MC from its pool
                poolMCs.releaseResource(mci.mc, false, true);
            } 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.
    }

    /**
     * @return the used (within Transaction) MC ({@link Transaction})
     */
    public void forceCloseConnection(final int connectionId) {
        poolMCs.forceCloseConnection(connectionId);
    }

    /**
     * @return the mcs
     */
    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;
    }
}
