001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.dbcp2;
018
019import java.sql.Connection;
020import java.sql.ResultSet;
021import java.sql.SQLException;
022import java.sql.SQLWarning;
023import java.sql.Statement;
024import java.util.List;
025
026/**
027 * A base delegating implementation of {@link Statement}.
028 * <p>
029 * All of the methods from the {@link Statement} interface simply check to see that the {@link Statement} is active, and
030 * call the corresponding method on the "delegate" provided in my constructor.
031 * <p>
032 * Extends AbandonedTrace to implement Statement tracking and logging of code which created the Statement. Tracking the
033 * Statement ensures that the Connection which created it can close any open Statement's on Connection close.
034 *
035 * @since 2.0
036 */
037public class DelegatingStatement extends AbandonedTrace implements Statement {
038
039    /** My delegate. */
040    private Statement statement;
041
042    /** The connection that created me. **/
043    private DelegatingConnection<?> connection;
044
045    /**
046     * Create a wrapper for the Statement which traces this Statement to the Connection which created it and the code
047     * which created it.
048     *
049     * @param statement
050     *            the {@link Statement} to delegate all calls to.
051     * @param connection
052     *            the {@link DelegatingConnection} that created this statement.
053     */
054    public DelegatingStatement(final DelegatingConnection<?> connection, final Statement statement) {
055        super(connection);
056        this.statement = statement;
057        this.connection = connection;
058    }
059
060    /**
061     * Returns my underlying {@link Statement}.
062     *
063     * @return my underlying {@link Statement}.
064     * @see #getInnermostDelegate
065     */
066    public Statement getDelegate() {
067        return statement;
068    }
069
070    /**
071     * If my underlying {@link Statement} is not a {@code DelegatingStatement}, returns it, otherwise recursively
072     * invokes this method on my delegate.
073     * <p>
074     * Hence this method will return the first delegate that is not a {@code DelegatingStatement} or {@code null} when
075     * no non-{@code DelegatingStatement} delegate can be found by traversing this chain.
076     * </p>
077     * <p>
078     * This method is useful when you may have nested {@code DelegatingStatement}s, and you want to make sure to obtain
079     * a "genuine" {@link Statement}.
080     * </p>
081     *
082     * @return The innermost delegate.
083     *
084     * @see #getDelegate
085     */
086    public Statement getInnermostDelegate() {
087        Statement s = statement;
088        while (s != null && s instanceof DelegatingStatement) {
089            s = ((DelegatingStatement) s).getDelegate();
090            if (this == s) {
091                return null;
092            }
093        }
094        return s;
095    }
096
097    /**
098     * Sets my delegate.
099     *
100     * @param statement
101     *            my delegate.
102     */
103    public void setDelegate(final Statement statement) {
104        this.statement = statement;
105    }
106
107    private boolean closed = false;
108
109    protected boolean isClosedInternal() {
110        return closed;
111    }
112
113    protected void setClosedInternal(final boolean closed) {
114        this.closed = closed;
115    }
116
117    protected void checkOpen() throws SQLException {
118        if (isClosed()) {
119            throw new SQLException(this.getClass().getName() + " with address: \"" + this.toString() + "\" is closed.");
120        }
121    }
122
123    /**
124     * Close this DelegatingStatement, and close any ResultSets that were not explicitly closed.
125     */
126    @Override
127    public void close() throws SQLException {
128        if (isClosed()) {
129            return;
130        }
131        try {
132            try {
133                if (connection != null) {
134                    connection.removeTrace(this);
135                    connection = null;
136                }
137
138                // The JDBC spec requires that a statement close any open
139                // ResultSet's when it is closed.
140                // FIXME The PreparedStatement we're wrapping should handle this for us.
141                // See bug 17301 for what could happen when ResultSets are closed twice.
142                final List<AbandonedTrace> resultSets = getTrace();
143                if (resultSets != null) {
144                    final ResultSet[] set = resultSets.toArray(new ResultSet[resultSets.size()]);
145                    for (final ResultSet element : set) {
146                        element.close();
147                    }
148                    clearTrace();
149                }
150
151                if (statement != null) {
152                    statement.close();
153                }
154            } catch (final SQLException e) {
155                handleException(e);
156            }
157        } finally {
158            closed = true;
159            statement = null;
160        }
161    }
162
163    protected void handleException(final SQLException e) throws SQLException {
164        if (connection != null) {
165            connection.handleException(e);
166        } else {
167            throw e;
168        }
169    }
170
171    /**
172     *
173     * @throws SQLException
174     *             thrown by the delegating statement.
175     * @since 2.4.0 made public, was protected in 2.3.0.
176     */
177    public void activate() throws SQLException {
178        if (statement instanceof DelegatingStatement) {
179            ((DelegatingStatement) statement).activate();
180        }
181    }
182
183    /**
184     *
185     * @throws SQLException
186     *             thrown by the delegating statement.
187     * @since 2.4.0 made public, was protected in 2.3.0.
188     */
189    public void passivate() throws SQLException {
190        if (statement instanceof DelegatingStatement) {
191            ((DelegatingStatement) statement).passivate();
192        }
193    }
194
195    @Override
196    public Connection getConnection() throws SQLException {
197        checkOpen();
198        return getConnectionInternal(); // return the delegating connection that created this
199    }
200
201    protected DelegatingConnection<?> getConnectionInternal() {
202        return connection;
203    }
204
205    @Override
206    public ResultSet executeQuery(final String sql) throws SQLException {
207        checkOpen();
208        if (connection != null) {
209            connection.setLastUsed();
210        }
211        try {
212            return DelegatingResultSet.wrapResultSet(this, statement.executeQuery(sql));
213        } catch (final SQLException e) {
214            handleException(e);
215            throw new AssertionError();
216        }
217    }
218
219    @Override
220    public ResultSet getResultSet() throws SQLException {
221        checkOpen();
222        try {
223            return DelegatingResultSet.wrapResultSet(this, statement.getResultSet());
224        } catch (final SQLException e) {
225            handleException(e);
226            throw new AssertionError();
227        }
228    }
229
230    @Override
231    public int executeUpdate(final String sql) throws SQLException {
232        checkOpen();
233        if (connection != null) {
234            connection.setLastUsed();
235        }
236        try {
237            return statement.executeUpdate(sql);
238        } catch (final SQLException e) {
239            handleException(e);
240            return 0;
241        }
242    }
243
244    @Override
245    public int getMaxFieldSize() throws SQLException {
246        checkOpen();
247        try {
248            return statement.getMaxFieldSize();
249        } catch (final SQLException e) {
250            handleException(e);
251            return 0;
252        }
253    }
254
255    @Override
256    public void setMaxFieldSize(final int max) throws SQLException {
257        checkOpen();
258        try {
259            statement.setMaxFieldSize(max);
260        } catch (final SQLException e) {
261            handleException(e);
262        }
263    }
264
265    @Override
266    public int getMaxRows() throws SQLException {
267        checkOpen();
268        try {
269            return statement.getMaxRows();
270        } catch (final SQLException e) {
271            handleException(e);
272            return 0;
273        }
274    }
275
276    @Override
277    public void setMaxRows(final int max) throws SQLException {
278        checkOpen();
279        try {
280            statement.setMaxRows(max);
281        } catch (final SQLException e) {
282            handleException(e);
283        }
284    }
285
286    @Override
287    public void setEscapeProcessing(final boolean enable) throws SQLException {
288        checkOpen();
289        try {
290            statement.setEscapeProcessing(enable);
291        } catch (final SQLException e) {
292            handleException(e);
293        }
294    }
295
296    @Override
297    public int getQueryTimeout() throws SQLException {
298        checkOpen();
299        try {
300            return statement.getQueryTimeout();
301        } catch (final SQLException e) {
302            handleException(e);
303            return 0;
304        }
305    }
306
307    @Override
308    public void setQueryTimeout(final int seconds) throws SQLException {
309        checkOpen();
310        try {
311            statement.setQueryTimeout(seconds);
312        } catch (final SQLException e) {
313            handleException(e);
314        }
315    }
316
317    @Override
318    public void cancel() throws SQLException {
319        checkOpen();
320        try {
321            statement.cancel();
322        } catch (final SQLException e) {
323            handleException(e);
324        }
325    }
326
327    @Override
328    public SQLWarning getWarnings() throws SQLException {
329        checkOpen();
330        try {
331            return statement.getWarnings();
332        } catch (final SQLException e) {
333            handleException(e);
334            throw new AssertionError();
335        }
336    }
337
338    @Override
339    public void clearWarnings() throws SQLException {
340        checkOpen();
341        try {
342            statement.clearWarnings();
343        } catch (final SQLException e) {
344            handleException(e);
345        }
346    }
347
348    @Override
349    public void setCursorName(final String name) throws SQLException {
350        checkOpen();
351        try {
352            statement.setCursorName(name);
353        } catch (final SQLException e) {
354            handleException(e);
355        }
356    }
357
358    @Override
359    public boolean execute(final String sql) throws SQLException {
360        checkOpen();
361        if (connection != null) {
362            connection.setLastUsed();
363        }
364        try {
365            return statement.execute(sql);
366        } catch (final SQLException e) {
367            handleException(e);
368            return false;
369        }
370    }
371
372    @Override
373    public int getUpdateCount() throws SQLException {
374        checkOpen();
375        try {
376            return statement.getUpdateCount();
377        } catch (final SQLException e) {
378            handleException(e);
379            return 0;
380        }
381    }
382
383    @Override
384    public boolean getMoreResults() throws SQLException {
385        checkOpen();
386        try {
387            return statement.getMoreResults();
388        } catch (final SQLException e) {
389            handleException(e);
390            return false;
391        }
392    }
393
394    @Override
395    public void setFetchDirection(final int direction) throws SQLException {
396        checkOpen();
397        try {
398            statement.setFetchDirection(direction);
399        } catch (final SQLException e) {
400            handleException(e);
401        }
402    }
403
404    @Override
405    public int getFetchDirection() throws SQLException {
406        checkOpen();
407        try {
408            return statement.getFetchDirection();
409        } catch (final SQLException e) {
410            handleException(e);
411            return 0;
412        }
413    }
414
415    @Override
416    public void setFetchSize(final int rows) throws SQLException {
417        checkOpen();
418        try {
419            statement.setFetchSize(rows);
420        } catch (final SQLException e) {
421            handleException(e);
422        }
423    }
424
425    @Override
426    public int getFetchSize() throws SQLException {
427        checkOpen();
428        try {
429            return statement.getFetchSize();
430        } catch (final SQLException e) {
431            handleException(e);
432            return 0;
433        }
434    }
435
436    @Override
437    public int getResultSetConcurrency() throws SQLException {
438        checkOpen();
439        try {
440            return statement.getResultSetConcurrency();
441        } catch (final SQLException e) {
442            handleException(e);
443            return 0;
444        }
445    }
446
447    @Override
448    public int getResultSetType() throws SQLException {
449        checkOpen();
450        try {
451            return statement.getResultSetType();
452        } catch (final SQLException e) {
453            handleException(e);
454            return 0;
455        }
456    }
457
458    @Override
459    public void addBatch(final String sql) throws SQLException {
460        checkOpen();
461        try {
462            statement.addBatch(sql);
463        } catch (final SQLException e) {
464            handleException(e);
465        }
466    }
467
468    @Override
469    public void clearBatch() throws SQLException {
470        checkOpen();
471        try {
472            statement.clearBatch();
473        } catch (final SQLException e) {
474            handleException(e);
475        }
476    }
477
478    @Override
479    public int[] executeBatch() throws SQLException {
480        checkOpen();
481        if (connection != null) {
482            connection.setLastUsed();
483        }
484        try {
485            return statement.executeBatch();
486        } catch (final SQLException e) {
487            handleException(e);
488            throw new AssertionError();
489        }
490    }
491
492    /**
493     * Returns a String representation of this object.
494     *
495     * @return String
496     */
497    @Override
498    public String toString() {
499        return statement == null ? "NULL" : statement.toString();
500    }
501
502    @Override
503    public boolean getMoreResults(final int current) throws SQLException {
504        checkOpen();
505        try {
506            return statement.getMoreResults(current);
507        } catch (final SQLException e) {
508            handleException(e);
509            return false;
510        }
511    }
512
513    @Override
514    public ResultSet getGeneratedKeys() throws SQLException {
515        checkOpen();
516        try {
517            return DelegatingResultSet.wrapResultSet(this, statement.getGeneratedKeys());
518        } catch (final SQLException e) {
519            handleException(e);
520            throw new AssertionError();
521        }
522    }
523
524    @Override
525    public int executeUpdate(final String sql, final int autoGeneratedKeys) throws SQLException {
526        checkOpen();
527        if (connection != null) {
528            connection.setLastUsed();
529        }
530        try {
531            return statement.executeUpdate(sql, autoGeneratedKeys);
532        } catch (final SQLException e) {
533            handleException(e);
534            return 0;
535        }
536    }
537
538    @Override
539    public int executeUpdate(final String sql, final int columnIndexes[]) throws SQLException {
540        checkOpen();
541        if (connection != null) {
542            connection.setLastUsed();
543        }
544        try {
545            return statement.executeUpdate(sql, columnIndexes);
546        } catch (final SQLException e) {
547            handleException(e);
548            return 0;
549        }
550    }
551
552    @Override
553    public int executeUpdate(final String sql, final String columnNames[]) throws SQLException {
554        checkOpen();
555        if (connection != null) {
556            connection.setLastUsed();
557        }
558        try {
559            return statement.executeUpdate(sql, columnNames);
560        } catch (final SQLException e) {
561            handleException(e);
562            return 0;
563        }
564    }
565
566    @Override
567    public boolean execute(final String sql, final int autoGeneratedKeys) throws SQLException {
568        checkOpen();
569        if (connection != null) {
570            connection.setLastUsed();
571        }
572        try {
573            return statement.execute(sql, autoGeneratedKeys);
574        } catch (final SQLException e) {
575            handleException(e);
576            return false;
577        }
578    }
579
580    @Override
581    public boolean execute(final String sql, final int columnIndexes[]) throws SQLException {
582        checkOpen();
583        if (connection != null) {
584            connection.setLastUsed();
585        }
586        try {
587            return statement.execute(sql, columnIndexes);
588        } catch (final SQLException e) {
589            handleException(e);
590            return false;
591        }
592    }
593
594    @Override
595    public boolean execute(final String sql, final String columnNames[]) throws SQLException {
596        checkOpen();
597        if (connection != null) {
598            connection.setLastUsed();
599        }
600        try {
601            return statement.execute(sql, columnNames);
602        } catch (final SQLException e) {
603            handleException(e);
604            return false;
605        }
606    }
607
608    @Override
609    public int getResultSetHoldability() throws SQLException {
610        checkOpen();
611        try {
612            return statement.getResultSetHoldability();
613        } catch (final SQLException e) {
614            handleException(e);
615            return 0;
616        }
617    }
618
619    /*
620     * Note was protected prior to JDBC 4
621     */
622    @Override
623    public boolean isClosed() throws SQLException {
624        return closed;
625    }
626
627    @Override
628    public boolean isWrapperFor(final Class<?> iface) throws SQLException {
629        if (iface.isAssignableFrom(getClass())) {
630            return true;
631        } else if (iface.isAssignableFrom(statement.getClass())) {
632            return true;
633        } else {
634            return statement.isWrapperFor(iface);
635        }
636    }
637
638    @Override
639    public <T> T unwrap(final Class<T> iface) throws SQLException {
640        if (iface.isAssignableFrom(getClass())) {
641            return iface.cast(this);
642        } else if (iface.isAssignableFrom(statement.getClass())) {
643            return iface.cast(statement);
644        } else {
645            return statement.unwrap(iface);
646        }
647    }
648
649    @Override
650    public void setPoolable(final boolean poolable) throws SQLException {
651        checkOpen();
652        try {
653            statement.setPoolable(poolable);
654        } catch (final SQLException e) {
655            handleException(e);
656        }
657    }
658
659    @Override
660    public boolean isPoolable() throws SQLException {
661        checkOpen();
662        try {
663            return statement.isPoolable();
664        } catch (final SQLException e) {
665            handleException(e);
666            return false;
667        }
668    }
669
670    @Override
671    public void closeOnCompletion() throws SQLException {
672        checkOpen();
673        try {
674            statement.closeOnCompletion();
675        } catch (final SQLException e) {
676            handleException(e);
677        }
678    }
679
680    @Override
681    public boolean isCloseOnCompletion() throws SQLException {
682        checkOpen();
683        try {
684            return statement.isCloseOnCompletion();
685        } catch (final SQLException e) {
686            handleException(e);
687            return false;
688        }
689    }
690
691    @Override
692    protected void finalize() throws Throwable {
693        // This is required because of statement pooling. The poolable
694        // statements will always be strongly held by the statement pool. If the
695        // delegating statements that wrap the poolable statement are not
696        // strongly held they will be garbage collected but at that point the
697        // poolable statements need to be returned to the pool else there will
698        // be a leak of statements from the pool. Closing this statement will
699        // close all the wrapped statements and return any poolable statements
700        // to the pool.
701        close();
702        super.finalize();
703    }
704}