| /* |
| * 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.Connection; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.sql.SQLWarning; |
| import java.sql.Statement; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * A base delegating implementation of {@link Statement}. |
| * <p> |
| * All of the methods from the {@link Statement} interface simply check to see that the {@link Statement} is active, and |
| * call the corresponding method on the "delegate" provided in my constructor. |
| * <p> |
| * Extends AbandonedTrace to implement Statement tracking and logging of code which created the Statement. Tracking the |
| * Statement ensures that the Connection which created it can close any open Statement's on Connection close. |
| * |
| * @since 2.0 |
| */ |
| public class DelegatingStatement extends AbandonedTrace implements Statement { |
| |
| /** My delegate. */ |
| private Statement statement; |
| |
| /** The connection that created me. **/ |
| private DelegatingConnection<?> connection; |
| |
| private boolean closed = false; |
| |
| /** |
| * Create a wrapper for the Statement which traces this Statement to the Connection which created it and the code |
| * which created it. |
| * |
| * @param statement |
| * the {@link Statement} to delegate all calls to. |
| * @param connection |
| * the {@link DelegatingConnection} that created this statement. |
| */ |
| public DelegatingStatement(final DelegatingConnection<?> connection, final Statement statement) { |
| super(connection); |
| this.statement = statement; |
| this.connection = connection; |
| } |
| |
| /** |
| * |
| * @throws SQLException |
| * thrown by the delegating statement. |
| * @since 2.4.0 made public, was protected in 2.3.0. |
| */ |
| public void activate() throws SQLException { |
| if (statement instanceof DelegatingStatement) { |
| ((DelegatingStatement) statement).activate(); |
| } |
| } |
| |
| @Override |
| public void addBatch(final String sql) throws SQLException { |
| checkOpen(); |
| try { |
| statement.addBatch(sql); |
| } catch (final SQLException e) { |
| handleException(e); |
| } |
| } |
| |
| @Override |
| public void cancel() throws SQLException { |
| checkOpen(); |
| try { |
| statement.cancel(); |
| } catch (final SQLException e) { |
| handleException(e); |
| } |
| } |
| |
| protected void checkOpen() throws SQLException { |
| if (isClosed()) { |
| throw new SQLException(this.getClass().getName() + " with address: \"" + this.toString() + "\" is closed."); |
| } |
| } |
| |
| @Override |
| public void clearBatch() throws SQLException { |
| checkOpen(); |
| try { |
| statement.clearBatch(); |
| } catch (final SQLException e) { |
| handleException(e); |
| } |
| } |
| |
| @Override |
| public void clearWarnings() throws SQLException { |
| checkOpen(); |
| try { |
| statement.clearWarnings(); |
| } catch (final SQLException e) { |
| handleException(e); |
| } |
| } |
| |
| /** |
| * Close this DelegatingStatement, and close any ResultSets that were not explicitly closed. |
| */ |
| @Override |
| public void close() throws SQLException { |
| if (isClosed()) { |
| return; |
| } |
| final List<Exception> thrown = new ArrayList<>(); |
| try { |
| if (connection != null) { |
| connection.removeTrace(this); |
| connection = null; |
| } |
| |
| // The JDBC spec requires that a statement close any open |
| // ResultSet's when it is closed. |
| // FIXME The PreparedStatement we're wrapping should handle this for us. |
| // See bug 17301 for what could happen when ResultSets are closed twice. |
| final List<AbandonedTrace> resultSetList = getTrace(); |
| if (resultSetList != null) { |
| final int size = resultSetList.size(); |
| final ResultSet[] resultSets = resultSetList.toArray(new ResultSet[size]); |
| for (final ResultSet resultSet : resultSets) { |
| if (resultSet != null) { |
| try { |
| resultSet.close(); |
| } catch (Exception e) { |
| if (connection != null) { |
| // Does not rethrow e. |
| connection.handleExceptionNoThrow(e); |
| } |
| thrown.add(e); |
| } |
| } |
| } |
| clearTrace(); |
| } |
| if (statement != null) { |
| try { |
| statement.close(); |
| } catch (Exception e) { |
| if (connection != null) { |
| // Does not rethrow e. |
| connection.handleExceptionNoThrow(e); |
| } |
| thrown.add(e); |
| } |
| } |
| } finally { |
| closed = true; |
| statement = null; |
| if (!thrown.isEmpty()) { |
| throw new SQLExceptionList(thrown); |
| } |
| } |
| } |
| |
| @Override |
| public void closeOnCompletion() throws SQLException { |
| checkOpen(); |
| try { |
| Jdbc41Bridge.closeOnCompletion(statement); |
| } catch (final SQLException e) { |
| handleException(e); |
| } |
| } |
| |
| @Override |
| public boolean execute(final String sql) throws SQLException { |
| checkOpen(); |
| setLastUsedInParent(); |
| try { |
| return statement.execute(sql); |
| } catch (final SQLException e) { |
| handleException(e); |
| return false; |
| } |
| } |
| |
| @Override |
| public boolean execute(final String sql, final int autoGeneratedKeys) throws SQLException { |
| checkOpen(); |
| setLastUsedInParent(); |
| try { |
| return statement.execute(sql, autoGeneratedKeys); |
| } catch (final SQLException e) { |
| handleException(e); |
| return false; |
| } |
| } |
| |
| @Override |
| public boolean execute(final String sql, final int columnIndexes[]) throws SQLException { |
| checkOpen(); |
| setLastUsedInParent(); |
| try { |
| return statement.execute(sql, columnIndexes); |
| } catch (final SQLException e) { |
| handleException(e); |
| return false; |
| } |
| } |
| |
| @Override |
| public boolean execute(final String sql, final String columnNames[]) throws SQLException { |
| checkOpen(); |
| setLastUsedInParent(); |
| try { |
| return statement.execute(sql, columnNames); |
| } catch (final SQLException e) { |
| handleException(e); |
| return false; |
| } |
| } |
| |
| @Override |
| public int[] executeBatch() throws SQLException { |
| checkOpen(); |
| setLastUsedInParent(); |
| try { |
| return statement.executeBatch(); |
| } catch (final SQLException e) { |
| handleException(e); |
| throw new AssertionError(); |
| } |
| } |
| |
| /** |
| * @since 2.5.0 |
| */ |
| @Override |
| public long[] executeLargeBatch() throws SQLException { |
| checkOpen(); |
| setLastUsedInParent(); |
| try { |
| return statement.executeLargeBatch(); |
| } catch (final SQLException e) { |
| handleException(e); |
| return null; |
| } |
| } |
| |
| /** |
| * @since 2.5.0 |
| */ |
| @Override |
| public long executeLargeUpdate(final String sql) throws SQLException { |
| checkOpen(); |
| setLastUsedInParent(); |
| try { |
| return statement.executeLargeUpdate(sql); |
| } catch (final SQLException e) { |
| handleException(e); |
| return 0; |
| } |
| } |
| |
| /** |
| * @since 2.5.0 |
| */ |
| @Override |
| public long executeLargeUpdate(final String sql, final int autoGeneratedKeys) throws SQLException { |
| checkOpen(); |
| setLastUsedInParent(); |
| try { |
| return statement.executeLargeUpdate(sql, autoGeneratedKeys); |
| } catch (final SQLException e) { |
| handleException(e); |
| return 0; |
| } |
| } |
| |
| /** |
| * @since 2.5.0 |
| */ |
| @Override |
| public long executeLargeUpdate(final String sql, final int[] columnIndexes) throws SQLException { |
| checkOpen(); |
| setLastUsedInParent(); |
| try { |
| return statement.executeLargeUpdate(sql, columnIndexes); |
| } catch (final SQLException e) { |
| handleException(e); |
| return 0; |
| } |
| } |
| |
| /** |
| * @since 2.5.0 |
| */ |
| @Override |
| public long executeLargeUpdate(final String sql, final String[] columnNames) throws SQLException { |
| checkOpen(); |
| setLastUsedInParent(); |
| try { |
| return statement.executeLargeUpdate(sql, columnNames); |
| } catch (final SQLException e) { |
| handleException(e); |
| return 0; |
| } |
| } |
| |
| @Override |
| public ResultSet executeQuery(final String sql) throws SQLException { |
| checkOpen(); |
| setLastUsedInParent(); |
| try { |
| return DelegatingResultSet.wrapResultSet(this, statement.executeQuery(sql)); |
| } catch (final SQLException e) { |
| handleException(e); |
| throw new AssertionError(); |
| } |
| } |
| |
| @Override |
| public int executeUpdate(final String sql) throws SQLException { |
| checkOpen(); |
| setLastUsedInParent(); |
| try { |
| return statement.executeUpdate(sql); |
| } catch (final SQLException e) { |
| handleException(e); |
| return 0; |
| } |
| } |
| |
| @Override |
| public int executeUpdate(final String sql, final int autoGeneratedKeys) throws SQLException { |
| checkOpen(); |
| setLastUsedInParent(); |
| try { |
| return statement.executeUpdate(sql, autoGeneratedKeys); |
| } catch (final SQLException e) { |
| handleException(e); |
| return 0; |
| } |
| } |
| |
| @Override |
| public int executeUpdate(final String sql, final int columnIndexes[]) throws SQLException { |
| checkOpen(); |
| setLastUsedInParent(); |
| try { |
| return statement.executeUpdate(sql, columnIndexes); |
| } catch (final SQLException e) { |
| handleException(e); |
| return 0; |
| } |
| } |
| |
| @Override |
| public int executeUpdate(final String sql, final String columnNames[]) throws SQLException { |
| checkOpen(); |
| setLastUsedInParent(); |
| try { |
| return statement.executeUpdate(sql, columnNames); |
| } catch (final SQLException e) { |
| handleException(e); |
| return 0; |
| } |
| } |
| |
| @Override |
| protected void finalize() throws Throwable { |
| // This is required because of statement pooling. The poolable |
| // statements will always be strongly held by the statement pool. If the |
| // delegating statements that wrap the poolable statement are not |
| // strongly held they will be garbage collected but at that point the |
| // poolable statements need to be returned to the pool else there will |
| // be a leak of statements from the pool. Closing this statement will |
| // close all the wrapped statements and return any poolable statements |
| // to the pool. |
| close(); |
| super.finalize(); |
| } |
| |
| @Override |
| public Connection getConnection() throws SQLException { |
| checkOpen(); |
| return getConnectionInternal(); // return the delegating connection that created this |
| } |
| |
| protected DelegatingConnection<?> getConnectionInternal() { |
| return connection; |
| } |
| |
| /** |
| * Returns my underlying {@link Statement}. |
| * |
| * @return my underlying {@link Statement}. |
| * @see #getInnermostDelegate |
| */ |
| public Statement getDelegate() { |
| return statement; |
| } |
| |
| @Override |
| public int getFetchDirection() throws SQLException { |
| checkOpen(); |
| try { |
| return statement.getFetchDirection(); |
| } catch (final SQLException e) { |
| handleException(e); |
| return 0; |
| } |
| } |
| |
| @Override |
| public int getFetchSize() throws SQLException { |
| checkOpen(); |
| try { |
| return statement.getFetchSize(); |
| } catch (final SQLException e) { |
| handleException(e); |
| return 0; |
| } |
| } |
| |
| @Override |
| public ResultSet getGeneratedKeys() throws SQLException { |
| checkOpen(); |
| try { |
| return DelegatingResultSet.wrapResultSet(this, statement.getGeneratedKeys()); |
| } catch (final SQLException e) { |
| handleException(e); |
| throw new AssertionError(); |
| } |
| } |
| |
| /** |
| * If my underlying {@link Statement} is not a {@code DelegatingStatement}, returns it, otherwise recursively |
| * invokes this method on my delegate. |
| * <p> |
| * Hence this method will return the first delegate that is not a {@code DelegatingStatement} or {@code null} when |
| * no non-{@code DelegatingStatement} delegate can be found by traversing this chain. |
| * </p> |
| * <p> |
| * This method is useful when you may have nested {@code DelegatingStatement}s, and you want to make sure to obtain |
| * a "genuine" {@link Statement}. |
| * </p> |
| * |
| * @return The innermost delegate. |
| * |
| * @see #getDelegate |
| */ |
| @SuppressWarnings("resource") |
| public Statement getInnermostDelegate() { |
| Statement s = statement; |
| while (s != null && s instanceof DelegatingStatement) { |
| s = ((DelegatingStatement) s).getDelegate(); |
| if (this == s) { |
| return null; |
| } |
| } |
| return s; |
| } |
| |
| /** |
| * @since 2.5.0 |
| */ |
| @Override |
| public long getLargeMaxRows() throws SQLException { |
| checkOpen(); |
| try { |
| return statement.getLargeMaxRows(); |
| } catch (final SQLException e) { |
| handleException(e); |
| return 0; |
| } |
| } |
| |
| /** |
| * @since 2.5.0 |
| */ |
| @Override |
| public long getLargeUpdateCount() throws SQLException { |
| checkOpen(); |
| try { |
| return statement.getLargeUpdateCount(); |
| } catch (final SQLException e) { |
| handleException(e); |
| return 0; |
| } |
| } |
| |
| @Override |
| public int getMaxFieldSize() throws SQLException { |
| checkOpen(); |
| try { |
| return statement.getMaxFieldSize(); |
| } catch (final SQLException e) { |
| handleException(e); |
| return 0; |
| } |
| } |
| |
| @Override |
| public int getMaxRows() throws SQLException { |
| checkOpen(); |
| try { |
| return statement.getMaxRows(); |
| } catch (final SQLException e) { |
| handleException(e); |
| return 0; |
| } |
| } |
| |
| @Override |
| public boolean getMoreResults() throws SQLException { |
| checkOpen(); |
| try { |
| return statement.getMoreResults(); |
| } catch (final SQLException e) { |
| handleException(e); |
| return false; |
| } |
| } |
| |
| @Override |
| public boolean getMoreResults(final int current) throws SQLException { |
| checkOpen(); |
| try { |
| return statement.getMoreResults(current); |
| } catch (final SQLException e) { |
| handleException(e); |
| return false; |
| } |
| } |
| |
| @Override |
| public int getQueryTimeout() throws SQLException { |
| checkOpen(); |
| try { |
| return statement.getQueryTimeout(); |
| } catch (final SQLException e) { |
| handleException(e); |
| return 0; |
| } |
| } |
| |
| @Override |
| public ResultSet getResultSet() throws SQLException { |
| checkOpen(); |
| try { |
| return DelegatingResultSet.wrapResultSet(this, statement.getResultSet()); |
| } catch (final SQLException e) { |
| handleException(e); |
| throw new AssertionError(); |
| } |
| } |
| |
| @Override |
| public int getResultSetConcurrency() throws SQLException { |
| checkOpen(); |
| try { |
| return statement.getResultSetConcurrency(); |
| } catch (final SQLException e) { |
| handleException(e); |
| return 0; |
| } |
| } |
| |
| @Override |
| public int getResultSetHoldability() throws SQLException { |
| checkOpen(); |
| try { |
| return statement.getResultSetHoldability(); |
| } catch (final SQLException e) { |
| handleException(e); |
| return 0; |
| } |
| } |
| |
| @Override |
| public int getResultSetType() throws SQLException { |
| checkOpen(); |
| try { |
| return statement.getResultSetType(); |
| } catch (final SQLException e) { |
| handleException(e); |
| return 0; |
| } |
| } |
| |
| @Override |
| public int getUpdateCount() throws SQLException { |
| checkOpen(); |
| try { |
| return statement.getUpdateCount(); |
| } catch (final SQLException e) { |
| handleException(e); |
| return 0; |
| } |
| } |
| |
| @Override |
| public SQLWarning getWarnings() throws SQLException { |
| checkOpen(); |
| try { |
| return statement.getWarnings(); |
| } catch (final SQLException e) { |
| handleException(e); |
| throw new AssertionError(); |
| } |
| } |
| |
| protected void handleException(final SQLException e) throws SQLException { |
| if (connection != null) { |
| connection.handleException(e); |
| } else { |
| throw e; |
| } |
| } |
| |
| /* |
| * Note: This method was protected prior to JDBC 4. |
| */ |
| @Override |
| public boolean isClosed() throws SQLException { |
| return closed; |
| } |
| |
| protected boolean isClosedInternal() { |
| return closed; |
| } |
| |
| @Override |
| public boolean isCloseOnCompletion() throws SQLException { |
| checkOpen(); |
| try { |
| return Jdbc41Bridge.isCloseOnCompletion(statement); |
| } catch (final SQLException e) { |
| handleException(e); |
| return false; |
| } |
| } |
| |
| @Override |
| public boolean isPoolable() throws SQLException { |
| checkOpen(); |
| try { |
| return statement.isPoolable(); |
| } catch (final SQLException e) { |
| handleException(e); |
| return false; |
| } |
| } |
| |
| @Override |
| public boolean isWrapperFor(final Class<?> iface) throws SQLException { |
| if (iface.isAssignableFrom(getClass())) { |
| return true; |
| } else if (iface.isAssignableFrom(statement.getClass())) { |
| return true; |
| } else { |
| return statement.isWrapperFor(iface); |
| } |
| } |
| |
| /** |
| * |
| * @throws SQLException |
| * thrown by the delegating statement. |
| * @since 2.4.0 made public, was protected in 2.3.0. |
| */ |
| public void passivate() throws SQLException { |
| if (statement instanceof DelegatingStatement) { |
| ((DelegatingStatement) statement).passivate(); |
| } |
| } |
| |
| protected void setClosedInternal(final boolean closed) { |
| this.closed = closed; |
| } |
| |
| @Override |
| public void setCursorName(final String name) throws SQLException { |
| checkOpen(); |
| try { |
| statement.setCursorName(name); |
| } catch (final SQLException e) { |
| handleException(e); |
| } |
| } |
| |
| /** |
| * Sets my delegate. |
| * |
| * @param statement |
| * my delegate. |
| */ |
| public void setDelegate(final Statement statement) { |
| this.statement = statement; |
| } |
| |
| @Override |
| public void setEscapeProcessing(final boolean enable) throws SQLException { |
| checkOpen(); |
| try { |
| statement.setEscapeProcessing(enable); |
| } catch (final SQLException e) { |
| handleException(e); |
| } |
| } |
| |
| @Override |
| public void setFetchDirection(final int direction) throws SQLException { |
| checkOpen(); |
| try { |
| statement.setFetchDirection(direction); |
| } catch (final SQLException e) { |
| handleException(e); |
| } |
| } |
| |
| @Override |
| public void setFetchSize(final int rows) throws SQLException { |
| checkOpen(); |
| try { |
| statement.setFetchSize(rows); |
| } catch (final SQLException e) { |
| handleException(e); |
| } |
| } |
| |
| /** |
| * @since 2.5.0 |
| */ |
| @Override |
| public void setLargeMaxRows(final long max) throws SQLException { |
| checkOpen(); |
| try { |
| statement.setLargeMaxRows(max); |
| } catch (final SQLException e) { |
| handleException(e); |
| } |
| } |
| |
| private void setLastUsedInParent() { |
| if (connection != null) { |
| connection.setLastUsed(); |
| } |
| } |
| |
| @Override |
| public void setMaxFieldSize(final int max) throws SQLException { |
| checkOpen(); |
| try { |
| statement.setMaxFieldSize(max); |
| } catch (final SQLException e) { |
| handleException(e); |
| } |
| } |
| |
| @Override |
| public void setMaxRows(final int max) throws SQLException { |
| checkOpen(); |
| try { |
| statement.setMaxRows(max); |
| } catch (final SQLException e) { |
| handleException(e); |
| } |
| } |
| |
| @Override |
| public void setPoolable(final boolean poolable) throws SQLException { |
| checkOpen(); |
| try { |
| statement.setPoolable(poolable); |
| } catch (final SQLException e) { |
| handleException(e); |
| } |
| } |
| |
| @Override |
| public void setQueryTimeout(final int seconds) throws SQLException { |
| checkOpen(); |
| try { |
| statement.setQueryTimeout(seconds); |
| } catch (final SQLException e) { |
| handleException(e); |
| } |
| } |
| |
| /** |
| * Returns a String representation of this object. |
| * |
| * @return String |
| */ |
| @Override |
| public synchronized String toString() { |
| return statement == null ? "NULL" : statement.toString(); |
| } |
| |
| @Override |
| public <T> T unwrap(final Class<T> iface) throws SQLException { |
| if (iface.isAssignableFrom(getClass())) { |
| return iface.cast(this); |
| } else if (iface.isAssignableFrom(statement.getClass())) { |
| return iface.cast(statement); |
| } else { |
| return statement.unwrap(iface); |
| } |
| } |
| } |