/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.dbcp.datasources;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;

import javax.sql.ConnectionEvent;
import javax.sql.ConnectionEventListener;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;

import org.apache.commons.pool.KeyedObjectPool;
import org.apache.commons.pool.KeyedPoolableObjectFactory;

A {*link PoolableObjectFactory} that creates {*link PoolableConnection}s.
Author:John D. McNally
Version:$Revision: 907288 $ $Date: 2010-02-06 14:42:58 -0500 (Sat, 06 Feb 2010) $
/** * A {*link PoolableObjectFactory} that creates * {*link PoolableConnection}s. * * @author John D. McNally * @version $Revision: 907288 $ $Date: 2010-02-06 14:42:58 -0500 (Sat, 06 Feb 2010) $ */
class KeyedCPDSConnectionFactory implements KeyedPoolableObjectFactory, ConnectionEventListener, PooledConnectionManager { private static final String NO_KEY_MESSAGE = "close() was called on a Connection, but " + "I have no record of the underlying PooledConnection."; private final ConnectionPoolDataSource _cpds; private final String _validationQuery; private final boolean _rollbackAfterValidation; private final KeyedObjectPool _pool;
Map of PooledConnections for which close events are ignored. Connections are muted when they are being validated.
/** * Map of PooledConnections for which close events are ignored. * Connections are muted when they are being validated. */
private final Map /* <PooledConnection, null> */ validatingMap = new HashMap();
Map of PooledConnectionAndInfo instances
/** * Map of PooledConnectionAndInfo instances */
private final WeakHashMap /* <PooledConnection, PooledConnectionAndInfo> */ pcMap = new WeakHashMap();
Create a new KeyedPoolableConnectionFactory.
Params:
  • cpds – the ConnectionPoolDataSource from which to obtain PooledConnection's
  • pool – the {*link ObjectPool} in which to pool those {*link Connection}s
  • validationQuery – a query to use to {*link #validateObject validate} {*link Connection}s. Should return at least one row. May be null
/** * Create a new <tt>KeyedPoolableConnectionFactory</tt>. * @param cpds the ConnectionPoolDataSource from which to obtain PooledConnection's * @param pool the {*link ObjectPool} in which to pool those {*link Connection}s * @param validationQuery a query to use to {*link #validateObject validate} {*link Connection}s. * Should return at least one row. May be <tt>null</tt> */
public KeyedCPDSConnectionFactory(ConnectionPoolDataSource cpds, KeyedObjectPool pool, String validationQuery) { this(cpds , pool, validationQuery, false); }
Create a new KeyedPoolableConnectionFactory.
Params:
  • cpds – the ConnectionPoolDataSource from which to obtain PooledConnections
  • pool – the KeyedObjectPool in which to pool those Connections
  • validationQuery – a query to use to validate Connections. Should return at least one row. May be null
  • rollbackAfterValidation – whether a rollback should be issued after validating Connections.
/** * Create a new <tt>KeyedPoolableConnectionFactory</tt>. * @param cpds the ConnectionPoolDataSource from which to obtain * PooledConnections * @param pool the {@link KeyedObjectPool} in which to pool those * {@link Connection}s * @param validationQuery a query to use to {@link #validateObject validate} * {@link Connection}s. Should return at least one row. May be <tt>null</tt> * @param rollbackAfterValidation whether a rollback should be issued after * {@link #validateObject validating} {@link Connection}s. */
public KeyedCPDSConnectionFactory(ConnectionPoolDataSource cpds, KeyedObjectPool pool, String validationQuery, boolean rollbackAfterValidation) { _cpds = cpds; _pool = pool; pool.setFactory(this); _validationQuery = validationQuery; _rollbackAfterValidation = rollbackAfterValidation; }
Returns the keyed object pool used to pool connections created by this factory.
Returns:KeyedObjectPool managing pooled connections
/** * Returns the keyed object pool used to pool connections created by this factory. * * @return KeyedObjectPool managing pooled connections */
public KeyedObjectPool getPool() { return _pool; }
Creates a new PooledConnectionAndInfo from the given UserPassKey.
Params:
Throws:
See Also:
/** * Creates a new {@link PooledConnectionAndInfo} from the given {@link UserPassKey}. * * @param key {@link UserPassKey} containing user credentials * @throws SQLException if the connection could not be created. * @see org.apache.commons.pool.KeyedPoolableObjectFactory#makeObject(java.lang.Object) */
public synchronized Object makeObject(Object key) throws Exception { Object obj = null; UserPassKey upkey = (UserPassKey)key; PooledConnection pc = null; String username = upkey.getUsername(); String password = upkey.getPassword(); if (username == null) { pc = _cpds.getPooledConnection(); } else { pc = _cpds.getPooledConnection(username, password); } if (pc == null) { throw new IllegalStateException("Connection pool data source returned null from getPooledConnection"); } // should we add this object as a listener or the pool. // consider the validateObject method in decision pc.addConnectionEventListener(this); obj = new PooledConnectionAndInfo(pc, username, password); pcMap.put(pc, obj); return obj; }
Closes the PooledConnection and stops listening for events from it.
/** * Closes the PooledConnection and stops listening for events from it. */
public void destroyObject(Object key, Object obj) throws Exception { if (obj instanceof PooledConnectionAndInfo) { PooledConnection pc = ((PooledConnectionAndInfo)obj).getPooledConnection(); pc.removeConnectionEventListener(this); pcMap.remove(pc); pc.close(); } }
Validates a pooled connection.
Params:
Returns:true if validation suceeds
/** * Validates a pooled connection. * * @param key ignored * @param obj {@link PooledConnectionAndInfo} containing the connection to validate * @return true if validation suceeds */
public boolean validateObject(Object key, Object obj) { boolean valid = false; if (obj instanceof PooledConnectionAndInfo) { PooledConnection pconn = ((PooledConnectionAndInfo)obj).getPooledConnection(); String query = _validationQuery; if (null != query) { Connection conn = null; Statement stmt = null; ResultSet rset = null; // logical Connection from the PooledConnection must be closed // before another one can be requested and closing it will // generate an event. Keep track so we know not to return // the PooledConnection validatingMap.put(pconn, null); try { conn = pconn.getConnection(); stmt = conn.createStatement(); rset = stmt.executeQuery(query); if (rset.next()) { valid = true; } else { valid = false; } if (_rollbackAfterValidation) { conn.rollback(); } } catch(Exception e) { valid = false; } finally { if (rset != null) { try { rset.close(); } catch (Throwable t) { // ignore } } if (stmt != null) { try { stmt.close(); } catch (Throwable t) { // ignore } } if (conn != null) { try { conn.close(); } catch (Throwable t) { // ignore } } validatingMap.remove(pconn); } } else { valid = true; } } else { valid = false; } return valid; } public void passivateObject(Object key, Object obj) { } public void activateObject(Object key, Object obj) { } // *********************************************************************** // java.sql.ConnectionEventListener implementation // ***********************************************************************
This will be called if the Connection returned by the getConnection method came from a PooledConnection, and the user calls the close() method of this connection object. What we need to do here is to release this PooledConnection from our pool...
/** * This will be called if the Connection returned by the getConnection * method came from a PooledConnection, and the user calls the close() * method of this connection object. What we need to do here is to * release this PooledConnection from our pool... */
public void connectionClosed(ConnectionEvent event) { PooledConnection pc = (PooledConnection)event.getSource(); // if this event occurred because we were validating, or if this // connection has been marked for removal, ignore it // otherwise return the connection to the pool. if (!validatingMap.containsKey(pc)) { PooledConnectionAndInfo info = (PooledConnectionAndInfo) pcMap.get(pc); if (info == null) { throw new IllegalStateException(NO_KEY_MESSAGE); } try { _pool.returnObject(info.getUserPassKey(), info); } catch (Exception e) { System.err.println("CLOSING DOWN CONNECTION AS IT COULD " + "NOT BE RETURNED TO THE POOL"); pc.removeConnectionEventListener(this); try { _pool.invalidateObject(info.getUserPassKey(), info); } catch (Exception e3) { System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + info); e3.printStackTrace(); } } } }
If a fatal error occurs, close the underlying physical connection so as not to be returned in the future
/** * If a fatal error occurs, close the underlying physical connection so as * not to be returned in the future */
public void connectionErrorOccurred(ConnectionEvent event) { PooledConnection pc = (PooledConnection)event.getSource(); if (null != event.getSQLException()) { System.err .println("CLOSING DOWN CONNECTION DUE TO INTERNAL ERROR (" + event.getSQLException() + ")"); } pc.removeConnectionEventListener(this); PooledConnectionAndInfo info = (PooledConnectionAndInfo) pcMap.get(pc); if (info == null) { throw new IllegalStateException(NO_KEY_MESSAGE); } try { _pool.invalidateObject(info.getUserPassKey(), info); } catch (Exception e) { System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + info); e.printStackTrace(); } } // *********************************************************************** // PooledConnectionManager implementation // ***********************************************************************
Invalidates the PooledConnection in the pool. The KeyedCPDSConnectionFactory closes the connection and pool counters are updated appropriately. Also clears any idle instances associated with the username that was used to create the PooledConnection. Connections associated with this user are not affected and they will not be automatically closed on return to the pool.
/** * Invalidates the PooledConnection in the pool. The KeyedCPDSConnectionFactory * closes the connection and pool counters are updated appropriately. * Also clears any idle instances associated with the username that was used * to create the PooledConnection. Connections associated with this user * are not affected and they will not be automatically closed on return to the pool. */
public void invalidate(PooledConnection pc) throws SQLException { PooledConnectionAndInfo info = (PooledConnectionAndInfo) pcMap.get(pc); if (info == null) { throw new IllegalStateException(NO_KEY_MESSAGE); } UserPassKey key = info.getUserPassKey(); try { _pool.invalidateObject(key, info); // Destroy and update pool counters _pool.clear(key); // Remove any idle instances with this key } catch (Exception ex) { throw (SQLException) new SQLException("Error invalidating connection").initCause(ex); } }
Does nothing. This factory does not cache user credentials.
/** * Does nothing. This factory does not cache user credentials. */
public void setPassword(String password) { }
This implementation does not fully close the KeyedObjectPool, as this would affect all users. Instead, it clears the pool associated with the given user. This method is not currently used.
/** * This implementation does not fully close the KeyedObjectPool, as * this would affect all users. Instead, it clears the pool associated * with the given user. This method is not currently used. */
public void closePool(String username) throws SQLException { try { _pool.clear(new UserPassKey(username, null)); } catch (Exception ex) { throw (SQLException) new SQLException("Error closing connection pool").initCause(ex); } } }