/* * 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 com.frameworkset.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 com.frameworkset.commons.dbcp.PoolableConnection; import com.frameworkset.commons.dbcp.SQLNestedException; import com.frameworkset.commons.pool.ObjectPool; import com.frameworkset.commons.pool.PoolableObjectFactory; /** * A {@link PoolableObjectFactory} that creates * {@link PoolableConnection}s. * * @author John D. McNally * @version $Revision: 479137 $ $Date: 2006-11-25 08:51:48 -0700 (Sat, 25 Nov 2006) $ */ class CPDSConnectionFactory implements PoolableObjectFactory, ConnectionEventListener { private static final String NO_KEY_MESSAGE = "close() was called on a Connection, but " + "I have no record of the underlying PooledConnection."; protected ConnectionPoolDataSource _cpds = null; protected String _validationQuery = null; protected boolean _rollbackAfterValidation = false; protected ObjectPool _pool = null; protected String _username = null; protected String _password = null; private Map validatingMap = new HashMap(); private WeakHashMap pcMap = new WeakHashMap(); /** * Create a new <tt>PoolableConnectionFactory</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> * @param username * @param password */ public CPDSConnectionFactory(ConnectionPoolDataSource cpds, ObjectPool pool, String validationQuery, String username, String password) { _cpds = cpds; _pool = pool; _pool.setFactory(this); _validationQuery = validationQuery; _username = username; _password = password; } /** * Create a new <tt>PoolableConnectionFactory</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> * @param rollbackAfterValidation whether a rollback should be issued * after {@link #validateObject validating} {@link Connection}s. * @param username * @param password */ public CPDSConnectionFactory(ConnectionPoolDataSource cpds, ObjectPool pool, String validationQuery, boolean rollbackAfterValidation, String username, String password) { this(cpds, pool, validationQuery, username, password); _rollbackAfterValidation = rollbackAfterValidation; } /** * Sets the {*link ConnectionFactory} from which to obtain base * {*link Connection}s. * @param connFactory the {*link ConnectionFactory} from which to obtain * base {*link Connection}s */ public synchronized void setCPDS(ConnectionPoolDataSource cpds) { _cpds = cpds; } /** * Sets the query I use to {*link #validateObject validate} * {*link Connection}s. * Should return at least one row. * May be <tt>null</tt> * @param validationQuery a query to use to {*link #validateObject validate} * {*link Connection}s. */ public synchronized void setValidationQuery(String validationQuery) { _validationQuery = validationQuery; } /** * Sets whether a rollback should be issued after * {*link #validateObject validating} * {*link Connection}s. * @param rollbackAfterValidation whether a rollback should be issued after * {*link #validateObject validating} * {*link Connection}s. */ public synchronized void setRollbackAfterValidation( boolean rollbackAfterValidation) { _rollbackAfterValidation = rollbackAfterValidation; } /** * Sets the {*link ObjectPool} in which to pool {*link Connection}s. * @param pool the {*link ObjectPool} in which to pool those * {*link Connection}s */ public synchronized void setPool(ObjectPool pool) throws SQLException { if (null != _pool && pool != _pool) { try { _pool.close(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new SQLNestedException("Cannot set the pool on this factory", e); } } _pool = pool; } public ObjectPool getPool() { return _pool; } public synchronized Object makeObject() { Object obj; try { PooledConnection pc = null; if (_username == null) { pc = _cpds.getPooledConnection(); } else { pc = _cpds.getPooledConnection(_username, _password); } // 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); } catch (SQLException e) { throw new RuntimeException(e.getMessage()); } return obj; } public void destroyObject(Object obj) throws Exception { if (obj instanceof PooledConnectionAndInfo) { ((PooledConnectionAndInfo) obj).getPooledConnection().close(); } } public boolean validateObject(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 obj) { } public void activateObject(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... */ public void connectionClosed(ConnectionEvent event) { PooledConnection pc = (PooledConnection) event.getSource(); // if this event occured becase we were validating, ignore it // otherwise return the connection to the pool. if (!validatingMap.containsKey(pc)) { Object info = pcMap.get(pc); if (info == null) { throw new IllegalStateException(NO_KEY_MESSAGE); } try { _pool.returnObject(info); } catch (Exception e) { System.err.println("CLOSING DOWN CONNECTION AS IT COULD " + "NOT BE RETURNED TO THE POOL"); try { destroyObject(info); } catch (Exception e2) { System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + info); e2.printStackTrace(); } } } } /** * 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(); try { if (null != event.getSQLException()) { System.err.println( "CLOSING DOWN CONNECTION DUE TO INTERNAL ERROR (" + event.getSQLException() + ")"); } //remove this from the listener list because we are no more //interested in errors since we are about to close this connection pc.removeConnectionEventListener(this); } catch (Exception ignore) { // ignore } Object info = pcMap.get(pc); if (info == null) { throw new IllegalStateException(NO_KEY_MESSAGE); } try { destroyObject(info); } catch (Exception e) { System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + info); e.printStackTrace(); } } }