/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999 Bull S.A.
 * Contact: jonas-team@objectweb.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.objectweb.jonas.resource;

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
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.jonas.common.Log;
import org.objectweb.jonas.resource.pool.api.Pool;
import org.objectweb.jonas.resource.pool.api.PoolMatchFactory;
import org.objectweb.jonas.resource.pool.lib.HArrayPool;
import org.objectweb.jotm.Current;
import org.objectweb.jotm.TransactionRecovery;
import org.objectweb.jotm.TransactionResourceManager;
import org.objectweb.transaction.jta.ResourceManagerEventListener;
import org.objectweb.transaction.jta.TransactionManager;
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;

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

    /**
     * Main logger
     */
    protected static Logger trace = null;
    /**
     * Connection Management logger
     */
    protected static Logger conTrace = null;
    /**
     * Pool information logger
     */
    protected static Logger poolTrace = null;

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

    /**
     *  Used by the server to register connections when no
     *  transactionnal context exists
     */
    protected ResourceManagerEventListener rmel = null;

    /**
     *  Holds the resource bundle name
     */
    private String resourceBundleName = 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 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.getRessource() is waiting for a connection releasing
     *  (max nb of instances is reached in the pool).
     */
    protected Pool poolMCs = null;

    /**
     *  The list of used ManagedConnections key = transaction reference value =
     *  MCInfo
     */
    protected Hashtable 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 = "";

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

    /**
     *  The list of Synchronisation instance managed in this ConnectionManager
     */
    protected Vector synchros = new Vector();

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

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

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

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

    /**
     * This constant is used 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 by the init method
     */
    public final static String LOGGER_FACTORY = "org.objectweb.util.monolog.loggerFactory";

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

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

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

    /**
     * This constant is used 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(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
     */
    private void setLogger(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
     */
    private void setLoggerFactory(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
     */
    private void setPrintWriter(PrintWriter pw) {
        trace = new LoggerImpl(pw);
    }


    /**
     *  Setters method to initialize the ConnectionManager The logger instance
     *  where events are logged
     *
     *@param  rmel  The new ResourceManagerEventListener value
     */
    public void setResourceManagerEventListener(ResourceManagerEventListener rmel) {
        this.rmel = rmel;
    }


    /**
     *  Setters method to initialize the ConnectionManager The Transaction manager
     *  linked to this resource managed
     *
     *@param  tm  TransactionManager value
     */
    public void setTransactionManager(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(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(ManagedConnectionFactory tmcf,
                                   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();

        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();
        rs = new ResourceSpec(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(Context ctx, 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);
            // poolLogger should be: Log.JONAS_JCA_PREFIX+".pool"
            poolTrace = (Logger) ctx.lookup(POOL_LOGGER);
        } catch (NamingException e) {
        }

        conTrace = Log.getLogger(Log.JONAS_JCA_PREFIX + ".connection");
        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 set of connection
        try {
            rmel = (ResourceManagerEventListener)
                    ctx.lookup(RESOURCE_MANAGER_EVENT_LISTENER);
        } catch (NamingException ne) {
        }

        // 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) {
                MCInfo mci = (MCInfo) 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 (Enumeration en = usedMCs.keys(); en.hasMoreElements();) {
                    Transaction tx = (Transaction) en.nextElement();
                    MCInfo mci = (MCInfo) usedMCs.get(tx);
                    if (mci == null) {
                        continue;
                    }
                    if (mci.rmeCalled) {
                        mci.rme.isValid = false;
                        rmel.connectionClosed(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) {
                MCInfo mci = (MCInfo) 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) {
                }
            }
        }
        poolMCs.closeAllConnections();
    }


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

        if (isEnabledDebug) {
            conTrace.log(BasicLevel.DEBUG, "");
        }
        MCInfo 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 : (MCInfo) 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
                    rs.cxRequestInfo = cxRequestInfo;
                    if (subject == null && cxRequestInfo != null) {
                        // Create a subject to pass on
                    }
                    try {
                        ManagedConnection mc = (ManagedConnection) poolMCs.getResource(rs);
                        if (mc == null) {
                            throw new ResourceException("ConnectionManagerImpl.allocateConnection: cannot allocate a ManagedConnection");
                        }
                        mci = (MCInfo) mc2mci.get(mc);
                        if (mci == null) {
                            mci = new MCInfo(mc);
                            mc2mci.put(mc, mci);
                        }
                        if (isEnabledDebug) {
                            trace.log(BasicLevel.DEBUG, "get a MC from the ra pool, mc=" + mci.mc);
                        }
                        conTrace.log(BasicLevel.DEBUG, " returning mc = " + mc);

                        if (transSupport.equalsIgnoreCase(LOCAL_TRANS_SUPPORT)) {
                            if (mci.lw == null) {
                                mci.lw = new LocalXAWrapper(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 RMEImpl(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.isValid = true;
                                    rmel.connectionOpened(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);

                conTrace.log(BasicLevel.DEBUG, " getting con(" + connection + ") from mc(" + mci.mc + ") of factory(" + jndiname + ")");
            }

            if (connection instanceof java.sql.Connection) {
                try {
                	org.objectweb.jonas.jdbc.ConnectionImpl jdbcConn = null;
                    if (connection instanceof org.objectweb.jonas.jdbc.ConnectionImpl) {
                    	jdbcConn = (org.objectweb.jonas.jdbc.ConnectionImpl) connection;
                        jdbcConn.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
                        jdbcConn.setUser();
                        // in case we do not start a Tx
                        // TODO this should be fruitless: remove it.
                        if (currentTx == null) {
                            jdbcConn.setAutoCommit(true);
                        }
                    } else {
                        // Need a wrapper for non JOnAS jdbc ConnectionImpl
                        connection = JonasSQLWrapper.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 org.objectweb.jonas.jdbc.ConnectionImpl) {
                                isClosed = jdbcConn.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.jonas.resource.pool.api.PoolMatchFactory
     */
    public boolean matchResource(Object res, 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.jonas.resource.pool.api.PoolMatchFactory
     */
    public Object matchResource(Set res, Object hints) throws Exception {
        ResourceSpec spec = (hints != null) ? (ResourceSpec) hints : new ResourceSpec(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: ResourceSpec
     *
     * @see org.objectweb.jonas.resource.pool.api.PoolMatchFactory
     */
    public Object createResource(Object hints) throws Exception {
        ResourceSpec spec = (hints != null) ? (ResourceSpec) hints : new ResourceSpec(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(MCInfo mcinfo, Object conn, String user, 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(MCInfo mcinfo, Object conn, String user, String sql,
                                           int resultSetType, int resultSetConcurrency)
            throws SQLException {
        return getPStatement(mcinfo, conn, user, sql, resultSetType, resultSetConcurrency,
                             -1, -1, null, null, PSWRAP_1);
    }

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

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

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

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


    private PreparedStatement getPStatement(MCInfo mcinfo,
                                            Object conn,
                                            String user,
                                            String sql,
                                            int resultSetType,
                                            int resultSetConcurrency,
                                            int resultSetHoldability,
                                            int autoGeneratedKeys,
                                            int [] columnIndexes,
                                            String [] columnNames,
                                            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(Object rMc) throws Exception {

        if (isEnabledDebug) {
            trace.log(BasicLevel.DEBUG, "MC: " + rMc);
        }
        if (rMc instanceof ManagedConnection) {
            synchronized (poolMCs) {
                ManagedConnection mc = (ManagedConnection) rMc;
                MCInfo mcinfo = (MCInfo) 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(MCInfo mcinfo) throws Exception {
        if (isEnabledDebug) {
            trace.log(BasicLevel.DEBUG, "MCInfo: " + 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(ConnectionEvent event) {

        synchronized (poolMCs) {

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

            MCInfo mci = (MCInfo) 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) {
                        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 MySynchro(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.getGlobalTx() != null) {
                            usedMCs.remove(mci.getGlobalTx());
                            mci.setGlobalTx(null);
                        }

                        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.isValid = false;
                        rmel.connectionClosed(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(ConnectionEvent event) {

        synchronized (poolMCs) {

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

            MCInfo mci = (MCInfo) 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.isValid = false;
                    rmel.connectionErrorOccured(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(ConnectionEvent event) {

        synchronized (poolMCs) {

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

            MCInfo mci = (MCInfo) 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.isValid = false;
                    rmel.connectionClosed(mci.rme);
                    mci.rmeCalled = false;
                }

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

                    try {
                        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(ConnectionEvent event) {

        synchronized (poolMCs) {

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

            MCInfo mci = (MCInfo) 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 ResourceManagerEventListener mc is released
                    mci.rme.isValid = false;
                    rmel.connectionClosed(mci.rme);
                    mci.rmeCalled = false;
                }

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

                    try {
                        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(ConnectionEvent event) {

        synchronized (poolMCs) {

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

            MCInfo mci = (MCInfo) 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
     */
    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
     */
    protected String getState(String prefix) {
        String res = null;
        synchronized(poolMCs) {
            res = prefix + "mcf=" + mcf + "\n";
            res += prefix + "ResourceSpec=" + rs.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 (Enumeration en = usedMCs.keys(); en.hasMoreElements();) {
                Object tx = en.nextElement();
                MCInfo mci = (MCInfo) usedMCs.get(tx);
                res += prefix + "MCI : tx=" + tx + "\n";
                res += mci.getState(prefix + "\t");
            }
            res += prefix + "mcs not attached to a tx:\n";
            for (Enumeration en = mcs.elements(); en.hasMoreElements();) {
                MCInfo mci = (MCInfo) en.nextElement();
                res += mci.getState(prefix + "\t");
            }
            res += prefix + "mcs waiting for tx commit or rollback:\n";
            for (Enumeration en = synchros.elements(); en.hasMoreElements();) {
                MCInfo mci = (MCInfo) en.nextElement();
                res += mci.getState(prefix + "\t");
            }
        }
        return res;
    }

    /**
     * Set the XAName to use
     * @param xanm String of XA Name
     */
    public void setXAName(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(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
        TransactionRecovery tr = ((Current) tm).getTransactionRecovery();
        if (tr == null || !((Current) tm).getDefaultRecovery()) {
            trace.log(BasicLevel.DEBUG, "No transaction recovery enabled");
            return;
        }

        synchronized (poolMCs) {

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

                mci = (MCInfo) mc2mci.get(mc);
                if (mci == null) {
                    mci = new MCInfo(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);
                }
                tr.registerResourceManager(xaName, jotmXar, "", tmProp, this);
            } catch (Exception ex) {
                trace.log(BasicLevel.ERROR, ex.getMessage(), ex);
                returnXAResource(xaName, 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 returnXAResource(String rmName, 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;
            }

            MCInfo mci = (MCInfo) 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);
            } catch (Exception ex) {
                trace.log(BasicLevel.ERROR, ex.getMessage(), ex);
            }

            jotmMc = null;
            jotmXar = null;
        }
    }

    // JSR 77 (JCAConnectionFactory methods)

    public Pool getPool() {
        return poolMCs;
    }

    public int getCheckLevel() {
        return jdbcConnLevel;
    }

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

    public String getTestStatement() {
        return jdbcConnTestStmt;
    }

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

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

	/**
	 * @param observable the observable to set
	 */
	public void setObservable(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(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(int connectionId) {
        ManagedConnection mc = poolMCs.getConnectionById(connectionId);
        // Look if used in a transaction
        Transaction mytx = null;
        if (usedMCs != null) {
            for (Enumeration en = usedMCs.keys(); en.hasMoreElements();) {
                Transaction tx = (Transaction) en.nextElement();
                MCInfo mci = (MCInfo) usedMCs.get(tx);
                if (mci != null && mci.mc.equals(mc)) {
                    mytx = tx;
                    break;
                }
            }
        }
        return poolMCs.getConnectionDetails(mc, mytx);
    }

}