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