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