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