org.firebirdsql.pool.PooledConnectionHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jaybird Show documentation
Show all versions of jaybird Show documentation
JDBC Driver for the Firebird RDBMS
/*
* Firebird Open Source J2ee connector - jdbc driver
*
* Distributable under LGPL license.
* You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html
*
* This program 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
* LGPL License for more details.
*
* This file was created by members of the firebird development team.
* All individual contributions remain the Copyright (C) of those
* individuals. Contributors to this file are either listed here or
* can be obtained from a CVS history command.
*
* All rights reserved.
*/
package org.firebirdsql.pool;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashSet;
import java.util.Iterator;
import org.firebirdsql.jdbc.FBSQLException;
import org.firebirdsql.logging.Logger;
import org.firebirdsql.logging.LoggerFactory;
/**
* This is a wrapper for {@link Connection} instances that caches prepared
* statements.
*
* @author Roman Rokytskyy
*/
class PooledConnectionHandler implements InvocationHandler {
private static final boolean LOG_REENTRANT_ACCESS = PoolDebugConfiguration.DEBUG_REENTRANT;
private static Logger logChannel = LoggerFactory.getLogger(PooledConnectionHandler.class, false);
/**
* Helper function to find specified method in a specified class.
*
* @param clazz class in which we look for a specified method.
* @param name name of the method.
* @param args types of method params.
*
* @return instance of {@link Method} corresponding to specified name
* and param types.
*/
public static Method findMethod(Class clazz, String name, Class[] args) {
try {
return clazz.getMethod(name, args);
} catch (NoSuchMethodException nmex) {
throw new NullPointerException(
"No method for proxying found. Please check your classpath.");
}
}
/**
* Get all implemented interfaces by the class.
*
* @param clazz class to inspect.
*
* @return array of all implemented interfaces.
*/
public static Class[] getAllInterfaces(Class clazz) {
HashSet result = new HashSet();
do {
Class[] interfaces = clazz.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
result.add(interfaces[i]);
}
clazz = clazz.getSuperclass();
} while(clazz.getSuperclass() != null);
return (Class[])result.toArray(new Class[result.size()]);
}
private final static Method CONNECTION_PREPARE_STATEMENT = findMethod(
Connection.class, "prepareStatement", new Class[] {String.class});
private final static Method CONNECTION_PREPARE_STATEMENT2 = findMethod(
Connection.class, "prepareStatement", new Class[] {
String.class, Integer.TYPE, Integer.TYPE
});
private final static Method CONNECTION_CREATE_STATEMENT = findMethod(
Connection.class, "createStatement", new Class[0]);
private final static Method CONNECTION_CREATE_STATEMENT2 = findMethod(
Connection.class, "createStatement", new Class[] {
Integer.TYPE, Integer.TYPE
});
private final static Method CONNECTION_CLOSE = findMethod(
Connection.class, "close", new Class[0]);
private final static Method CONNECTION_COMMIT = findMethod(
Connection.class, "commit", new Class[0]);
private final static Method CONNECTION_ROLLBACK = findMethod(
Connection.class, "rollback", new Class[0]);
private Connection connection;
private XConnectionManager owner;
private Connection proxy;
private boolean closed;
private ObjectCloseTraceException closeStackTrace;
/**
* Construct instance of this class. This method constructs new proxy
* that implements {@link Connection} interface and uses newly constructed
* object as invocation handler.
*
* @param connection connection to wrap.
*
* @param owner instance of {@link XConnectionOwner} that owns this
* connection instance.
*
* @param pingable true
if created connection should be
* pingable.
*
* @param useProxy true
if this class should use dynamic
* proxies, otherwise {@link PingableConnectionWrapper} will be used.
*
* @throws SQLException if something went wrong during initialization.
*/
public
PooledConnectionHandler(Connection connection, XConnectionManager owner)
throws SQLException
{
this.connection = connection;
this.owner = owner;
Class[] implementedInterfaces =
getAllInterfaces(connection.getClass());
proxy = (Connection)Proxy.newProxyInstance(
PooledConnectionHandler.class.getClassLoader(),
implementedInterfaces,
this);
}
/**
* Get proxy implementing {@link Connection} interface and using this
* instance as invocation handler.
*
* @return instance of {@link Connection}.
*/
public Connection getProxy() {
return proxy;
}
/**
* Get manager of this connection wrapper.
*
* @return instance of {@link XConnectionManager}.
*/
public XConnectionManager getManager() {
return owner;
}
/**
* Deallocate current connection. This call is similar to the call
* {@link Connection#close()} when invoked on the proxy object. However,
* unlike that call no listener is notified that connection being closed;
*
* @throws SQLException if something goes wrong.
*/
public void deallocate() throws SQLException {
handleConnectionClose(false);
}
private boolean invokeEntered;
/**
* Invoke method on a specified proxy. Here we check if method
* is a method {@link Connection#prepareStatement(String)}. If yes, we check
* if there is already a prepared statement for the wrapped connection or
* wrap a newly created one.
*
* @param proxy proxy on which method is invoked.
* @param method instance of {@link Method} describing method being invoked.
* @param args array with arguments.
*
* @return result of method invokation.
*
* @throws Throwable if invoked method threw an exception.
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable
{
try {
if (LOG_REENTRANT_ACCESS && invokeEntered && logChannel != null)
logChannel.warn("Re-entrant access detected.", new Exception());
invokeEntered = true;
// if object is closed, throw an exception
if (closed) {
FBSQLException ex = new FBSQLException(
"Connection " + this + " was closed. " +
"See the attached exception to find the place " +
"where it was closed");
ex.setNextException(closeStackTrace);
throw ex;
}
if ((owner != null && !owner.isValid(this)))
throw new SQLException(
"This connection owner is not valid anymore.");
if (method.equals(CONNECTION_PREPARE_STATEMENT)){
String statement = (String)args[0];
return handlePrepareStatement(
statement,
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY);
} else
if (method.equals(CONNECTION_PREPARE_STATEMENT2)) {
String statement = (String)args[0];
Integer resultSetType = (Integer)args[1];
Integer resultSetConcurrency = (Integer)args[2];
return handlePrepareStatement(
statement,
resultSetType.intValue(),
resultSetConcurrency.intValue());
} else
if (method.equals(CONNECTION_CREATE_STATEMENT)){
return handleCreateStatement(
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY);
} else
if (method.equals(CONNECTION_CREATE_STATEMENT2)) {
Integer resultSetType = (Integer)args[0];
Integer resultSetConcurrency = (Integer)args[1];
return handleCreateStatement(
resultSetType.intValue(),
resultSetConcurrency.intValue());
} else
if (method.equals(CONNECTION_COMMIT)) {
handleConnectionCommit();
return Void.TYPE;
} else
if (method.equals(CONNECTION_ROLLBACK)) {
handleConnectionRollback();
return Void.TYPE;
} else
if (method.equals(CONNECTION_CLOSE)) {
handleConnectionClose();
return Void.TYPE;
} else
return method.invoke(connection, args);
} catch(InvocationTargetException ex) {
if (ex.getTargetException() instanceof SQLException && owner != null)
owner.connectionErrorOccured(this, (SQLException)ex.getTargetException());
throw ex.getTargetException();
} catch(SQLException ex) {
if (owner != null)
owner.connectionErrorOccured(this, ex);
throw ex;
} finally {
invokeEntered = false;
}
}
/**
* Handle {@link Connection#prepareStatement(String)} method call. This
* method check internal cache first and returns prepared statement if found.
* Otherwise, it prepares statement and caches it.
*
* @param statement statement to prepare.
*
* @return instance of {@link PreparedStatement} corresponding to the
* statement
.
*
* @throws SQLException if there was problem preparing statement.
*/
synchronized PreparedStatement handlePrepareStatement(String statement,
int resultSetType, int resultSetConcurrency) throws SQLException
{
return getManager().getPreparedStatement(
statement, resultSetType, resultSetConcurrency);
}
private HashSet openStatements = new HashSet();
/**
* Handle {@link Connection#createStatement(int, int)} method call.
* @param resultSetType
* @param resultSetConcurrency
* @return
* @throws SQLException
*/
synchronized Statement handleCreateStatement(int resultSetType,
int resultSetConcurrency) throws SQLException
{
Statement result =
connection.createStatement(resultSetType, resultSetConcurrency);
StatementHandler handler = new StatementHandler(this, result);
openStatements.add(handler);
return handler.getProxy();
}
/**
* Forget about a statement. This method removes a statement from the
* internal collection containing open statements. This method should be
* called only from a dynamic proxy intercepting {@link Statement#close()}
* method.
*
* @param handler instance of {@link StatementHandler} wrapping a statement
* to forget.
*/
public synchronized void forgetStatement(StatementHandler handler) {
openStatements.remove(handler);
}
/**
* Close all open statements that were not correctly closed by the
* application.
*
* @throws SQLException if some error happened during close.
*/
synchronized void closeOpenStatements() throws SQLException {
SQLException error = null;
for (Iterator iter = openStatements.iterator(); iter.hasNext();) {
StatementHandler handler = (StatementHandler) iter.next();
try {
handler.getWrappedObject().close();
} catch(SQLException ex) {
if (error == null)
error = ex;
else
error.setNextException(ex);
}
}
if (error != null)
throw error;
}
/**
* Handle {@link Connection#close()} method. This implementation closes the
* connection, cleans the cache and notifies the owner.
*
* @throws SQLException if underlying connection threw this exception.
*/
synchronized void handleConnectionClose() throws SQLException {
handleConnectionClose(true);
}
/**
* Handle {@link Connection#close()} method. This implementation closes the
* connection and cleans the cache.
*
* @param notifyOwner true
when connection owner should be
* notified.
*
* @throws SQLException if underlying connection threw this exception.
*/
synchronized void handleConnectionClose(boolean notifyOwner) throws SQLException {
try {
closeOpenStatements();
} finally {
if (owner != null && notifyOwner) {
owner.connectionClosed(this);
}
closed = true;
closeStackTrace = new ObjectCloseTraceException();
}
}
/**
* Handle {@link Connection#commit()} method. This implementation commits the
* connection and cleans the cache.
*
* @throws SQLException if underlying connection threw this exception.
*/
synchronized void handleConnectionCommit() throws SQLException {
connection.commit();
getManager().connectionCommitted(this);
}
/**
* Handle {@link Connection#rollback()} method. This implementation rolls the
* connection back and cleans the cache.
*
* @throws SQLException if underlying connection threw this exception.
*/
synchronized void handleConnectionRollback() throws SQLException {
connection.rollback();
getManager().connectionRolledBack(this);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy