/* * Copyright (C) 2013, 2014 Brett Wooldridge * * Licensed 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.zaxxer.hikari.pool; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Savepoint; import java.sql.Statement; import java.sql.Wrapper; import java.util.HashSet; import java.util.Set; import java.util.concurrent.Executor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.zaxxer.hikari.util.ClockSource; import com.zaxxer.hikari.util.FastList; /** * This is the proxy class for java.sql.Connection. * * @author Brett Wooldridge */ public abstract class ProxyConnection implements Connection { static final int DIRTY_BIT_READONLY = 0b00001; static final int DIRTY_BIT_AUTOCOMMIT = 0b00010; static final int DIRTY_BIT_ISOLATION = 0b00100; static final int DIRTY_BIT_CATALOG = 0b01000; static final int DIRTY_BIT_NETTIMEOUT = 0b10000; private static final Logger LOGGER; private static final Set<String> SQL_ERRORS; private static final ClockSource clockSource; protected Connection delegate; private final PoolEntry poolEntry; private final ProxyLeakTask leakTask; private final FastList<Statement> openStatements; private int dirtyBits; private long lastAccess; private boolean isCommitStateDirty; private boolean isReadOnly; private boolean isAutoCommit; private int networkTimeout; private int transactionIsolation; private String dbcatalog; // static initializer static { LOGGER = LoggerFactory.getLogger(ProxyConnection.class); clockSource = ClockSource.INSTANCE; SQL_ERRORS = new HashSet<>(); SQL_ERRORS.add("57P01"); // ADMIN SHUTDOWN SQL_ERRORS.add("57P02"); // CRASH SHUTDOWN SQL_ERRORS.add("57P03"); // CANNOT CONNECT NOW SQL_ERRORS.add("01002"); // SQL92 disconnect error SQL_ERRORS.add("JZ0C0"); // Sybase disconnect error SQL_ERRORS.add("JZ0C1"); // Sybase disconnect error } protected ProxyConnection(final PoolEntry poolEntry, final Connection connection, final FastList<Statement> openStatements, final ProxyLeakTask leakTask, final long now, final boolean isReadOnly, final boolean isAutoCommit) { this.poolEntry = poolEntry; this.delegate = connection; this.openStatements = openStatements; this.leakTask = leakTask; this.lastAccess = now; this.isReadOnly = isReadOnly; this.isAutoCommit = isAutoCommit; } /** {@inheritDoc} */ @Override public final String toString() { return new StringBuilder(64) .append(this.getClass().getSimpleName()).append('@').append(System.identityHashCode(this)) .append(" wrapping ") .append(delegate).toString(); } // *********************************************************************** // Connection State Accessors // *********************************************************************** final boolean getAutoCommitState() { return isAutoCommit; } final String getCatalogState() { return dbcatalog; } final int getTransactionIsolationState() { return transactionIsolation; } final boolean getReadOnlyState() { return isReadOnly; } final int getNetworkTimeoutState() { return networkTimeout; } // *********************************************************************** // Internal methods // *********************************************************************** final PoolEntry getPoolEntry() { return poolEntry; } final SQLException checkException(final SQLException sqle) { final String sqlState = sqle.getSQLState(); if (sqlState != null && delegate != ClosedConnection.CLOSED_CONNECTION) { if (sqlState.startsWith("08") || SQL_ERRORS.contains(sqlState)) { // broken connection LOGGER.warn("{} - Connection {} marked as broken because of SQLSTATE({}), ErrorCode({})", poolEntry.getPoolName(), delegate, sqlState, sqle.getErrorCode(), sqle); leakTask.cancel(); poolEntry.evict("(connection is broken)"); delegate = ClosedConnection.CLOSED_CONNECTION; } else { final SQLException nse = sqle.getNextException(); if (nse != null && nse != sqle) { checkException(nse); } } } return sqle; } final void untrackStatement(final Statement statement) { openStatements.remove(statement); } final void markCommitStateDirty() { if (isAutoCommit) { lastAccess = clockSource.currentTime(); } else { isCommitStateDirty = true; } } void cancelLeakTask() { leakTask.cancel(); } private final <T extends Statement> T trackStatement(final T statement) { openStatements.add(statement); return statement; } private final void closeStatements() { final int size = openStatements.size(); if (size > 0) { for (int i = 0; i < size && delegate != ClosedConnection.CLOSED_CONNECTION; i++) { try { final Statement statement = openStatements.get(i); if (statement != null) { statement.close(); } } catch (SQLException e) { checkException(e); } } openStatements.clear(); } } // ********************************************************************** // "Overridden" java.sql.Connection Methods // ********************************************************************** /** {@inheritDoc} */ @Override public final void close() throws SQLException { // Closing statements can cause connection eviction, so this must run before the conditional below closeStatements(); if (delegate != ClosedConnection.CLOSED_CONNECTION) { leakTask.cancel(); try { if (isCommitStateDirty && !isAutoCommit && !isReadOnly) { delegate.rollback(); lastAccess = clockSource.currentTime(); LOGGER.debug("{} - Executed rollback on connection {} due to dirty commit state on close().", poolEntry.getPoolName(), delegate); } if (dirtyBits != 0) { poolEntry.resetConnectionState(this, dirtyBits); lastAccess = clockSource.currentTime(); } delegate.clearWarnings(); } catch (SQLException e) { // when connections are aborted, exceptions are often thrown that should not reach the application if (!poolEntry.isMarkedEvicted()) { throw checkException(e); } } finally { delegate = ClosedConnection.CLOSED_CONNECTION; poolEntry.recycle(lastAccess); } } } /** {@inheritDoc} */ @Override public boolean isClosed() throws SQLException { return (delegate == ClosedConnection.CLOSED_CONNECTION); } /** {@inheritDoc} */ @Override public Statement createStatement() throws SQLException { return ProxyFactory.getProxyStatement(this, trackStatement(delegate.createStatement())); } /** {@inheritDoc} */ @Override public Statement createStatement(int resultSetType, int concurrency) throws SQLException { return ProxyFactory.getProxyStatement(this, trackStatement(delegate.createStatement(resultSetType, concurrency))); } /** {@inheritDoc} */ @Override public Statement createStatement(int resultSetType, int concurrency, int holdability) throws SQLException { return ProxyFactory.getProxyStatement(this, trackStatement(delegate.createStatement(resultSetType, concurrency, holdability))); } /** {@inheritDoc} */ @Override public CallableStatement prepareCall(String sql) throws SQLException { return ProxyFactory.getProxyCallableStatement(this, trackStatement(delegate.prepareCall(sql))); } /** {@inheritDoc} */ @Override public CallableStatement prepareCall(String sql, int resultSetType, int concurrency) throws SQLException { return ProxyFactory.getProxyCallableStatement(this, trackStatement(delegate.prepareCall(sql, resultSetType, concurrency))); } /** {@inheritDoc} */ @Override public CallableStatement prepareCall(String sql, int resultSetType, int concurrency, int holdability) throws SQLException { return ProxyFactory.getProxyCallableStatement(this, trackStatement(delegate.prepareCall(sql, resultSetType, concurrency, holdability))); } /** {@inheritDoc} */ @Override public PreparedStatement prepareStatement(String sql) throws SQLException { return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql))); } /** {@inheritDoc} */ @Override public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, autoGeneratedKeys))); } /** {@inheritDoc} */ @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int concurrency) throws SQLException { return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, resultSetType, concurrency))); } /** {@inheritDoc} */ @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int concurrency, int holdability) throws SQLException { return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, resultSetType, concurrency, holdability))); } /** {@inheritDoc} */ @Override public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, columnIndexes))); } /** {@inheritDoc} */ @Override public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, columnNames))); } /** {@inheritDoc} */ @Override public void commit() throws SQLException { delegate.commit(); isCommitStateDirty = false; lastAccess = clockSource.currentTime(); } /** {@inheritDoc} */ @Override public void rollback() throws SQLException { delegate.rollback(); isCommitStateDirty = false; lastAccess = clockSource.currentTime(); } /** {@inheritDoc} */ @Override public void rollback(Savepoint savepoint) throws SQLException { delegate.rollback(savepoint); isCommitStateDirty = false; lastAccess = clockSource.currentTime(); } /** {@inheritDoc} */ @Override public void setAutoCommit(boolean autoCommit) throws SQLException { delegate.setAutoCommit(autoCommit); isAutoCommit = autoCommit; dirtyBits |= DIRTY_BIT_AUTOCOMMIT; } /** {@inheritDoc} */ @Override public void setReadOnly(boolean readOnly) throws SQLException { delegate.setReadOnly(readOnly); isReadOnly = readOnly; isCommitStateDirty = false; dirtyBits |= DIRTY_BIT_READONLY; } /** {@inheritDoc} */ @Override public void setTransactionIsolation(int level) throws SQLException { delegate.setTransactionIsolation(level); transactionIsolation = level; dirtyBits |= DIRTY_BIT_ISOLATION; } /** {@inheritDoc} */ @Override public void setCatalog(String catalog) throws SQLException { delegate.setCatalog(catalog); dbcatalog = catalog; dirtyBits |= DIRTY_BIT_CATALOG; } /** {@inheritDoc} */ @Override public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { delegate.setNetworkTimeout(executor, milliseconds); networkTimeout = milliseconds; dirtyBits |= DIRTY_BIT_NETTIMEOUT; } /** {@inheritDoc} */ @Override public final boolean isWrapperFor(Class<?> iface) throws SQLException { return iface.isInstance(delegate) || (delegate instanceof Wrapper && delegate.isWrapperFor(iface)); } /** {@inheritDoc} */ @Override @SuppressWarnings("unchecked") public final <T> T unwrap(Class<T> iface) throws SQLException { if (iface.isInstance(delegate)) { return (T) delegate; } else if (delegate instanceof Wrapper) { return delegate.unwrap(iface); } throw new SQLException("Wrapped connection is not an instance of " + iface); } // ********************************************************************** // Private classes // ********************************************************************** private static final class ClosedConnection { static final Connection CLOSED_CONNECTION = getClosedConnection(); private static Connection getClosedConnection() { InvocationHandler handler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { final String methodName = method.getName(); if ("abort".equals(methodName)) { return Void.TYPE; } else if ("isValid".equals(methodName)) { return Boolean.FALSE; } else if ("toString".equals(methodName)) { return ClosedConnection.class.getCanonicalName(); } throw new SQLException("Connection is closed"); } }; return (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), new Class[] { Connection.class }, handler); } } }