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