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    private boolean closed = false;
046
047    /**
048     * Create a wrapper for the Statement which traces this Statement to the Connection which created it and the code
049     * which created it.
050     *
051     * @param statement
052     *            the {@link Statement} to delegate all calls to.
053     * @param connection
054     *            the {@link DelegatingConnection} that created this statement.
055     */
056    public DelegatingStatement(final DelegatingConnection<?> connection, final Statement statement) {
057        super(connection);
058        this.statement = statement;
059        this.connection = connection;
060    }
061
062    /**
063     *
064     * @throws SQLException
065     *             thrown by the delegating statement.
066     * @since 2.4.0 made public, was protected in 2.3.0.
067     */
068    public void activate() throws SQLException {
069        if (statement instanceof DelegatingStatement) {
070            ((DelegatingStatement) statement).activate();
071        }
072    }
073
074    @Override
075    public void addBatch(final String sql) throws SQLException {
076        checkOpen();
077        try {
078            statement.addBatch(sql);
079        } catch (final SQLException e) {
080            handleException(e);
081        }
082    }
083
084    @Override
085    public void cancel() throws SQLException {
086        checkOpen();
087        try {
088            statement.cancel();
089        } catch (final SQLException e) {
090            handleException(e);
091        }
092    }
093
094    protected void checkOpen() throws SQLException {
095        if (isClosed()) {
096            throw new SQLException(this.getClass().getName() + " with address: \"" + this.toString() + "\" is closed.");
097        }
098    }
099
100    @Override
101    public void clearBatch() throws SQLException {
102        checkOpen();
103        try {
104            statement.clearBatch();
105        } catch (final SQLException e) {
106            handleException(e);
107        }
108    }
109
110    @Override
111    public void clearWarnings() throws SQLException {
112        checkOpen();
113        try {
114            statement.clearWarnings();
115        } catch (final SQLException e) {
116            handleException(e);
117        }
118    }
119
120    /**
121     * Close this DelegatingStatement, and close any ResultSets that were not explicitly closed.
122     */
123    @Override
124    public void close() throws SQLException {
125        if (isClosed()) {
126            return;
127        }
128        try {
129            try {
130                if (connection != null) {
131                    connection.removeTrace(this);
132                    connection = null;
133                }
134
135                // The JDBC spec requires that a statement close any open
136                // ResultSet's when it is closed.
137                // FIXME The PreparedStatement we're wrapping should handle this for us.
138                // See bug 17301 for what could happen when ResultSets are closed twice.
139                final List<AbandonedTrace> resultSets = getTrace();
140                if (resultSets != null) {
141                    final ResultSet[] set = resultSets.toArray(new ResultSet[resultSets.size()]);
142                    for (final ResultSet element : set) {
143                        element.close();
144                    }
145                    clearTrace();
146                }
147
148                if (statement != null) {
149                    statement.close();
150                }
151            } catch (final SQLException e) {
152                handleException(e);
153            }
154        } finally {
155            closed = true;
156            statement = null;
157        }
158    }
159
160    @Override
161    public void closeOnCompletion() throws SQLException {
162        checkOpen();
163        try {
164            statement.closeOnCompletion();
165        } catch (final SQLException e) {
166            handleException(e);
167        }
168    }
169
170    @Override
171    public boolean execute(final String sql) throws SQLException {
172        checkOpen();
173        setLastUsedInParent();
174        try {
175            return statement.execute(sql);
176        } catch (final SQLException e) {
177            handleException(e);
178            return false;
179        }
180    }
181
182    @Override
183    public boolean execute(final String sql, final int autoGeneratedKeys) throws SQLException {
184        checkOpen();
185        setLastUsedInParent();
186        try {
187            return statement.execute(sql, autoGeneratedKeys);
188        } catch (final SQLException e) {
189            handleException(e);
190            return false;
191        }
192    }
193
194    @Override
195    public boolean execute(final String sql, final int columnIndexes[]) throws SQLException {
196        checkOpen();
197        setLastUsedInParent();
198        try {
199            return statement.execute(sql, columnIndexes);
200        } catch (final SQLException e) {
201            handleException(e);
202            return false;
203        }
204    }
205
206    @Override
207    public boolean execute(final String sql, final String columnNames[]) throws SQLException {
208        checkOpen();
209        setLastUsedInParent();
210        try {
211            return statement.execute(sql, columnNames);
212        } catch (final SQLException e) {
213            handleException(e);
214            return false;
215        }
216    }
217
218    @Override
219    public int[] executeBatch() throws SQLException {
220        checkOpen();
221        setLastUsedInParent();
222        try {
223            return statement.executeBatch();
224        } catch (final SQLException e) {
225            handleException(e);
226            throw new AssertionError();
227        }
228    }
229
230    /**
231     * @since 2.5.0
232     */
233    @Override
234    public long[] executeLargeBatch() throws SQLException {
235        checkOpen();
236        setLastUsedInParent();
237        try {
238            return statement.executeLargeBatch();
239        } catch (final SQLException e) {
240            handleException(e);
241            return null;
242        }
243    }
244
245    /**
246     * @since 2.5.0
247     */
248    @Override
249    public long executeLargeUpdate(final String sql) throws SQLException {
250        checkOpen();
251        setLastUsedInParent();
252        try {
253            return statement.executeLargeUpdate(sql);
254        } catch (final SQLException e) {
255            handleException(e);
256            return 0;
257        }
258    }
259
260    /**
261     * @since 2.5.0
262     */
263    @Override
264    public long executeLargeUpdate(final String sql, final int autoGeneratedKeys) throws SQLException {
265        checkOpen();
266        setLastUsedInParent();
267        try {
268            return statement.executeLargeUpdate(sql, autoGeneratedKeys);
269        } catch (final SQLException e) {
270            handleException(e);
271            return 0;
272        }
273    }
274
275    /**
276     * @since 2.5.0
277     */
278    @Override
279    public long executeLargeUpdate(final String sql, final int[] columnIndexes) throws SQLException {
280        checkOpen();
281        setLastUsedInParent();
282        try {
283            return statement.executeLargeUpdate(sql, columnIndexes);
284        } catch (final SQLException e) {
285            handleException(e);
286            return 0;
287        }
288    }
289
290    /**
291     * @since 2.5.0
292     */
293    @Override
294    public long executeLargeUpdate(final String sql, final String[] columnNames) throws SQLException {
295        checkOpen();
296        setLastUsedInParent();
297        try {
298            return statement.executeLargeUpdate(sql, columnNames);
299        } catch (final SQLException e) {
300            handleException(e);
301            return 0;
302        }
303    }
304
305    @Override
306    public ResultSet executeQuery(final String sql) throws SQLException {
307        checkOpen();
308        setLastUsedInParent();
309        try {
310            return DelegatingResultSet.wrapResultSet(this, statement.executeQuery(sql));
311        } catch (final SQLException e) {
312            handleException(e);
313            throw new AssertionError();
314        }
315    }
316
317    @Override
318    public int executeUpdate(final String sql) throws SQLException {
319        checkOpen();
320        setLastUsedInParent();
321        try {
322            return statement.executeUpdate(sql);
323        } catch (final SQLException e) {
324            handleException(e);
325            return 0;
326        }
327    }
328
329    @Override
330    public int executeUpdate(final String sql, final int autoGeneratedKeys) throws SQLException {
331        checkOpen();
332        setLastUsedInParent();
333        try {
334            return statement.executeUpdate(sql, autoGeneratedKeys);
335        } catch (final SQLException e) {
336            handleException(e);
337            return 0;
338        }
339    }
340
341    @Override
342    public int executeUpdate(final String sql, final int columnIndexes[]) throws SQLException {
343        checkOpen();
344        setLastUsedInParent();
345        try {
346            return statement.executeUpdate(sql, columnIndexes);
347        } catch (final SQLException e) {
348            handleException(e);
349            return 0;
350        }
351    }
352
353    @Override
354    public int executeUpdate(final String sql, final String columnNames[]) throws SQLException {
355        checkOpen();
356        setLastUsedInParent();
357        try {
358            return statement.executeUpdate(sql, columnNames);
359        } catch (final SQLException e) {
360            handleException(e);
361            return 0;
362        }
363    }
364
365    @Override
366    protected void finalize() throws Throwable {
367        // This is required because of statement pooling. The poolable
368        // statements will always be strongly held by the statement pool. If the
369        // delegating statements that wrap the poolable statement are not
370        // strongly held they will be garbage collected but at that point the
371        // poolable statements need to be returned to the pool else there will
372        // be a leak of statements from the pool. Closing this statement will
373        // close all the wrapped statements and return any poolable statements
374        // to the pool.
375        close();
376        super.finalize();
377    }
378
379    @Override
380    public Connection getConnection() throws SQLException {
381        checkOpen();
382        return getConnectionInternal(); // return the delegating connection that created this
383    }
384
385    protected DelegatingConnection<?> getConnectionInternal() {
386        return connection;
387    }
388
389    /**
390     * Returns my underlying {@link Statement}.
391     *
392     * @return my underlying {@link Statement}.
393     * @see #getInnermostDelegate
394     */
395    public Statement getDelegate() {
396        return statement;
397    }
398
399    @Override
400    public int getFetchDirection() throws SQLException {
401        checkOpen();
402        try {
403            return statement.getFetchDirection();
404        } catch (final SQLException e) {
405            handleException(e);
406            return 0;
407        }
408    }
409
410    @Override
411    public int getFetchSize() throws SQLException {
412        checkOpen();
413        try {
414            return statement.getFetchSize();
415        } catch (final SQLException e) {
416            handleException(e);
417            return 0;
418        }
419    }
420
421    @Override
422    public ResultSet getGeneratedKeys() throws SQLException {
423        checkOpen();
424        try {
425            return DelegatingResultSet.wrapResultSet(this, statement.getGeneratedKeys());
426        } catch (final SQLException e) {
427            handleException(e);
428            throw new AssertionError();
429        }
430    }
431
432    /**
433     * If my underlying {@link Statement} is not a {@code DelegatingStatement}, returns it, otherwise recursively
434     * invokes this method on my delegate.
435     * <p>
436     * Hence this method will return the first delegate that is not a {@code DelegatingStatement} or {@code null} when
437     * no non-{@code DelegatingStatement} delegate can be found by traversing this chain.
438     * </p>
439     * <p>
440     * This method is useful when you may have nested {@code DelegatingStatement}s, and you want to make sure to obtain
441     * a "genuine" {@link Statement}.
442     * </p>
443     *
444     * @return The innermost delegate.
445     *
446     * @see #getDelegate
447     */
448    @SuppressWarnings("resource")
449    public Statement getInnermostDelegate() {
450        Statement s = statement;
451        while (s != null && s instanceof DelegatingStatement) {
452            s = ((DelegatingStatement) s).getDelegate();
453            if (this == s) {
454                return null;
455            }
456        }
457        return s;
458    }
459
460    /**
461     * @since 2.5.0
462     */
463    @Override
464    public long getLargeMaxRows() throws SQLException {
465        checkOpen();
466        try {
467            return statement.getLargeMaxRows();
468        } catch (final SQLException e) {
469            handleException(e);
470            return 0;
471        }
472    }
473
474    /**
475     * @since 2.5.0
476     */
477    @Override
478    public long getLargeUpdateCount() throws SQLException {
479        checkOpen();
480        try {
481            return statement.getLargeUpdateCount();
482        } catch (final SQLException e) {
483            handleException(e);
484            return 0;
485        }
486    }
487
488    @Override
489    public int getMaxFieldSize() throws SQLException {
490        checkOpen();
491        try {
492            return statement.getMaxFieldSize();
493        } catch (final SQLException e) {
494            handleException(e);
495            return 0;
496        }
497    }
498
499    @Override
500    public int getMaxRows() throws SQLException {
501        checkOpen();
502        try {
503            return statement.getMaxRows();
504        } catch (final SQLException e) {
505            handleException(e);
506            return 0;
507        }
508    }
509
510    @Override
511    public boolean getMoreResults() throws SQLException {
512        checkOpen();
513        try {
514            return statement.getMoreResults();
515        } catch (final SQLException e) {
516            handleException(e);
517            return false;
518        }
519    }
520
521    @Override
522    public boolean getMoreResults(final int current) throws SQLException {
523        checkOpen();
524        try {
525            return statement.getMoreResults(current);
526        } catch (final SQLException e) {
527            handleException(e);
528            return false;
529        }
530    }
531
532    @Override
533    public int getQueryTimeout() throws SQLException {
534        checkOpen();
535        try {
536            return statement.getQueryTimeout();
537        } catch (final SQLException e) {
538            handleException(e);
539            return 0;
540        }
541    }
542
543    @Override
544    public ResultSet getResultSet() throws SQLException {
545        checkOpen();
546        try {
547            return DelegatingResultSet.wrapResultSet(this, statement.getResultSet());
548        } catch (final SQLException e) {
549            handleException(e);
550            throw new AssertionError();
551        }
552    }
553
554    @Override
555    public int getResultSetConcurrency() throws SQLException {
556        checkOpen();
557        try {
558            return statement.getResultSetConcurrency();
559        } catch (final SQLException e) {
560            handleException(e);
561            return 0;
562        }
563    }
564
565    @Override
566    public int getResultSetHoldability() throws SQLException {
567        checkOpen();
568        try {
569            return statement.getResultSetHoldability();
570        } catch (final SQLException e) {
571            handleException(e);
572            return 0;
573        }
574    }
575
576    @Override
577    public int getResultSetType() throws SQLException {
578        checkOpen();
579        try {
580            return statement.getResultSetType();
581        } catch (final SQLException e) {
582            handleException(e);
583            return 0;
584        }
585    }
586
587    @Override
588    public int getUpdateCount() throws SQLException {
589        checkOpen();
590        try {
591            return statement.getUpdateCount();
592        } catch (final SQLException e) {
593            handleException(e);
594            return 0;
595        }
596    }
597
598    @Override
599    public SQLWarning getWarnings() throws SQLException {
600        checkOpen();
601        try {
602            return statement.getWarnings();
603        } catch (final SQLException e) {
604            handleException(e);
605            throw new AssertionError();
606        }
607    }
608
609    protected void handleException(final SQLException e) throws SQLException {
610        if (connection != null) {
611            connection.handleException(e);
612        } else {
613            throw e;
614        }
615    }
616
617    /*
618     * Note was protected prior to JDBC 4
619     */
620    @Override
621    public boolean isClosed() throws SQLException {
622        return closed;
623    }
624
625    protected boolean isClosedInternal() {
626        return closed;
627    }
628
629    @Override
630    public boolean isCloseOnCompletion() throws SQLException {
631        checkOpen();
632        try {
633            return statement.isCloseOnCompletion();
634        } catch (final SQLException e) {
635            handleException(e);
636            return false;
637        }
638    }
639
640    @Override
641    public boolean isPoolable() throws SQLException {
642        checkOpen();
643        try {
644            return statement.isPoolable();
645        } catch (final SQLException e) {
646            handleException(e);
647            return false;
648        }
649    }
650
651    @Override
652    public boolean isWrapperFor(final Class<?> iface) throws SQLException {
653        if (iface.isAssignableFrom(getClass())) {
654            return true;
655        } else if (iface.isAssignableFrom(statement.getClass())) {
656            return true;
657        } else {
658            return statement.isWrapperFor(iface);
659        }
660    }
661
662    /**
663     *
664     * @throws SQLException
665     *             thrown by the delegating statement.
666     * @since 2.4.0 made public, was protected in 2.3.0.
667     */
668    public void passivate() throws SQLException {
669        if (statement instanceof DelegatingStatement) {
670            ((DelegatingStatement) statement).passivate();
671        }
672    }
673
674    protected void setClosedInternal(final boolean closed) {
675        this.closed = closed;
676    }
677
678    @Override
679    public void setCursorName(final String name) throws SQLException {
680        checkOpen();
681        try {
682            statement.setCursorName(name);
683        } catch (final SQLException e) {
684            handleException(e);
685        }
686    }
687
688    /**
689     * Sets my delegate.
690     *
691     * @param statement
692     *            my delegate.
693     */
694    public void setDelegate(final Statement statement) {
695        this.statement = statement;
696    }
697
698    @Override
699    public void setEscapeProcessing(final boolean enable) throws SQLException {
700        checkOpen();
701        try {
702            statement.setEscapeProcessing(enable);
703        } catch (final SQLException e) {
704            handleException(e);
705        }
706    }
707
708    @Override
709    public void setFetchDirection(final int direction) throws SQLException {
710        checkOpen();
711        try {
712            statement.setFetchDirection(direction);
713        } catch (final SQLException e) {
714            handleException(e);
715        }
716    }
717
718    @Override
719    public void setFetchSize(final int rows) throws SQLException {
720        checkOpen();
721        try {
722            statement.setFetchSize(rows);
723        } catch (final SQLException e) {
724            handleException(e);
725        }
726    }
727
728    /**
729     * @since 2.5.0
730     */
731    @Override
732    public void setLargeMaxRows(final long max) throws SQLException {
733        checkOpen();
734        try {
735            statement.setLargeMaxRows(max);
736        } catch (final SQLException e) {
737            handleException(e);
738        }
739    }
740
741    private void setLastUsedInParent() {
742        if (connection != null) {
743            connection.setLastUsed();
744        }
745    }
746
747    @Override
748    public void setMaxFieldSize(final int max) throws SQLException {
749        checkOpen();
750        try {
751            statement.setMaxFieldSize(max);
752        } catch (final SQLException e) {
753            handleException(e);
754        }
755    }
756
757    @Override
758    public void setMaxRows(final int max) throws SQLException {
759        checkOpen();
760        try {
761            statement.setMaxRows(max);
762        } catch (final SQLException e) {
763            handleException(e);
764        }
765    }
766
767    @Override
768    public void setPoolable(final boolean poolable) throws SQLException {
769        checkOpen();
770        try {
771            statement.setPoolable(poolable);
772        } catch (final SQLException e) {
773            handleException(e);
774        }
775    }
776
777    @Override
778    public void setQueryTimeout(final int seconds) throws SQLException {
779        checkOpen();
780        try {
781            statement.setQueryTimeout(seconds);
782        } catch (final SQLException e) {
783            handleException(e);
784        }
785    }
786
787    /**
788     * Returns a String representation of this object.
789     *
790     * @return String
791     */
792    @Override
793    public String toString() {
794        return statement == null ? "NULL" : statement.toString();
795    }
796
797    @Override
798    public <T> T unwrap(final Class<T> iface) throws SQLException {
799        if (iface.isAssignableFrom(getClass())) {
800            return iface.cast(this);
801        } else if (iface.isAssignableFrom(statement.getClass())) {
802            return iface.cast(statement);
803        } else {
804            return statement.unwrap(iface);
805        }
806    }
807}