static {
    try {
      CON_PROXY_CTOR = createProxyConstructor(ProxyConnection.class);

      Class[] argClasses = new Class[0];
      RS_CLOSE_METHOD = ResultSet.class.getMethod("close", argClasses);
      STMT_CLOSE_METHOD = Statement.class.getMethod("close", argClasses);

      CLOSE_ARGS = new Object[0];

      OBJECT_METHODS =
          Collections.unmodifiableSet(new HashSet(Arrays.asList(Object.class.getMethods())));
    } catch (Exception e) {
      // e.printStackTrace();
      logger.log(
          MLevel.SEVERE,
          "An Exception occurred in static initializer of" + C3P0PooledConnection.class.getName(),
          e);
      throw new InternalError(
          "Something is very wrong, or this is a pre 1.3 JVM."
              + "We cannot set up dynamic proxies and/or methods!");
    }
  }
Exemple #2
0
/*
 *  The primary purpose of C3P0Registry is to maintain a mapping of "identityTokens"
 *  to c3p0 DataSources so that if the same DataSource is looked up (and deserialized
 *  or dereferenced) via JNDI, c3p0 can ensure that the same instance is always returned.
 *  But there are subtle issues here. If C3P0Registry maintains hard references to
 *  DataSources, then they can never be garbage collected. But if c3p0 retains only
 *  weak references, then applications that look up DataSources, then dereference them,
 *  and then re-look them up again (not a great idea, but not uncommon) might see
 *  distinct DataSources over multiple lookups.
 *
 *  C3P0 resolves this issue has followed: At first creation or lookup of a PooledDataSource,
 *  c3p0 creates a hard reference to that DataSource. So long as the DataSource has not
 *  been close()ed or DataSources.destroy()ed, subsequent lookups will consistently
 *  return the same DataSource. If the DataSource is never closed, then there is a potential
 *  memory leak (as well as the potential Thread leak and Connection leak). But if
 *  the DataSource is close()ed, only weak refernces to the DataSource will be retained.
 *  A lookup of a DataSource after it has been close()ed within the current VM may
 *  return the previously close()ed instance, or may return a fresh instance, depending
 *  on whether the weak reference has been cleared. In other words, the result of
 *  looking up a DataSource after having close()ed it in the current VM is undefined.
 *
 *  Note that unpooled c3p0 DataSources are always held by weak references, since
 *  they are never explicitly close()ed. The result of looking up an unpooled DataSource,
 *  modifying it, dereferencing it, and then relooking up is therefore undefined as well.
 *
 *  These issues are mostly academic. Under normal use scenarios, how c3p0 deals with
 *  maintaining its registry doesn't much matter. In the past, c3p0 maintained hard
 *  references to DataSources indefinitely. At least one user ran into side effects
 *  of the unwanted retention of old DataSources (in a process left to run for months
 *  at a time, and frequently reconstructing multiple DataSources), so now we take care
 *  to ensure that when users properly close() and dereference DataSources, they can
 *  indeed be garbage collected.
 */
public final class C3P0Registry {
  private static final String MC_PARAM = "com.mchange.v2.c3p0.management.ManagementCoordinator";

  // MT: thread-safe
  static final MLogger logger = MLog.getLogger(C3P0Registry.class);

  // MT: protected by class' lock
  static boolean banner_printed = false;

  // MT: protected by class' lock
  static boolean registry_mbean_registered = false;

  // MT: thread-safe, immutable
  private static CoalesceChecker CC = IdentityTokenizedCoalesceChecker.INSTANCE;

  // MT: protected by class' lock
  // a weak, unsynchronized coalescer
  private static Coalescer idtCoalescer = CoalescerFactory.createCoalescer(CC, true, false);

  // MT: protected by class' lock
  private static Map tokensToTokenized = new DoubleWeakHashMap();

  // MT: protected by class' lock
  private static HashSet unclosedPooledDataSources = new HashSet();

  // MT: protected by its own lock
  private static Map classNamesToConnectionTesters = Collections.synchronizedMap(new HashMap());

  // MT: protected by its own lock
  private static Map classNamesToConnectionCustomizers = Collections.synchronizedMap(new HashMap());

  private static ManagementCoordinator mc;

  static {
    classNamesToConnectionTesters.put(
        C3P0Defaults.connectionTesterClassName(), C3P0Defaults.connectionTester());

    String userManagementCoordinator = C3P0ConfigUtils.getPropsFileConfigProperty(MC_PARAM);
    if (userManagementCoordinator != null) {
      try {
        mc = (ManagementCoordinator) Class.forName(userManagementCoordinator).newInstance();
      } catch (Exception e) {
        if (logger.isLoggable(MLevel.WARNING))
          logger.log(
              MLevel.WARNING,
              "Could not instantiate user-specified ManagementCoordinator "
                  + userManagementCoordinator
                  + ". Using NullManagementCoordinator (c3p0 JMX management disabled!)",
              e);
        mc = new NullManagementCoordinator();
      }
    } else {
      try {
        Class.forName("java.lang.management.ManagementFactory");

        mc =
            (ManagementCoordinator)
                Class.forName("com.mchange.v2.c3p0.management.ActiveManagementCoordinator")
                    .newInstance();
      } catch (Exception e) {
        if (logger.isLoggable(MLevel.INFO))
          logger.log(
              MLevel.INFO, "jdk1.5 management interfaces unavailable... JMX support disabled.", e);
        mc = new NullManagementCoordinator();
      }
    }
  }

  public static ConnectionTester getConnectionTester(String className) {
    try {
      ConnectionTester out = (ConnectionTester) classNamesToConnectionTesters.get(className);
      if (out == null) {
        out = (ConnectionTester) Class.forName(className).newInstance();
        classNamesToConnectionTesters.put(className, out);
      }
      return out;
    } catch (Exception e) {
      if (logger.isLoggable(MLevel.WARNING))
        logger.log(
            MLevel.WARNING,
            "Could not create for find ConnectionTester with class name '"
                + className
                + "'. Using default.",
            e);
      return C3P0Defaults.connectionTester();
    }
  }

  public static ConnectionCustomizer getConnectionCustomizer(String className) throws SQLException {
    if (className == null) return null;
    else {
      try {
        ConnectionCustomizer out =
            (ConnectionCustomizer) classNamesToConnectionCustomizers.get(className);
        if (out == null) {
          out = (ConnectionCustomizer) Class.forName(className).newInstance();
          classNamesToConnectionCustomizers.put(className, out);
        }
        return out;
      } catch (Exception e) {
        if (logger.isLoggable(MLevel.WARNING))
          logger.log(
              MLevel.WARNING,
              "Could not create for find ConnectionCustomizer with class name '" + className + "'.",
              e);
        throw SqlUtils.toSQLException(e);
      }
    }
  }

  // must be called from a static sync'ed method
  private static void banner() {
    if (!banner_printed) {
      if (logger.isLoggable(MLevel.INFO))
        logger.info(
            "Initializing c3p0-"
                + C3P0Substitutions.VERSION
                + " [built "
                + C3P0Substitutions.TIMESTAMP
                + "; debug? "
                + C3P0Substitutions.DEBUG
                + "; trace: "
                + C3P0Substitutions.TRACE
                + ']');
      banner_printed = true;
    }
  }

  // must be called from a static, sync'ed method
  private static void attemptRegisterRegistryMBean() {
    if (!registry_mbean_registered) {
      mc.attemptManageC3P0Registry();
      registry_mbean_registered = true;
    }
  }

  // must be called with class' lock
  private static boolean isIncorporated(IdentityTokenized idt) {
    return tokensToTokenized.keySet().contains(idt.getIdentityToken());
  }

  // must be called with class' lock
  private static void incorporate(IdentityTokenized idt) {
    tokensToTokenized.put(idt.getIdentityToken(), idt);
    if (idt instanceof PooledDataSource) {
      unclosedPooledDataSources.add(idt);
      mc.attemptManagePooledDataSource((PooledDataSource) idt);
    }
  }

  public static synchronized IdentityTokenized reregister(IdentityTokenized idt) {
    if (idt instanceof PooledDataSource) {
      banner();
      attemptRegisterRegistryMBean();
    }

    if (idt.getIdentityToken() == null)
      throw new RuntimeException(
          "[c3p0 issue] The identityToken of a registered object should be set prior to registration.");

    IdentityTokenized coalesceCheck = (IdentityTokenized) idtCoalescer.coalesce(idt);

    if (!isIncorporated(coalesceCheck)) incorporate(coalesceCheck);

    return coalesceCheck;
  }

  public static synchronized void markClosed(PooledDataSource pds) {
    unclosedPooledDataSources.remove(pds);
    mc.attemptUnmanagePooledDataSource(pds);
    if (unclosedPooledDataSources.isEmpty()) {
      mc.attemptUnmanageC3P0Registry();
      registry_mbean_registered = false;
    }
  }

  public static synchronized Set getPooledDataSources() {
    return (Set) unclosedPooledDataSources.clone();
  }

  /** @return the set of all PooledDataSources sharing the given dataSourceName */
  public static synchronized Set pooledDataSourcesByName(String dataSourceName) {
    Set out = new HashSet();
    for (Iterator ii = unclosedPooledDataSources.iterator(); ii.hasNext(); ) {
      PooledDataSource pds = (PooledDataSource) ii.next();
      if (pds.getDataSourceName().equals(dataSourceName)) out.add(pds);
    }
    return out;
  }

  /**
   * <b>Note:</b> If multiple PooledDataSources in your JVM share the same <tt>dataSourceName</tt>,
   * which of those multiple DataSources will be returned by this method is undefined!
   *
   * @return a PooledDataSource with the given <tt>dataSourceName</tt>, if at least one exists.
   *     <tt>null</tt> otherwise.
   */
  public static synchronized PooledDataSource pooledDataSourceByName(String dataSourceName) {
    for (Iterator ii = unclosedPooledDataSources.iterator(); ii.hasNext(); ) {
      PooledDataSource pds = (PooledDataSource) ii.next();
      if (pds.getDataSourceName().equals(dataSourceName)) return pds;
    }
    return null;
  }

  public static synchronized Set allIdentityTokens() {
    Set out = Collections.unmodifiableSet(tokensToTokenized.keySet());
    // System.err.println( "allIdentityTokens(): " + out );
    return out;
  }

  public static synchronized Set allIdentityTokenized() {
    HashSet out = new HashSet();
    out.addAll(tokensToTokenized.values());
    // System.err.println( "allIdentityTokenized(): " + out );
    return Collections.unmodifiableSet(out);
  }

  public static synchronized Set allPooledDataSources() {
    Set out = Collections.unmodifiableSet(unclosedPooledDataSources);
    // System.err.println( "allPooledDataSources(): " + out );
    return out;
  }

  public static synchronized int getNumPooledDataSources() {
    return unclosedPooledDataSources.size();
  }

  public static synchronized int getNumPoolsAllDataSources() throws SQLException {
    int count = 0;
    for (Iterator ii = unclosedPooledDataSources.iterator(); ii.hasNext(); ) {
      PooledDataSource pds = (PooledDataSource) ii.next();
      count += pds.getNumUserPools();
    }
    return count;
  }

  public synchronized int getNumThreadsAllThreadPools() throws SQLException {
    int count = 0;
    for (Iterator ii = unclosedPooledDataSources.iterator(); ii.hasNext(); ) {
      PooledDataSource pds = (PooledDataSource) ii.next();
      count += pds.getNumHelperThreads();
    }
    return count;
  }
}
Exemple #3
0
 public static synchronized Set allPooledDataSources() {
   Set out = Collections.unmodifiableSet(unclosedPooledDataSources);
   // System.err.println( "allPooledDataSources(): " + out );
   return out;
 }
Exemple #4
0
 public static synchronized Set allIdentityTokenized() {
   HashSet out = new HashSet();
   out.addAll(tokensToTokenized.values());
   // System.err.println( "allIdentityTokenized(): " + out );
   return Collections.unmodifiableSet(out);
 }
Exemple #5
0
 public static synchronized Set allIdentityTokens() {
   Set out = Collections.unmodifiableSet(tokensToTokenized.keySet());
   // System.err.println( "allIdentityTokens(): " + out );
   return out;
 }
  /*
   * TODO: factor all this convolution out into
   *       C3P0Statement
   */
  Statement createProxyStatement( // final Method cachedStmtProducingMethod,
      // final Object[] cachedStmtProducingMethodArgs,
      final boolean inner_is_cached,
      final Statement innerStmt)
      throws Exception {
    final Set activeResultSets = Collections.synchronizedSet(new HashSet());
    final Connection parentConnection = exposedProxy;

    if (Debug.DEBUG && parentConnection == null) {
      // 		System.err.print("PROBABLE C3P0 BUG -- ");
      // 		System.err.println(this + ": created a proxy Statement when there is no active, exposed
      // proxy Connection???");

      logger.warning(
          "PROBABLE C3P0 BUG -- "
              + this
              + ": created a proxy Statement when there is no active, exposed proxy Connection???");
    }

    // we can use this one wrapper under all circumstances
    // except jdbc3 CallableStatement multiple open ResultSets...
    // avoid object allocation in statement methods where possible.
    final StatementProxyingSetManagedResultSet mainResultSet =
        new StatementProxyingSetManagedResultSet(activeResultSets);

    class WrapperStatementHelper {
      Statement wrapperStmt;
      Statement nakedInner;

      public WrapperStatementHelper(Statement wrapperStmt, Statement nakedInner) {
        this.wrapperStmt = wrapperStmt;
        this.nakedInner = nakedInner;

        if (!inner_is_cached) uncachedActiveStatements.add(wrapperStmt);
      }

      private boolean closeAndRemoveActiveResultSets() {
        return closeAndRemoveResultSets(activeResultSets);
      }

      public ResultSet wrap(ResultSet rs) {
        if (mainResultSet.getInner() == null) {
          mainResultSet.setInner(rs);
          mainResultSet.setProxyStatement(wrapperStmt);
          return mainResultSet;
        } else {
          // for the case of multiple open ResultSets
          StatementProxyingSetManagedResultSet out =
              new StatementProxyingSetManagedResultSet(activeResultSets);
          out.setInner(rs);
          out.setProxyStatement(wrapperStmt);
          return out;
        }
      }

      public void doClose() throws SQLException {
        boolean okay = closeAndRemoveActiveResultSets();

        if (inner_is_cached) // this statement was cached
        scache.checkinStatement(innerStmt);
        else {
          innerStmt.close();
          uncachedActiveStatements.remove(wrapperStmt);
        }

        if (!okay) throw new SQLException("Failed to close an orphaned ResultSet properly.");
      }

      public Object doRawStatementOperation(Method m, Object target, Object[] args)
          throws IllegalAccessException, InvocationTargetException, SQLException {
        if (target == C3P0ProxyStatement.RAW_STATEMENT) target = nakedInner;
        for (int i = 0, len = args.length; i < len; ++i)
          if (args[i] == C3P0ProxyStatement.RAW_STATEMENT) args[i] = nakedInner;

        Object out = m.invoke(target, args);

        if (out instanceof ResultSet) out = wrap((ResultSet) out);

        return out;
      }
    }

    if (innerStmt instanceof CallableStatement) {
      class ProxyCallableStatement extends FilterCallableStatement implements C3P0ProxyStatement {
        WrapperStatementHelper wsh;

        ProxyCallableStatement(CallableStatement is) {
          super(is);
          this.wsh = new WrapperStatementHelper(this, is);
        }

        public Connection getConnection() {
          return parentConnection;
        }

        public ResultSet getResultSet() throws SQLException {
          return wsh.wrap(super.getResultSet());
        }

        public ResultSet getGeneratedKeys() throws SQLException {
          return wsh.wrap(super.getGeneratedKeys());
        }

        public ResultSet executeQuery(String sql) throws SQLException {
          return wsh.wrap(super.executeQuery(sql));
        }

        public ResultSet executeQuery() throws SQLException {
          return wsh.wrap(super.executeQuery());
        }

        public Object rawStatementOperation(Method m, Object target, Object[] args)
            throws IllegalAccessException, InvocationTargetException, SQLException {
          return wsh.doRawStatementOperation(m, target, args);
        }

        public void close() throws SQLException {
          wsh.doClose();
        }
      }

      return new ProxyCallableStatement((CallableStatement) innerStmt);
    } else if (innerStmt instanceof PreparedStatement) {
      class ProxyPreparedStatement extends FilterPreparedStatement implements C3P0ProxyStatement {
        WrapperStatementHelper wsh;

        ProxyPreparedStatement(PreparedStatement ps) {
          super(ps);
          this.wsh = new WrapperStatementHelper(this, ps);
        }

        public Connection getConnection() {
          return parentConnection;
        }

        public ResultSet getResultSet() throws SQLException {
          return wsh.wrap(super.getResultSet());
        }

        public ResultSet getGeneratedKeys() throws SQLException {
          return wsh.wrap(super.getGeneratedKeys());
        }

        public ResultSet executeQuery(String sql) throws SQLException {
          return wsh.wrap(super.executeQuery(sql));
        }

        public ResultSet executeQuery() throws SQLException {
          return wsh.wrap(super.executeQuery());
        }

        public Object rawStatementOperation(Method m, Object target, Object[] args)
            throws IllegalAccessException, InvocationTargetException, SQLException {
          return wsh.doRawStatementOperation(m, target, args);
        }

        public void close() throws SQLException {
          wsh.doClose();
        }
      }

      return new ProxyPreparedStatement((PreparedStatement) innerStmt);
    } else {
      class ProxyStatement extends FilterStatement implements C3P0ProxyStatement {
        WrapperStatementHelper wsh;

        ProxyStatement(Statement s) {
          super(s);
          this.wsh = new WrapperStatementHelper(this, s);
        }

        public Connection getConnection() {
          return parentConnection;
        }

        public ResultSet getResultSet() throws SQLException {
          return wsh.wrap(super.getResultSet());
        }

        public ResultSet getGeneratedKeys() throws SQLException {
          return wsh.wrap(super.getGeneratedKeys());
        }

        public ResultSet executeQuery(String sql) throws SQLException {
          return wsh.wrap(super.executeQuery(sql));
        }

        public Object rawStatementOperation(Method m, Object target, Object[] args)
            throws IllegalAccessException, InvocationTargetException, SQLException {
          return wsh.doRawStatementOperation(m, target, args);
        }

        public void close() throws SQLException {
          wsh.doClose();
        }
      }

      return new ProxyStatement(innerStmt);
    }
  }
public final class C3P0PooledConnection extends AbstractC3P0PooledConnection {
  static final MLogger logger = MLog.getLogger(C3P0PooledConnection.class);

  static final Class[] PROXY_CTOR_ARGS = new Class[] {InvocationHandler.class};

  static final Constructor CON_PROXY_CTOR;

  static final Method RS_CLOSE_METHOD;
  static final Method STMT_CLOSE_METHOD;

  static final Object[] CLOSE_ARGS;

  static final Set OBJECT_METHODS;

  /** @deprecated use or rewrite in terms of ReflectUtils.findProxyConstructor() */
  private static Constructor createProxyConstructor(Class intfc) throws NoSuchMethodException {
    Class[] proxyInterfaces = new Class[] {intfc};
    Class proxyCl =
        Proxy.getProxyClass(C3P0PooledConnection.class.getClassLoader(), proxyInterfaces);
    return proxyCl.getConstructor(PROXY_CTOR_ARGS);
  }

  static {
    try {
      CON_PROXY_CTOR = createProxyConstructor(ProxyConnection.class);

      Class[] argClasses = new Class[0];
      RS_CLOSE_METHOD = ResultSet.class.getMethod("close", argClasses);
      STMT_CLOSE_METHOD = Statement.class.getMethod("close", argClasses);

      CLOSE_ARGS = new Object[0];

      OBJECT_METHODS =
          Collections.unmodifiableSet(new HashSet(Arrays.asList(Object.class.getMethods())));
    } catch (Exception e) {
      // e.printStackTrace();
      logger.log(
          MLevel.SEVERE,
          "An Exception occurred in static initializer of" + C3P0PooledConnection.class.getName(),
          e);
      throw new InternalError(
          "Something is very wrong, or this is a pre 1.3 JVM."
              + "We cannot set up dynamic proxies and/or methods!");
    }
  }

  // MT: post-constructor constants
  final ConnectionTester connectionTester;
  final boolean autoCommitOnClose;
  final boolean forceIgnoreUnresolvedTransactions;
  final boolean supports_setTypeMap;
  final boolean supports_setHoldability;
  final int dflt_txn_isolation;
  final String dflt_catalog;
  final int dflt_holdability;

  // MT: thread-safe
  final ConnectionEventSupport ces = new ConnectionEventSupport(this);
  final StatementEventSupport ses = new StatementEventSupport(this);

  // MT: threadsafe, but reassigned (on close)
  volatile Connection physicalConnection;
  volatile Exception invalidatingException = null;

  // MT: threadsafe, but reassigned, and a read + reassignment must happen
  //    atomically. protected by this' lock.
  ProxyConnection exposedProxy;

  // MT: protected by this' lock
  int connection_status = ConnectionTester.CONNECTION_IS_OKAY;

  /*
   * contains all unclosed Statements not managed by a StatementCache
   * associated with the physical connection
   *
   * MT: protected by its own lock, not reassigned
   */
  final Set uncachedActiveStatements = Collections.synchronizedSet(new HashSet());

  // MT: Thread-safe, assigned
  volatile GooGooStatementCache scache;
  volatile boolean isolation_lvl_nondefault = false;
  volatile boolean catalog_nondefault = false;
  volatile boolean holdability_nondefault = false;

  public C3P0PooledConnection(
      Connection con,
      ConnectionTester connectionTester,
      boolean autoCommitOnClose,
      boolean forceIgnoreUnresolvedTransactions,
      ConnectionCustomizer cc,
      String pdsIdt)
      throws SQLException {
    try {
      if (cc != null) cc.onAcquire(con, pdsIdt);
    } catch (Exception e) {
      throw SqlUtils.toSQLException(e);
    }

    this.physicalConnection = con;
    this.connectionTester = connectionTester;
    this.autoCommitOnClose = autoCommitOnClose;
    this.forceIgnoreUnresolvedTransactions = forceIgnoreUnresolvedTransactions;
    this.supports_setTypeMap =
        C3P0ImplUtils.supportsMethod(con, "setTypeMap", new Class[] {Map.class});
    this.supports_setHoldability =
        C3P0ImplUtils.supportsMethod(con, "setHoldability", new Class[] {int.class});
    this.dflt_txn_isolation = con.getTransactionIsolation();
    this.dflt_catalog = con.getCatalog();
    this.dflt_holdability =
        (supports_setHoldability ? con.getHoldability() : ResultSet.CLOSE_CURSORS_AT_COMMIT);
  }

  // used by C3P0PooledConnectionPool
  Connection getPhysicalConnection() {
    return physicalConnection;
  }

  boolean isClosed() throws SQLException {
    return (physicalConnection == null);
  }

  void initStatementCache(GooGooStatementCache scache) {
    this.scache = scache;
  }

  // DEBUG
  // Exception origGet = null;

  // synchronized to protect exposedProxy
  public synchronized Connection getConnection() throws SQLException {
    if (exposedProxy != null) {
      // DEBUG
      // System.err.println("[DOUBLE_GET_TESTER] -- double getting a Connection from " + this );
      // new Exception("[DOUBLE_GET_TESTER] -- Double-Get Stack Trace").printStackTrace();
      // origGet.printStackTrace();

      // 		System.err.println("c3p0 -- Uh oh... getConnection() was called on a PooledConnection
      // when " +
      // 				   "it had already provided a client with a Connection that has not yet been " +
      // 				   "closed. This probably indicates a bug in the connection pool!!!");

      logger.warning(
          "c3p0 -- Uh oh... getConnection() was called on a PooledConnection when "
              + "it had already provided a client with a Connection that has not yet been "
              + "closed. This probably indicates a bug in the connection pool!!!");

      return exposedProxy;
    } else {
      return getCreateNewConnection();
    }
  }

  // must be called from sync'ed method to protecte
  // exposedProxy
  private Connection getCreateNewConnection() throws SQLException {
    try {
      // DEBUG
      // origGet = new Exception("[DOUBLE_GET_TESTER] -- Orig Get");

      ensureOkay();
      /*
       * we reset the physical connection when we close an exposed proxy
       * no need to do it again when we create one
       */
      // reset();
      return (exposedProxy = createProxyConnection());
    } catch (SQLException e) {
      throw e;
    } catch (Exception e) {
      // e.printStackTrace();
      logger.log(MLevel.WARNING, "Failed to acquire connection!", e);
      throw new SQLException("Failed to acquire connection!");
    }
  }

  public void closeAll() throws SQLException {
    if (scache != null) scache.closeAll(physicalConnection);
  }

  public void close() throws SQLException {
    this.close(false);
  }

  synchronized void closeMaybeCheckedOut(boolean checked_out) throws SQLException {
    if (checked_out) {
      // reset transaction state
      try {
        C3P0ImplUtils.resetTxnState(
            physicalConnection, forceIgnoreUnresolvedTransactions, autoCommitOnClose, false);
      } catch (Exception e) {
        if (logger.isLoggable(MLevel.FINER))
          logger.log(
              MLevel.FINER,
              "Failed to reset the transaction state of  "
                  + physicalConnection
                  + "just prior to close(). "
                  + "Only relevant at all if this was a Connection being forced close()ed midtransaction.",
              e);
      }
    }
    close(false);
  }

  // TODO: factor out repetitive debugging code
  private synchronized void close(boolean known_invalid) throws SQLException {
    // System.err.println("Closing " + this);
    if (physicalConnection != null) {
      try {
        StringBuffer debugOnlyLog = null;
        if (Debug.DEBUG && known_invalid) {
          debugOnlyLog = new StringBuffer();
          debugOnlyLog.append("[ exceptions: ");
        }

        Exception exc = cleanupUncachedActiveStatements();
        if (Debug.DEBUG && exc != null) {
          if (known_invalid) debugOnlyLog.append(exc.toString() + ' ');
          else
            logger.log(
                MLevel.WARNING,
                "An exception occurred while cleaning up uncached active Statements.",
                exc);
          // exc.printStackTrace();
        }

        try {
          // we've got to use silentClose() rather than close() here,
          // 'cuz if there's still an exposedProxy (say a user forgot to
          // close his Connection) before we close, and we use regular (loud)
          // close, we will try to check this dead or dying PooledConnection
          // back into the pool. We only want to do this when close is called
          // on user proxies, and the underlying PooledConnection might still
          // be good. The PooledConnection itself should only be closed by the
          // pool.
          if (exposedProxy != null) exposedProxy.silentClose(known_invalid);
        } catch (Exception e) {
          if (Debug.DEBUG) {
            if (known_invalid) debugOnlyLog.append(e.toString() + ' ');
            else logger.log(MLevel.WARNING, "An exception occurred.", exc);
            // e.printStackTrace();
          }
          exc = e;
        }
        try {
          this.closeAll();
        } catch (Exception e) {
          if (Debug.DEBUG) {
            if (known_invalid) debugOnlyLog.append(e.toString() + ' ');
            else logger.log(MLevel.WARNING, "An exception occurred.", exc);
            // e.printStackTrace();
          }
          exc = e;
        }

        try {
          physicalConnection.close();
        } catch (Exception e) {
          if (Debug.DEBUG) {
            if (known_invalid) debugOnlyLog.append(e.toString() + ' ');
            else logger.log(MLevel.WARNING, "An exception occurred.", exc);
            e.printStackTrace();
          }
          exc = e;
        }

        if (exc != null) {
          if (known_invalid) {
            debugOnlyLog.append(" ]");
            if (Debug.DEBUG) {
              // 						System.err.print("[DEBUG]" + this + ": while closing a PooledConnection known
              // to be invalid, ");
              // 						System.err.println("  some exceptions occurred. This is probably not a
              // problem:");
              // 						System.err.println( debugOnlyLog.toString() );

              logger.fine(
                  this
                      + ": while closing a PooledConnection known to be invalid, "
                      + "  some exceptions occurred. This is probably not a problem: "
                      + debugOnlyLog.toString());
            }
          } else
            throw new SQLException(
                "At least one error occurred while attempting "
                    + "to close() the PooledConnection: "
                    + exc);
        }
        if (Debug.TRACE == Debug.TRACE_MAX)
          logger.fine("C3P0PooledConnection closed. [" + this + ']');
        // System.err.println("C3P0PooledConnection closed. [" + this + ']');
      } finally {
        physicalConnection = null;
      }
    }
  }

  public void addConnectionEventListener(ConnectionEventListener listener) {
    ces.addConnectionEventListener(listener);
  }

  public void removeConnectionEventListener(ConnectionEventListener listener) {
    ces.removeConnectionEventListener(listener);
  }

  public void addStatementEventListener(StatementEventListener sel) {
    if (logger.isLoggable(MLevel.INFO))
      logger.info(
          "Per the JDBC4 spec, "
              + this.getClass().getName()
              + " accepts StatementListeners, but for now there is no circumstance under which they are notified!");

    ses.addStatementEventListener(sel);
  }

  public void removeStatementEventListener(StatementEventListener sel) {
    ses.removeStatementEventListener(sel);
  }

  private void reset() throws SQLException {
    reset(false);
  }

  private void reset(boolean known_resolved_txn) throws SQLException {
    ensureOkay();
    C3P0ImplUtils.resetTxnState(
        physicalConnection,
        forceIgnoreUnresolvedTransactions,
        autoCommitOnClose,
        known_resolved_txn);
    if (isolation_lvl_nondefault) {
      physicalConnection.setTransactionIsolation(dflt_txn_isolation);
      isolation_lvl_nondefault = false;
    }
    if (catalog_nondefault) {
      physicalConnection.setCatalog(dflt_catalog);
      catalog_nondefault = false;
    }
    if (holdability_nondefault) // we don't test if holdability is supported, 'cuz it can never go
                                // nondefault if it's not.
    {
      physicalConnection.setHoldability(dflt_holdability);
      holdability_nondefault = false;
    }

    try {
      physicalConnection.setReadOnly(false);
    } catch (Throwable t) {
      if (logger.isLoggable(MLevel.FINE))
        logger.log(
            MLevel.FINE,
            "A Throwable occurred while trying to reset the readOnly property of our Connection to false!",
            t);
    }

    try {
      if (supports_setTypeMap) physicalConnection.setTypeMap(Collections.EMPTY_MAP);
    } catch (Throwable t) {
      if (logger.isLoggable(MLevel.FINE))
        logger.log(
            MLevel.FINE,
            "A Throwable occurred while trying to reset the typeMap property of our Connection to Collections.EMPTY_MAP!",
            t);
    }
  }

  boolean closeAndRemoveResultSets(Set rsSet) {
    boolean okay = true;
    synchronized (rsSet) {
      for (Iterator ii = rsSet.iterator(); ii.hasNext(); ) {
        ResultSet rs = (ResultSet) ii.next();
        try {
          rs.close();
        } catch (SQLException e) {
          if (Debug.DEBUG)
            logger.log(MLevel.WARNING, "An exception occurred while cleaning up a ResultSet.", e);
          // e.printStackTrace();
          okay = false;
        } finally {
          ii.remove();
        }
      }
    }
    return okay;
  }

  void ensureOkay() throws SQLException {
    if (physicalConnection == null)
      throw new SQLException(
          invalidatingException == null
              ? "Connection is closed or broken."
              : "Connection is broken. Invalidating Exception: "
                  + invalidatingException.toString());
  }

  boolean closeAndRemoveResourcesInSet(Set s, Method closeMethod) {
    boolean okay = true;

    Set temp;
    synchronized (s) {
      temp = new HashSet(s);
    }

    for (Iterator ii = temp.iterator(); ii.hasNext(); ) {
      Object rsrc = ii.next();
      try {
        closeMethod.invoke(rsrc, CLOSE_ARGS);
      } catch (Exception e) {
        Throwable t = e;
        if (t instanceof InvocationTargetException)
          t = ((InvocationTargetException) e).getTargetException();
        logger.log(MLevel.WARNING, "An exception occurred while cleaning up a resource.", t);
        // t.printStackTrace();
        okay = false;
      } finally {
        s.remove(rsrc);
      }
    }

    // We had to abandon the idea of simply iterating over s directly, because
    // our resource close methods sometimes try to remove the resource from
    // its parent Set. This is important (when the user closes the resources
    // directly), but leads to ConcurrenModificationExceptions while we are
    // iterating over the Set to close. So, now we iterate over a copy, but remove
    // from the original Set. Since removal is idempotent, it don't matter if
    // the close method already provoked a remove. Sucks that we have to copy
    // the set though.
    //
    // Original (direct iteration) version:
    //
    //  	synchronized (s)
    //  	    {
    //  		for (Iterator ii = s.iterator(); ii.hasNext(); )
    //  		    {
    //  			Object rsrc = ii.next();
    //  			try
    //  			    { closeMethod.invoke(rsrc, CLOSE_ARGS); }
    //  			catch (Exception e)
    //  			    {
    //  				Throwable t = e;
    //  				if (t instanceof InvocationTargetException)
    //  				    t = ((InvocationTargetException) e).getTargetException();
    //  				t.printStackTrace();
    //  				okay = false;
    //  			    }
    //  			finally
    //  			    { ii.remove(); }
    //  		    }
    //  	    }

    return okay;
  }

  private SQLException cleanupUncachedActiveStatements() {
    // System.err.println("IN  uncachedActiveStatements.size(): " +
    // uncachedActiveStatements.size());

    boolean okay = closeAndRemoveResourcesInSet(uncachedActiveStatements, STMT_CLOSE_METHOD);

    // System.err.println("OUT uncachedActiveStatements.size(): " +
    // uncachedActiveStatements.size());

    if (okay) return null;
    else
      return new SQLException(
          "An exception occurred while trying to " + "clean up orphaned resources.");
  }

  ProxyConnection createProxyConnection() throws Exception {
    // we should always have a separate handler for each proxy connection, so
    // that object methods behave as expected... the handler covers
    // all object methods on behalf of the proxy.
    InvocationHandler handler = new ProxyConnectionInvocationHandler();
    return (ProxyConnection) CON_PROXY_CTOR.newInstance(new Object[] {handler});
  }

  Statement createProxyStatement(Statement innerStmt) throws Exception {
    return this.createProxyStatement(false, innerStmt);
  }

  private static class StatementProxyingSetManagedResultSet extends SetManagedResultSet {
    private Statement proxyStatement;

    StatementProxyingSetManagedResultSet(Set activeResultSets) {
      super(activeResultSets);
    }

    public void setProxyStatement(Statement proxyStatement) {
      this.proxyStatement = proxyStatement;
    }

    public Statement getStatement() throws SQLException {
      return (proxyStatement == null ? super.getStatement() : proxyStatement);
    }
  }

  /*
   * TODO: factor all this convolution out into
   *       C3P0Statement
   */
  Statement createProxyStatement( // final Method cachedStmtProducingMethod,
      // final Object[] cachedStmtProducingMethodArgs,
      final boolean inner_is_cached,
      final Statement innerStmt)
      throws Exception {
    final Set activeResultSets = Collections.synchronizedSet(new HashSet());
    final Connection parentConnection = exposedProxy;

    if (Debug.DEBUG && parentConnection == null) {
      // 		System.err.print("PROBABLE C3P0 BUG -- ");
      // 		System.err.println(this + ": created a proxy Statement when there is no active, exposed
      // proxy Connection???");

      logger.warning(
          "PROBABLE C3P0 BUG -- "
              + this
              + ": created a proxy Statement when there is no active, exposed proxy Connection???");
    }

    // we can use this one wrapper under all circumstances
    // except jdbc3 CallableStatement multiple open ResultSets...
    // avoid object allocation in statement methods where possible.
    final StatementProxyingSetManagedResultSet mainResultSet =
        new StatementProxyingSetManagedResultSet(activeResultSets);

    class WrapperStatementHelper {
      Statement wrapperStmt;
      Statement nakedInner;

      public WrapperStatementHelper(Statement wrapperStmt, Statement nakedInner) {
        this.wrapperStmt = wrapperStmt;
        this.nakedInner = nakedInner;

        if (!inner_is_cached) uncachedActiveStatements.add(wrapperStmt);
      }

      private boolean closeAndRemoveActiveResultSets() {
        return closeAndRemoveResultSets(activeResultSets);
      }

      public ResultSet wrap(ResultSet rs) {
        if (mainResultSet.getInner() == null) {
          mainResultSet.setInner(rs);
          mainResultSet.setProxyStatement(wrapperStmt);
          return mainResultSet;
        } else {
          // for the case of multiple open ResultSets
          StatementProxyingSetManagedResultSet out =
              new StatementProxyingSetManagedResultSet(activeResultSets);
          out.setInner(rs);
          out.setProxyStatement(wrapperStmt);
          return out;
        }
      }

      public void doClose() throws SQLException {
        boolean okay = closeAndRemoveActiveResultSets();

        if (inner_is_cached) // this statement was cached
        scache.checkinStatement(innerStmt);
        else {
          innerStmt.close();
          uncachedActiveStatements.remove(wrapperStmt);
        }

        if (!okay) throw new SQLException("Failed to close an orphaned ResultSet properly.");
      }

      public Object doRawStatementOperation(Method m, Object target, Object[] args)
          throws IllegalAccessException, InvocationTargetException, SQLException {
        if (target == C3P0ProxyStatement.RAW_STATEMENT) target = nakedInner;
        for (int i = 0, len = args.length; i < len; ++i)
          if (args[i] == C3P0ProxyStatement.RAW_STATEMENT) args[i] = nakedInner;

        Object out = m.invoke(target, args);

        if (out instanceof ResultSet) out = wrap((ResultSet) out);

        return out;
      }
    }

    if (innerStmt instanceof CallableStatement) {
      class ProxyCallableStatement extends FilterCallableStatement implements C3P0ProxyStatement {
        WrapperStatementHelper wsh;

        ProxyCallableStatement(CallableStatement is) {
          super(is);
          this.wsh = new WrapperStatementHelper(this, is);
        }

        public Connection getConnection() {
          return parentConnection;
        }

        public ResultSet getResultSet() throws SQLException {
          return wsh.wrap(super.getResultSet());
        }

        public ResultSet getGeneratedKeys() throws SQLException {
          return wsh.wrap(super.getGeneratedKeys());
        }

        public ResultSet executeQuery(String sql) throws SQLException {
          return wsh.wrap(super.executeQuery(sql));
        }

        public ResultSet executeQuery() throws SQLException {
          return wsh.wrap(super.executeQuery());
        }

        public Object rawStatementOperation(Method m, Object target, Object[] args)
            throws IllegalAccessException, InvocationTargetException, SQLException {
          return wsh.doRawStatementOperation(m, target, args);
        }

        public void close() throws SQLException {
          wsh.doClose();
        }
      }

      return new ProxyCallableStatement((CallableStatement) innerStmt);
    } else if (innerStmt instanceof PreparedStatement) {
      class ProxyPreparedStatement extends FilterPreparedStatement implements C3P0ProxyStatement {
        WrapperStatementHelper wsh;

        ProxyPreparedStatement(PreparedStatement ps) {
          super(ps);
          this.wsh = new WrapperStatementHelper(this, ps);
        }

        public Connection getConnection() {
          return parentConnection;
        }

        public ResultSet getResultSet() throws SQLException {
          return wsh.wrap(super.getResultSet());
        }

        public ResultSet getGeneratedKeys() throws SQLException {
          return wsh.wrap(super.getGeneratedKeys());
        }

        public ResultSet executeQuery(String sql) throws SQLException {
          return wsh.wrap(super.executeQuery(sql));
        }

        public ResultSet executeQuery() throws SQLException {
          return wsh.wrap(super.executeQuery());
        }

        public Object rawStatementOperation(Method m, Object target, Object[] args)
            throws IllegalAccessException, InvocationTargetException, SQLException {
          return wsh.doRawStatementOperation(m, target, args);
        }

        public void close() throws SQLException {
          wsh.doClose();
        }
      }

      return new ProxyPreparedStatement((PreparedStatement) innerStmt);
    } else {
      class ProxyStatement extends FilterStatement implements C3P0ProxyStatement {
        WrapperStatementHelper wsh;

        ProxyStatement(Statement s) {
          super(s);
          this.wsh = new WrapperStatementHelper(this, s);
        }

        public Connection getConnection() {
          return parentConnection;
        }

        public ResultSet getResultSet() throws SQLException {
          return wsh.wrap(super.getResultSet());
        }

        public ResultSet getGeneratedKeys() throws SQLException {
          return wsh.wrap(super.getGeneratedKeys());
        }

        public ResultSet executeQuery(String sql) throws SQLException {
          return wsh.wrap(super.executeQuery(sql));
        }

        public Object rawStatementOperation(Method m, Object target, Object[] args)
            throws IllegalAccessException, InvocationTargetException, SQLException {
          return wsh.doRawStatementOperation(m, target, args);
        }

        public void close() throws SQLException {
          wsh.doClose();
        }
      }

      return new ProxyStatement(innerStmt);
    }
  }

  final class ProxyConnectionInvocationHandler implements InvocationHandler {
    // MT: ThreadSafe, but reassigned -- protected by this' lock
    Connection activeConnection = physicalConnection;
    DatabaseMetaData metaData = null;
    boolean connection_error_signaled = false;

    /*
     * contains all unclosed ResultSets derived from this Connection's metadata
     * associated with the physical connection
     *
     * MT: protected by this' lock
     */
    final Set activeMetaDataResultSets = new HashSet();

    // being careful with doRawConnectionOperation
    // we initialize lazily, because this will be very rarely used
    Set doRawResultSets = null;

    boolean txn_known_resolved = true;

    public String toString() {
      return "C3P0ProxyConnection [Invocation Handler: " + super.toString() + ']';
    }

    private Object doRawConnectionOperation(Method m, Object target, Object[] args)
        throws IllegalAccessException, InvocationTargetException, SQLException, Exception {
      if (activeConnection == null)
        throw new SQLException(
            "Connection previously closed. You cannot operate on a closed Connection.");

      if (target == C3P0ProxyConnection.RAW_CONNECTION) target = activeConnection;
      for (int i = 0, len = args.length; i < len; ++i)
        if (args[i] == C3P0ProxyConnection.RAW_CONNECTION) args[i] = activeConnection;

      Object out = m.invoke(target, args);

      // we never cache Statements generated by an operation on the raw Connection
      if (out instanceof Statement) out = createProxyStatement(false, (Statement) out);
      else if (out instanceof ResultSet) {
        if (doRawResultSets == null) doRawResultSets = new HashSet();
        out = new NullStatementSetManagedResultSet((ResultSet) out, doRawResultSets);
      }
      return out;
    }

    public synchronized Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
      if (OBJECT_METHODS.contains(m)) return m.invoke(this, args);

      try {
        String mname = m.getName();
        if (activeConnection != null) {
          if (mname.equals("rawConnectionOperation")) {
            ensureOkay();
            txn_known_resolved = false;

            return doRawConnectionOperation((Method) args[0], args[1], (Object[]) args[2]);
          } else if (mname.equals("setTransactionIsolation")) {
            ensureOkay();

            // don't modify txn_known_resolved

            m.invoke(activeConnection, args);

            int lvl = ((Integer) args[0]).intValue();
            isolation_lvl_nondefault = (lvl != dflt_txn_isolation);

            // System.err.println("updated txn isolation to " + lvl + ", nondefault level? " +
            // isolation_lvl_nondefault);

            return null;
          } else if (mname.equals("setCatalog")) {
            ensureOkay();

            // don't modify txn_known_resolved

            m.invoke(activeConnection, args);

            String catalog = (String) args[0];
            catalog_nondefault = ObjectUtils.eqOrBothNull(catalog, dflt_catalog);

            return null;
          } else if (mname.equals("setHoldability")) {
            ensureOkay();

            // don't modify txn_known_resolved

            m.invoke(
                activeConnection,
                args); // will throw an exception if setHoldability() not supported...

            int holdability = ((Integer) args[0]).intValue();
            holdability_nondefault = (holdability != dflt_holdability);

            return null;
          } else if (mname.equals("createStatement")) {
            ensureOkay();
            txn_known_resolved = false;

            Object stmt = m.invoke(activeConnection, args);
            return createProxyStatement((Statement) stmt);
          } else if (mname.equals("prepareStatement")) {
            ensureOkay();
            txn_known_resolved = false;

            Object pstmt;
            if (scache == null) {
              pstmt = m.invoke(activeConnection, args);
              return createProxyStatement((Statement) pstmt);
            } else {
              pstmt = scache.checkoutStatement(physicalConnection, m, args);
              return createProxyStatement(true, (Statement) pstmt);
            }
          } else if (mname.equals("prepareCall")) {
            ensureOkay();
            txn_known_resolved = false;

            Object cstmt;
            if (scache == null) {
              cstmt = m.invoke(activeConnection, args);
              return createProxyStatement((Statement) cstmt);
            } else {
              cstmt = scache.checkoutStatement(physicalConnection, m, args);
              return createProxyStatement(true, (Statement) cstmt);
            }
          } else if (mname.equals("getMetaData")) {
            ensureOkay();
            txn_known_resolved = false; // views of tables etc. might be txn dependent

            DatabaseMetaData innerMd = activeConnection.getMetaData();
            if (metaData == null) {
              // exposedProxy is protected by C3P0PooledConnection.this' lock
              synchronized (C3P0PooledConnection.this) {
                metaData =
                    new SetManagedDatabaseMetaData(innerMd, activeMetaDataResultSets, exposedProxy);
              }
            }
            return metaData;
          } else if (mname.equals("silentClose")) {
            // the PooledConnection doesn't have to be okay

            doSilentClose(proxy, ((Boolean) args[0]).booleanValue(), this.txn_known_resolved);
            return null;
          } else if (mname.equals("close")) {
            // the PooledConnection doesn't have to be okay

            Exception e = doSilentClose(proxy, false, this.txn_known_resolved);
            if (!connection_error_signaled) ces.fireConnectionClosed();
            // System.err.println("close() called on a ProxyConnection.");
            if (e != null) {
              // 					    System.err.print("user close exception -- ");
              // 					    e.printStackTrace();
              throw e;
            } else return null;
          }
          // 			    else if ( mname.equals("finalize") ) //REMOVE THIS CASE -- TMP DEBUG
          // 				{
          // 				    System.err.println("Connection apparently finalized!");
          // 				    return m.invoke( activeConnection, args );
          // 				}
          else {
            ensureOkay();

            // we've disabled setting txn_known_resolved to true, ever, because
            // we failed to deal with the case that clients would work with previously
            // acquired Statements and ResultSets after a commit(), rollback(), or setAutoCommit().
            // the new non-reflective proxies have been modified to deal with this case.
            // here, with soon-to-be-deprecated in "traditional reflective proxies mode"
            // we are reverting to the conservative, always-presume-you-have-to-rollback
            // policy.

            // txn_known_resolved = ( mname.equals("commit") || mname.equals( "rollback" ) ||
            // mname.equals( "setAutoCommit" ) );
            txn_known_resolved = false;

            return m.invoke(activeConnection, args);
          }
        } else {
          if (mname.equals("close") || mname.equals("silentClose")) return null;
          else if (mname.equals("isClosed")) return Boolean.TRUE;
          else {
            throw new SQLException("You can't operate on " + "a closed connection!!!");
          }
        }
      } catch (InvocationTargetException e) {
        Throwable convertMe = e.getTargetException();
        SQLException sqle = handleMaybeFatalToPooledConnection(convertMe, proxy, false);
        sqle.fillInStackTrace();
        throw sqle;
      }
    }

    private Exception doSilentClose(Object proxyConnection, boolean pooled_connection_is_dead) {
      return doSilentClose(proxyConnection, pooled_connection_is_dead, false);
    }

    private Exception doSilentClose(
        Object proxyConnection, boolean pooled_connection_is_dead, boolean known_resolved_txn) {
      if (activeConnection != null) {
        synchronized (
            C3P0PooledConnection
                .this) // uh oh... this is a nested lock acq... is there a deadlock hazard here?
        {
          if (C3P0PooledConnection.this.exposedProxy == proxyConnection) {
            C3P0PooledConnection.this.exposedProxy = null;
            // System.err.println("Reset exposed proxy.");

            // DEBUG
            // origGet = null;
          } else // else case -- DEBUG only
          logger.warning(
                "(c3p0 issue) doSilentClose( ... ) called on a proxyConnection "
                    + "other than the current exposed proxy for its PooledConnection. [exposedProxy: "
                    + exposedProxy
                    + ", proxyConnection: "
                    + proxyConnection);
          // 				System.err.println("[DEBUG] WARNING: doSilentClose( ... ) called on a
          // proxyConnection " +
          // 						   "other than the current exposed proxy for its PooledConnection. [exposedProxy:
          // " +
          // 						   exposedProxy + ", proxyConnection: " + proxyConnection);
        }

        Exception out = null;

        Exception exc1 = null, exc2 = null, exc3 = null, exc4 = null;
        try {
          if (!pooled_connection_is_dead) C3P0PooledConnection.this.reset(known_resolved_txn);
        } catch (Exception e) {
          exc1 = e;
          // 		    if (Debug.DEBUG)
          // 			{
          // 			    System.err.print("exc1 -- ");
          // 			    exc1.printStackTrace();
          // 			}
        }

        exc2 = cleanupUncachedActiveStatements();
        // 	    if (Debug.DEBUG && exc2 != null)
        // 		{
        // 		    System.err.print("exc2 -- ");
        // 		    exc2.printStackTrace();
        // 		}
        String errSource;
        if (doRawResultSets != null) {
          activeMetaDataResultSets.addAll(doRawResultSets);
          errSource = "DataBaseMetaData or raw Connection operation";
        } else errSource = "DataBaseMetaData";

        if (!closeAndRemoveResultSets(activeMetaDataResultSets))
          exc3 = new SQLException("Failed to close some " + errSource + " Result Sets.");
        // 	    if (Debug.DEBUG && exc3 != null)
        // 		{
        // 		    System.err.print("exc3 -- ");
        // 		    exc3.printStackTrace();
        // 		}
        if (scache != null) {
          try {
            scache.checkinAll(physicalConnection);
          } catch (Exception e) {
            exc4 = e;
          }
          // 		    if (Debug.DEBUG && exc4 != null)
          // 			{
          // 			    System.err.print("exc4 -- ");
          // 			    exc4.printStackTrace();
          // 			}
        }

        if (exc1 != null) {
          handleMaybeFatalToPooledConnection(exc1, proxyConnection, true);
          out = exc1;
        } else if (exc2 != null) {
          handleMaybeFatalToPooledConnection(exc2, proxyConnection, true);
          out = exc2;
        } else if (exc3 != null) {
          handleMaybeFatalToPooledConnection(exc3, proxyConnection, true);
          out = exc3;
        } else if (exc4 != null) {
          handleMaybeFatalToPooledConnection(exc4, proxyConnection, true);
          out = exc4;
        }

        // 	    if (out != null)
        // 		{
        // 		    System.err.print("out -- ");
        // 		    out.printStackTrace();
        // 		}

        activeConnection = null;
        return out;
      } else return null;
    }

    private SQLException handleMaybeFatalToPooledConnection(
        Throwable t, Object proxyConnection, boolean already_closed) {
      // System.err.println("handleMaybeFatalToPooledConnection()");

      SQLException sqle = SqlUtils.toSQLException(t);
      int status = connectionTester.statusOnException(physicalConnection, sqle);
      updateConnectionStatus(status);
      if (status != ConnectionTester.CONNECTION_IS_OKAY) {
        if (Debug.DEBUG) {
          // 			    System.err.print(C3P0PooledConnection.this + " will no longer be pooled because
          // it has been " +
          // 					     "marked invalid by the following Exception: ");
          // 			    t.printStackTrace();

          logger.log(
              MLevel.INFO,
              C3P0PooledConnection.this
                  + " will no longer be pooled because it has been marked invalid by an Exception.",
              t);
        }

        invalidatingException = sqle;

        /*
          ------
          A users have complained that SQLExceptions ought not close their Connections underneath
          them under any circumstance. Signalling the Connection error after updating the Connection
          status should be sufficient from the pool's perspective, because the PooledConnection
          will be marked broken by the pool and will be destroyed on checkin. I think actually
          close()ing the Connection when it appears to be broken rather than waiting for users
          to close() it themselves is overly aggressive, so I'm commenting the old behavior out.
          The only potential downside to this approach is that users who do not close() in a finally
          clause properly might see their close()es skipped by exceptions that previously would
          have led to automatic close(). But relying on the automatic close() was never reliable
          (since it only ever happened when c3p0 determined a Connection to be absolutely broken),
          and is generally speaking a client error that c3p0 ought not be responsible for dealing
          with. I think it's right to leave this out. -- swaldman 2004-12-09
          ------

          if (! already_closed )
              doSilentClose( proxyConnection, true );
        */

        if (!connection_error_signaled) {
          ces.fireConnectionErrorOccurred(sqle);
          connection_error_signaled = true;
        }
      }
      return sqle;
    }
  }

  interface ProxyConnection extends C3P0ProxyConnection {
    void silentClose(boolean known_invalid) throws SQLException;
  }

  public synchronized int getConnectionStatus() {
    return this.connection_status;
  }

  private synchronized void updateConnectionStatus(int status) {
    switch (this.connection_status) {
      case ConnectionTester.DATABASE_IS_INVALID:
        // can't get worse than this, do nothing.
        break;
      case ConnectionTester.CONNECTION_IS_INVALID:
        if (status == ConnectionTester.DATABASE_IS_INVALID) doBadUpdate(status);
        break;
      case ConnectionTester.CONNECTION_IS_OKAY:
        if (status != ConnectionTester.CONNECTION_IS_OKAY) doBadUpdate(status);
        break;
      default:
        throw new InternalError(this + " -- Illegal Connection Status: " + this.connection_status);
    }
  }

  // must be called from sync'ed method
  private void doBadUpdate(int new_status) {
    this.connection_status = new_status;
    try {
      this.close(true);
    } catch (SQLException e) {
      // 		System.err.print("Broken Connection Close Error: ");
      // 		e.printStackTrace();

      logger.log(MLevel.WARNING, "Broken Connection Close Error. ", e);
    }
  }
}