/* * 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.cpdsadapter; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Iterator; import java.util.Vector; import javax.sql.ConnectionEvent; import javax.sql.ConnectionEventListener; import javax.sql.PooledConnection; import com.frameworkset.commons.dbcp.DelegatingConnection; import com.frameworkset.commons.dbcp.DelegatingPreparedStatement; import com.frameworkset.commons.dbcp.SQLNestedException; import com.frameworkset.commons.pool.KeyedObjectPool; import com.frameworkset.commons.pool.KeyedPoolableObjectFactory; /** * Implementation of PooledConnection that is returned by * PooledConnectionDataSource. * * @author John D. McNally * @version $Revision: 479137 $ $Date: 2006-11-25 08:51:48 -0700 (Sat, 25 Nov 2006) $ */ class PooledConnectionImpl implements PooledConnection, KeyedPoolableObjectFactory { private static final String CLOSED = "Attempted to use PooledConnection after closed() was called."; /** * The JDBC database connection that represents the physical db connection. */ private Connection connection = null; /** * A DelegatingConnection used to create a PoolablePreparedStatementStub */ private DelegatingConnection delegatingConnection = null; /** * The JDBC database logical connection. */ private Connection logicalConnection = null; /** * ConnectionEventListeners */ private Vector eventListeners; /** * flag set to true, once close() is called. */ boolean isClosed; /** My pool of {*link PreparedStatement}s. */ protected KeyedObjectPool pstmtPool = null; /** * Wrap the real connection. */ PooledConnectionImpl(Connection connection, KeyedObjectPool pool) { this.connection = connection; if (connection instanceof DelegatingConnection) { this.delegatingConnection = (DelegatingConnection) connection; } else { this.delegatingConnection = new DelegatingConnection(connection); } eventListeners = new Vector(); isClosed = false; if (pool != null) { pstmtPool = pool; pstmtPool.setFactory(this); } } /** * Add an event listener. */ public void addConnectionEventListener(ConnectionEventListener listener) { if (!eventListeners.contains(listener)) { eventListeners.add(listener); } } /** * Closes the physical connection and marks this * <code>PooledConnection</code> so that it may not be used * to generate any more logical <code>Connection</code>s. * * @exception SQLException if an error occurs */ public void close() throws SQLException { assertOpen(); isClosed = true; try { if (pstmtPool != null) { try { pstmtPool.close(); } finally { pstmtPool = null; } } } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new SQLNestedException("Cannot close connection (return to pool failed)", e); } finally { try { connection.close(); } finally { connection = null; } } } /** * Throws an SQLException, if isClosed() is true */ private void assertOpen() throws SQLException { if (isClosed) { throw new SQLException(CLOSED); } } /** * Returns a JDBC connection. * * @return The database connection. */ public Connection getConnection() throws SQLException { assertOpen(); // make sure the last connection is marked as closed if (logicalConnection != null && !logicalConnection.isClosed()) { // should notify pool of error so the pooled connection can // be removed !FIXME! throw new SQLException("PooledConnection was reused, without" + "its previous Connection being closed."); } // the spec requires that this return a new Connection instance. logicalConnection = new ConnectionImpl(this, connection); return logicalConnection; } /** * Remove an event listener. */ public void removeConnectionEventListener( ConnectionEventListener listener) { eventListeners.remove(listener); } /** * Closes the physical connection and checks that the logical connection * was closed as well. */ protected void finalize() throws Throwable { // Closing the Connection ensures that if anyone tries to use it, // an error will occur. try { connection.close(); } catch (Exception ignored) { } // make sure the last connection is marked as closed if (logicalConnection != null && !logicalConnection.isClosed()) { throw new SQLException("PooledConnection was gc'ed, without" + "its last Connection being closed."); } } /** * sends a connectionClosed event. */ void notifyListeners() { ConnectionEvent event = new ConnectionEvent(this); Iterator i = eventListeners.iterator(); while (i.hasNext()) { ((ConnectionEventListener) i.next()).connectionClosed(event); } } // ------------------------------------------------------------------- // The following code implements a PreparedStatement pool /** * Create or obtain a {*link PreparedStatement} from my pool. * @return a {*link PoolablePreparedStatement} */ PreparedStatement prepareStatement(String sql) throws SQLException { if (pstmtPool == null) { return connection.prepareStatement(sql); } else { try { return (PreparedStatement) pstmtPool.borrowObject(createKey(sql)); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new SQLNestedException("Borrow prepareStatement from pool failed", e); } } } /** * Create or obtain a {*link PreparedStatement} from my pool. * @return a {*link PoolablePreparedStatement} */ PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { if (pstmtPool == null) { return connection.prepareStatement(sql, resultSetType, resultSetConcurrency); } else { try { return (PreparedStatement) pstmtPool.borrowObject( createKey(sql,resultSetType,resultSetConcurrency)); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new SQLNestedException("Borrow prepareStatement from pool failed", e); } } } /** * Create a {*link PooledConnectionImpl.PStmtKey} for the given arguments. */ protected Object createKey(String sql, int resultSetType, int resultSetConcurrency) { return new PStmtKey(normalizeSQL(sql), resultSetType, resultSetConcurrency); } /** * Create a {*link PooledConnectionImpl.PStmtKey} for the given arguments. */ protected Object createKey(String sql) { return new PStmtKey(normalizeSQL(sql)); } /** * Normalize the given SQL statement, producing a * cannonical form that is semantically equivalent to the original. */ protected String normalizeSQL(String sql) { return sql.trim(); } /** * My {*link KeyedPoolableObjectFactory} method for creating * {*link PreparedStatement}s. * @param obj the key for the {*link PreparedStatement} to be created */ public Object makeObject(Object obj) throws Exception { if (null == obj || !(obj instanceof PStmtKey)) { throw new IllegalArgumentException(); } else { // _openPstmts++; PStmtKey key = (PStmtKey)obj; if (null == key._resultSetType && null == key._resultSetConcurrency) { return new PoolablePreparedStatementStub( connection.prepareStatement(key._sql), key, pstmtPool, delegatingConnection); } else { return new PoolablePreparedStatementStub( connection.prepareStatement(key._sql, key._resultSetType.intValue(), key._resultSetConcurrency.intValue()), key, pstmtPool, delegatingConnection); } } } /** * My {*link KeyedPoolableObjectFactory} method for destroying * {*link PreparedStatement}s. * @param key ignored * @param obj the {*link PreparedStatement} to be destroyed. */ public void destroyObject(Object key, Object obj) throws Exception { //_openPstmts--; if (obj instanceof DelegatingPreparedStatement) { ((DelegatingPreparedStatement) obj).getInnermostDelegate().close(); } else { ((PreparedStatement) obj).close(); } } /** * My {*link KeyedPoolableObjectFactory} method for validating * {*link PreparedStatement}s. * @param key ignored * @param obj ignored * @return <tt>true</tt> */ public boolean validateObject(Object key, Object obj) { return true; } /** * My {*link KeyedPoolableObjectFactory} method for activating * {*link PreparedStatement}s. * @param key ignored * @param obj ignored */ public void activateObject(Object key, Object obj) throws Exception { ((PoolablePreparedStatementStub) obj).activate(); } /** * My {*link KeyedPoolableObjectFactory} method for passivating * {*link PreparedStatement}s. Currently invokes {*link PreparedStatement#clearParameters}. * @param key ignored * @param obj a {*link PreparedStatement} */ public void passivateObject(Object key, Object obj) throws Exception { ((PreparedStatement) obj).clearParameters(); ((PoolablePreparedStatementStub) obj).passivate(); } /** * A key uniquely identifying {*link PreparedStatement}s. */ class PStmtKey { protected String _sql = null; protected Integer _resultSetType = null; protected Integer _resultSetConcurrency = null; PStmtKey(String sql) { _sql = sql; } PStmtKey(String sql, int resultSetType, int resultSetConcurrency) { _sql = sql; _resultSetType = new Integer(resultSetType); _resultSetConcurrency = new Integer(resultSetConcurrency); } public boolean equals(Object that) { try { PStmtKey key = (PStmtKey) that; return(((null == _sql && null == key._sql) || _sql.equals(key._sql)) && ((null == _resultSetType && null == key._resultSetType) || _resultSetType.equals(key._resultSetType)) && ((null == _resultSetConcurrency && null == key._resultSetConcurrency) || _resultSetConcurrency.equals(key._resultSetConcurrency)) ); } catch (ClassCastException e) { return false; } catch (NullPointerException e) { return false; } } public int hashCode() { return(null == _sql ? 0 : _sql.hashCode()); } public String toString() { StringBuffer buf = new StringBuffer(); buf.append("PStmtKey: sql="); buf.append(_sql); buf.append(", resultSetType="); buf.append(_resultSetType); buf.append(", resultSetConcurrency="); buf.append(_resultSetConcurrency); return buf.toString(); } } }