/*
 * 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.dbcp2;

import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.ClientInfoStatus;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;

A base delegating implementation of Connection.

All of the methods from the Connection interface simply check to see that the Connection is active, and call the corresponding method on the "delegate" provided in my constructor.

Extends AbandonedTrace to implement Connection tracking and logging of code which created the Connection. Tracking the Connection ensures that the AbandonedObjectPool can close this connection and recycle it if its pool of connections is nearing exhaustion and this connection's last usage is older than the removeAbandonedTimeout.

Type parameters:
  • <C> – the Connection type
Since:2.0
/** * A base delegating implementation of {@link Connection}. * <p> * All of the methods from the {@link Connection} interface simply check to see that the {@link Connection} is active, * and call the corresponding method on the "delegate" provided in my constructor. * </p> * <p> * Extends AbandonedTrace to implement Connection tracking and logging of code which created the Connection. Tracking * the Connection ensures that the AbandonedObjectPool can close this connection and recycle it if its pool of * connections is nearing exhaustion and this connection's last usage is older than the removeAbandonedTimeout. * </p> * * @param <C> * the Connection type * * @since 2.0 */
public class DelegatingConnection<C extends Connection> extends AbandonedTrace implements Connection { private static final Map<String, ClientInfoStatus> EMPTY_FAILED_PROPERTIES = Collections .<String, ClientInfoStatus>emptyMap();
My delegate Connection.
/** My delegate {@link Connection}. */
private volatile C connection; private volatile boolean closed; private boolean cacheState = true; private Boolean autoCommitCached; private Boolean readOnlyCached; private Integer defaultQueryTimeoutSeconds;
Creates a wrapper for the Connection which traces this Connection in the AbandonedObjectPool.
Params:
/** * Creates a wrapper for the Connection which traces this Connection in the AbandonedObjectPool. * * @param c * the {@link Connection} to delegate all calls to. */
public DelegatingConnection(final C c) { super(); connection = c; }
Returns a string representation of the metadata associated with the innermost delegate connection.
/** * Returns a string representation of the metadata associated with the innermost delegate connection. */
@SuppressWarnings("resource") @Override public synchronized String toString() { String str = null; final Connection conn = this.getInnermostDelegateInternal(); if (conn != null) { try { if (conn.isClosed()) { str = "connection is closed"; } else { final StringBuffer sb = new StringBuffer(); sb.append(hashCode()); final DatabaseMetaData meta = conn.getMetaData(); if (meta != null) { sb.append(", URL="); sb.append(meta.getURL()); sb.append(", UserName="); sb.append(meta.getUserName()); sb.append(", "); sb.append(meta.getDriverName()); str = sb.toString(); } } } catch (final SQLException ex) { // Ignore } } return str != null ? str : super.toString(); }
Returns my underlying Connection.
Returns:my underlying Connection.
/** * Returns my underlying {@link Connection}. * * @return my underlying {@link Connection}. */
public C getDelegate() { return getDelegateInternal(); } protected final C getDelegateInternal() { return connection; }
Compares innermost delegate to the given connection.
Params:
  • c – connection to compare innermost delegate with
Returns:true if innermost delegate equals c
/** * Compares innermost delegate to the given connection. * * @param c * connection to compare innermost delegate with * @return true if innermost delegate equals <code>c</code> */
@SuppressWarnings("resource") public boolean innermostDelegateEquals(final Connection c) { final Connection innerCon = getInnermostDelegateInternal(); if (innerCon == null) { return c == null; } return innerCon.equals(c); }
If my underlying Connection is not a DelegatingConnection, returns it, otherwise recursively invokes this method on my delegate.

Hence this method will return the first delegate that is not a DelegatingConnection, or null when no non-DelegatingConnection delegate can be found by traversing this chain.

This method is useful when you may have nested DelegatingConnections, and you want to make sure to obtain a "genuine" Connection.

Returns:innermost delegate.
/** * If my underlying {@link Connection} is not a {@code DelegatingConnection}, returns it, otherwise recursively * invokes this method on my delegate. * <p> * Hence this method will return the first delegate that is not a {@code DelegatingConnection}, or {@code null} when * no non-{@code DelegatingConnection} delegate can be found by traversing this chain. * </p> * <p> * This method is useful when you may have nested {@code DelegatingConnection}s, and you want to make sure to obtain * a "genuine" {@link Connection}. * </p> * * @return innermost delegate. */
public Connection getInnermostDelegate() { return getInnermostDelegateInternal(); }
Although this method is public, it is part of the internal API and should not be used by clients. The signature of this method may change at any time including in ways that break backwards compatibility.
Returns:innermost delegate.
/** * Although this method is public, it is part of the internal API and should not be used by clients. The signature * of this method may change at any time including in ways that break backwards compatibility. * * @return innermost delegate. */
@SuppressWarnings("resource") public final Connection getInnermostDelegateInternal() { Connection conn = connection; while (conn != null && conn instanceof DelegatingConnection) { conn = ((DelegatingConnection<?>) conn).getDelegateInternal(); if (this == conn) { return null; } } return conn; }
Sets my delegate.
Params:
  • connection – my delegate.
/** * Sets my delegate. * * @param connection * my delegate. */
public void setDelegate(final C connection) { this.connection = connection; }
Closes the underlying connection, and close any Statements that were not explicitly closed. Sub-classes that override this method must:
  1. Call passivate()
  2. Call close (or the equivalent appropriate action) on the wrapped connection
  3. Set _closed to false
/** * Closes the underlying connection, and close any Statements that were not explicitly closed. Sub-classes that * override this method must: * <ol> * <li>Call passivate()</li> * <li>Call close (or the equivalent appropriate action) on the wrapped connection</li> * <li>Set _closed to <code>false</code></li> * </ol> */
@Override public void close() throws SQLException { if (!closed) { closeInternal(); } } protected boolean isClosedInternal() { return closed; } protected void setClosedInternal(final boolean closed) { this.closed = closed; } protected final void closeInternal() throws SQLException { try { passivate(); } finally { if (connection != null) { boolean connectionIsClosed; try { connectionIsClosed = connection.isClosed(); } catch (final SQLException e) { // not sure what the state is, so assume the connection is open. connectionIsClosed = false; } try { // DBCP-512: Avoid exceptions when closing a connection in mutli-threaded use case. // Avoid closing again, which should be a no-op, but some drivers like H2 throw an exception when // closing from multiple threads. if (!connectionIsClosed) { connection.close(); } } finally { closed = true; } } else { closed = true; } } } protected void handleException(final SQLException e) throws SQLException { throw e; }
Handles the given SQLException.
Params:
  • e – The SQLException
Type parameters:
  • <T> – The throwable type.
Returns:the given SQLException
Since:2.7.0
/** * Handles the given {@code SQLException}. * * @param <T> The throwable type. * @param e The SQLException * @return the given {@code SQLException} * @since 2.7.0 */
protected <T extends Throwable> T handleExceptionNoThrow(final T e) { return e; } private void initializeStatement(final DelegatingStatement ds) throws SQLException { if (defaultQueryTimeoutSeconds != null && defaultQueryTimeoutSeconds.intValue() != ds.getQueryTimeout()) { ds.setQueryTimeout(defaultQueryTimeoutSeconds.intValue()); } } @Override public Statement createStatement() throws SQLException { checkOpen(); try { final DelegatingStatement ds = new DelegatingStatement(this, connection.createStatement()); initializeStatement(ds); return ds; } catch (final SQLException e) { handleException(e); return null; } } @Override public Statement createStatement(final int resultSetType, final int resultSetConcurrency) throws SQLException { checkOpen(); try { final DelegatingStatement ds = new DelegatingStatement(this, connection.createStatement(resultSetType, resultSetConcurrency)); initializeStatement(ds); return ds; } catch (final SQLException e) { handleException(e); return null; } } @Override public PreparedStatement prepareStatement(final String sql) throws SQLException { checkOpen(); try { final DelegatingPreparedStatement dps = new DelegatingPreparedStatement(this, connection.prepareStatement(sql)); initializeStatement(dps); return dps; } catch (final SQLException e) { handleException(e); return null; } } @Override public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency) throws SQLException { checkOpen(); try { final DelegatingPreparedStatement dps = new DelegatingPreparedStatement(this, connection.prepareStatement(sql, resultSetType, resultSetConcurrency)); initializeStatement(dps); return dps; } catch (final SQLException e) { handleException(e); return null; } } @Override public CallableStatement prepareCall(final String sql) throws SQLException { checkOpen(); try { final DelegatingCallableStatement dcs = new DelegatingCallableStatement(this, connection.prepareCall(sql)); initializeStatement(dcs); return dcs; } catch (final SQLException e) { handleException(e); return null; } } @Override public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency) throws SQLException { checkOpen(); try { final DelegatingCallableStatement dcs = new DelegatingCallableStatement(this, connection.prepareCall(sql, resultSetType, resultSetConcurrency)); initializeStatement(dcs); return dcs; } catch (final SQLException e) { handleException(e); return null; } } @Override public void clearWarnings() throws SQLException { checkOpen(); try { connection.clearWarnings(); } catch (final SQLException e) { handleException(e); } } @Override public void commit() throws SQLException { checkOpen(); try { connection.commit(); } catch (final SQLException e) { handleException(e); } }
Returns the state caching flag.
Returns:the state caching flag
/** * Returns the state caching flag. * * @return the state caching flag */
public boolean getCacheState() { return cacheState; } @Override public boolean getAutoCommit() throws SQLException { checkOpen(); if (cacheState && autoCommitCached != null) { return autoCommitCached.booleanValue(); } try { autoCommitCached = Boolean.valueOf(connection.getAutoCommit()); return autoCommitCached.booleanValue(); } catch (final SQLException e) { handleException(e); return false; } } @Override public String getCatalog() throws SQLException { checkOpen(); try { return connection.getCatalog(); } catch (final SQLException e) { handleException(e); return null; } } @Override public DatabaseMetaData getMetaData() throws SQLException { checkOpen(); try { return new DelegatingDatabaseMetaData(this, connection.getMetaData()); } catch (final SQLException e) { handleException(e); return null; } } @Override public int getTransactionIsolation() throws SQLException { checkOpen(); try { return connection.getTransactionIsolation(); } catch (final SQLException e) { handleException(e); return -1; } } @Override public Map<String, Class<?>> getTypeMap() throws SQLException { checkOpen(); try { return connection.getTypeMap(); } catch (final SQLException e) { handleException(e); return null; } } @Override public SQLWarning getWarnings() throws SQLException { checkOpen(); try { return connection.getWarnings(); } catch (final SQLException e) { handleException(e); return null; } } @Override public boolean isReadOnly() throws SQLException { checkOpen(); if (cacheState && readOnlyCached != null) { return readOnlyCached.booleanValue(); } try { readOnlyCached = Boolean.valueOf(connection.isReadOnly()); return readOnlyCached.booleanValue(); } catch (final SQLException e) { handleException(e); return false; } } @Override public String nativeSQL(final String sql) throws SQLException { checkOpen(); try { return connection.nativeSQL(sql); } catch (final SQLException e) { handleException(e); return null; } } @Override public void rollback() throws SQLException { checkOpen(); try { connection.rollback(); } catch (final SQLException e) { handleException(e); } }
Gets the default query timeout that will be used for Statements created from this connection. null means that the driver default will be used.
Returns:query timeout limit in seconds; zero means there is no limit.
/** * Gets the default query timeout that will be used for {@link Statement}s created from this connection. * <code>null</code> means that the driver default will be used. * * @return query timeout limit in seconds; zero means there is no limit. */
public Integer getDefaultQueryTimeout() { return defaultQueryTimeoutSeconds; }
Sets the default query timeout that will be used for Statements created from this connection. null means that the driver default will be used.
Params:
  • defaultQueryTimeoutSeconds – the new query timeout limit in seconds; zero means there is no limit
/** * Sets the default query timeout that will be used for {@link Statement}s created from this connection. * <code>null</code> means that the driver default will be used. * * @param defaultQueryTimeoutSeconds * the new query timeout limit in seconds; zero means there is no limit */
public void setDefaultQueryTimeout(final Integer defaultQueryTimeoutSeconds) { this.defaultQueryTimeoutSeconds = defaultQueryTimeoutSeconds; }
Sets the state caching flag.
Params:
  • cacheState – The new value for the state caching flag
/** * Sets the state caching flag. * * @param cacheState * The new value for the state caching flag */
public void setCacheState(final boolean cacheState) { this.cacheState = cacheState; }
Can be used to clear cached state when it is known that the underlying connection may have been accessed directly.
/** * Can be used to clear cached state when it is known that the underlying connection may have been accessed * directly. */
public void clearCachedState() { autoCommitCached = null; readOnlyCached = null; if (connection instanceof DelegatingConnection) { ((DelegatingConnection<?>) connection).clearCachedState(); } } @Override public void setAutoCommit(final boolean autoCommit) throws SQLException { checkOpen(); try { connection.setAutoCommit(autoCommit); if (cacheState) { autoCommitCached = Boolean.valueOf(autoCommit); } } catch (final SQLException e) { autoCommitCached = null; handleException(e); } } @Override public void setCatalog(final String catalog) throws SQLException { checkOpen(); try { connection.setCatalog(catalog); } catch (final SQLException e) { handleException(e); } } @Override public void setReadOnly(final boolean readOnly) throws SQLException { checkOpen(); try { connection.setReadOnly(readOnly); if (cacheState) { readOnlyCached = Boolean.valueOf(readOnly); } } catch (final SQLException e) { readOnlyCached = null; handleException(e); } } @Override public void setTransactionIsolation(final int level) throws SQLException { checkOpen(); try { connection.setTransactionIsolation(level); } catch (final SQLException e) { handleException(e); } } @Override public void setTypeMap(final Map<String, Class<?>> map) throws SQLException { checkOpen(); try { connection.setTypeMap(map); } catch (final SQLException e) { handleException(e); } } @Override public boolean isClosed() throws SQLException { return closed || connection == null || connection.isClosed(); } protected void checkOpen() throws SQLException { if (closed) { if (null != connection) { String label = ""; try { label = connection.toString(); } catch (final Exception ex) { // ignore, leave label empty } throw new SQLException("Connection " + label + " is closed."); } throw new SQLException("Connection is null."); } } protected void activate() { closed = false; setLastUsed(); if (connection instanceof DelegatingConnection) { ((DelegatingConnection<?>) connection).activate(); } } protected void passivate() throws SQLException { // The JDBC specification requires that a Connection close any open // Statement's when it is closed. // DBCP-288. Not all the traced objects will be statements final List<AbandonedTrace> traces = getTrace(); if (traces != null && !traces.isEmpty()) { final List<Exception> thrown = new ArrayList<>(); final Iterator<AbandonedTrace> traceIter = traces.iterator(); while (traceIter.hasNext()) { final Object trace = traceIter.next(); if (trace instanceof Statement) { try { ((Statement) trace).close(); } catch (Exception e) { thrown.add(e); } } else if (trace instanceof ResultSet) { // DBCP-265: Need to close the result sets that are // generated via DatabaseMetaData try { ((ResultSet) trace).close(); } catch (Exception e) { thrown.add(e); } } } clearTrace(); if (!thrown.isEmpty()) { throw new SQLExceptionList(thrown); } } setLastUsed(0); } @Override public int getHoldability() throws SQLException { checkOpen(); try { return connection.getHoldability(); } catch (final SQLException e) { handleException(e); return 0; } } @Override public void setHoldability(final int holdability) throws SQLException { checkOpen(); try { connection.setHoldability(holdability); } catch (final SQLException e) { handleException(e); } } @Override public Savepoint setSavepoint() throws SQLException { checkOpen(); try { return connection.setSavepoint(); } catch (final SQLException e) { handleException(e); return null; } } @Override public Savepoint setSavepoint(final String name) throws SQLException { checkOpen(); try { return connection.setSavepoint(name); } catch (final SQLException e) { handleException(e); return null; } } @Override public void rollback(final Savepoint savepoint) throws SQLException { checkOpen(); try { connection.rollback(savepoint); } catch (final SQLException e) { handleException(e); } } @Override public void releaseSavepoint(final Savepoint savepoint) throws SQLException { checkOpen(); try { connection.releaseSavepoint(savepoint); } catch (final SQLException e) { handleException(e); } } @Override public Statement createStatement(final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) throws SQLException { checkOpen(); try { final DelegatingStatement ds = new DelegatingStatement(this, connection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability)); initializeStatement(ds); return ds; } catch (final SQLException e) { handleException(e); return null; } } @Override public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) throws SQLException { checkOpen(); try { final DelegatingPreparedStatement dps = new DelegatingPreparedStatement(this, connection.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability)); initializeStatement(dps); return dps; } catch (final SQLException e) { handleException(e); return null; } } @Override public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) throws SQLException { checkOpen(); try { final DelegatingCallableStatement dcs = new DelegatingCallableStatement(this, connection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability)); initializeStatement(dcs); return dcs; } catch (final SQLException e) { handleException(e); return null; } } @Override public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException { checkOpen(); try { final DelegatingPreparedStatement dps = new DelegatingPreparedStatement(this, connection.prepareStatement(sql, autoGeneratedKeys)); initializeStatement(dps); return dps; } catch (final SQLException e) { handleException(e); return null; } } @Override public PreparedStatement prepareStatement(final String sql, final int columnIndexes[]) throws SQLException { checkOpen(); try { final DelegatingPreparedStatement dps = new DelegatingPreparedStatement(this, connection.prepareStatement(sql, columnIndexes)); initializeStatement(dps); return dps; } catch (final SQLException e) { handleException(e); return null; } } @Override public PreparedStatement prepareStatement(final String sql, final String columnNames[]) throws SQLException { checkOpen(); try { final DelegatingPreparedStatement dps = new DelegatingPreparedStatement(this, connection.prepareStatement(sql, columnNames)); initializeStatement(dps); return dps; } catch (final SQLException e) { handleException(e); return null; } } @Override public boolean isWrapperFor(final Class<?> iface) throws SQLException { if (iface.isAssignableFrom(getClass())) { return true; } else if (iface.isAssignableFrom(connection.getClass())) { return true; } else { return connection.isWrapperFor(iface); } } @Override public <T> T unwrap(final Class<T> iface) throws SQLException { if (iface.isAssignableFrom(getClass())) { return iface.cast(this); } else if (iface.isAssignableFrom(connection.getClass())) { return iface.cast(connection); } else { return connection.unwrap(iface); } } @Override public Array createArrayOf(final String typeName, final Object[] elements) throws SQLException { checkOpen(); try { return connection.createArrayOf(typeName, elements); } catch (final SQLException e) { handleException(e); return null; } } @Override public Blob createBlob() throws SQLException { checkOpen(); try { return connection.createBlob(); } catch (final SQLException e) { handleException(e); return null; } } @Override public Clob createClob() throws SQLException { checkOpen(); try { return connection.createClob(); } catch (final SQLException e) { handleException(e); return null; } } @Override public NClob createNClob() throws SQLException { checkOpen(); try { return connection.createNClob(); } catch (final SQLException e) { handleException(e); return null; } } @Override public SQLXML createSQLXML() throws SQLException { checkOpen(); try { return connection.createSQLXML(); } catch (final SQLException e) { handleException(e); return null; } } @Override public Struct createStruct(final String typeName, final Object[] attributes) throws SQLException { checkOpen(); try { return connection.createStruct(typeName, attributes); } catch (final SQLException e) { handleException(e); return null; } } @Override public boolean isValid(final int timeoutSeconds) throws SQLException { if (isClosed()) { return false; } try { return connection.isValid(timeoutSeconds); } catch (final SQLException e) { handleException(e); return false; } } @Override public void setClientInfo(final String name, final String value) throws SQLClientInfoException { try { checkOpen(); connection.setClientInfo(name, value); } catch (final SQLClientInfoException e) { throw e; } catch (final SQLException e) { throw new SQLClientInfoException("Connection is closed.", EMPTY_FAILED_PROPERTIES, e); } } @Override public void setClientInfo(final Properties properties) throws SQLClientInfoException { try { checkOpen(); connection.setClientInfo(properties); } catch (final SQLClientInfoException e) { throw e; } catch (final SQLException e) { throw new SQLClientInfoException("Connection is closed.", EMPTY_FAILED_PROPERTIES, e); } } @Override public Properties getClientInfo() throws SQLException { checkOpen(); try { return connection.getClientInfo(); } catch (final SQLException e) { handleException(e); return null; } } @Override public String getClientInfo(final String name) throws SQLException { checkOpen(); try { return connection.getClientInfo(name); } catch (final SQLException e) { handleException(e); return null; } } @Override public void setSchema(final String schema) throws SQLException { checkOpen(); try { Jdbc41Bridge.setSchema(connection, schema); } catch (final SQLException e) { handleException(e); } } @Override public String getSchema() throws SQLException { checkOpen(); try { return Jdbc41Bridge.getSchema(connection); } catch (final SQLException e) { handleException(e); return null; } } @Override public void abort(final Executor executor) throws SQLException { checkOpen(); try { Jdbc41Bridge.abort(connection, executor); } catch (final SQLException e) { handleException(e); } } @Override public void setNetworkTimeout(final Executor executor, final int milliseconds) throws SQLException { checkOpen(); try { Jdbc41Bridge.setNetworkTimeout(connection, executor, milliseconds); } catch (final SQLException e) { handleException(e); } } @Override public int getNetworkTimeout() throws SQLException { checkOpen(); try { return Jdbc41Bridge.getNetworkTimeout(connection); } catch (final SQLException e) { handleException(e); return 0; } } }