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 = false;
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> thrown = 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 int size = resultSetList.size();
143                final ResultSet[] resultSets = resultSetList.toArray(new ResultSet[size]);
144                for (final ResultSet resultSet : resultSets) {
145                    if (resultSet != null) {
146                        try {
147                            resultSet.close();
148                        } catch (Exception e) {
149                            if (connection != null) {
150                                // Does not rethrow e.
151                                connection.handleExceptionNoThrow(e);
152                            }
153                            thrown.add(e);
154                        }
155                    }
156                }
157                clearTrace();
158            }
159            if (statement != null) {
160                try {
161                    statement.close();
162                } catch (Exception e) {
163                    if (connection != null) {
164                        // Does not rethrow e.
165                        connection.handleExceptionNoThrow(e);
166                    }
167                    thrown.add(e);
168                }
169            }
170        } finally {
171            closed = true;
172            statement = null;
173            if (!thrown.isEmpty()) {
174                throw new SQLExceptionList(thrown);
175            }
176        }
177    }
178
179    @Override
180    public void closeOnCompletion() throws SQLException {
181        checkOpen();
182        try {
183            Jdbc41Bridge.closeOnCompletion(statement);
184        } catch (final SQLException e) {
185            handleException(e);
186        }
187    }
188
189    @Override
190    public boolean execute(final String sql) throws SQLException {
191        checkOpen();
192        setLastUsedInParent();
193        try {
194            return statement.execute(sql);
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 autoGeneratedKeys) throws SQLException {
203        checkOpen();
204        setLastUsedInParent();
205        try {
206            return statement.execute(sql, autoGeneratedKeys);
207        } catch (final SQLException e) {
208            handleException(e);
209            return false;
210        }
211    }
212
213    @Override
214    public boolean execute(final String sql, final int columnIndexes[]) throws SQLException {
215        checkOpen();
216        setLastUsedInParent();
217        try {
218            return statement.execute(sql, columnIndexes);
219        } catch (final SQLException e) {
220            handleException(e);
221            return false;
222        }
223    }
224
225    @Override
226    public boolean execute(final String sql, final String columnNames[]) throws SQLException {
227        checkOpen();
228        setLastUsedInParent();
229        try {
230            return statement.execute(sql, columnNames);
231        } catch (final SQLException e) {
232            handleException(e);
233            return false;
234        }
235    }
236
237    @Override
238    public int[] executeBatch() throws SQLException {
239        checkOpen();
240        setLastUsedInParent();
241        try {
242            return statement.executeBatch();
243        } catch (final SQLException e) {
244            handleException(e);
245            throw new AssertionError();
246        }
247    }
248
249    /**
250     * @since 2.5.0
251     */
252    @Override
253    public long[] executeLargeBatch() throws SQLException {
254        checkOpen();
255        setLastUsedInParent();
256        try {
257            return statement.executeLargeBatch();
258        } catch (final SQLException e) {
259            handleException(e);
260            return null;
261        }
262    }
263
264    /**
265     * @since 2.5.0
266     */
267    @Override
268    public long executeLargeUpdate(final String sql) throws SQLException {
269        checkOpen();
270        setLastUsedInParent();
271        try {
272            return statement.executeLargeUpdate(sql);
273        } catch (final SQLException e) {
274            handleException(e);
275            return 0;
276        }
277    }
278
279    /**
280     * @since 2.5.0
281     */
282    @Override
283    public long executeLargeUpdate(final String sql, final int autoGeneratedKeys) throws SQLException {
284        checkOpen();
285        setLastUsedInParent();
286        try {
287            return statement.executeLargeUpdate(sql, autoGeneratedKeys);
288        } catch (final SQLException e) {
289            handleException(e);
290            return 0;
291        }
292    }
293
294    /**
295     * @since 2.5.0
296     */
297    @Override
298    public long executeLargeUpdate(final String sql, final int[] columnIndexes) throws SQLException {
299        checkOpen();
300        setLastUsedInParent();
301        try {
302            return statement.executeLargeUpdate(sql, columnIndexes);
303        } catch (final SQLException e) {
304            handleException(e);
305            return 0;
306        }
307    }
308
309    /**
310     * @since 2.5.0
311     */
312    @Override
313    public long executeLargeUpdate(final String sql, final String[] columnNames) throws SQLException {
314        checkOpen();
315        setLastUsedInParent();
316        try {
317            return statement.executeLargeUpdate(sql, columnNames);
318        } catch (final SQLException e) {
319            handleException(e);
320            return 0;
321        }
322    }
323
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    @Override
441    public ResultSet getGeneratedKeys() throws SQLException {
442        checkOpen();
443        try {
444            return DelegatingResultSet.wrapResultSet(this, statement.getGeneratedKeys());
445        } catch (final SQLException e) {
446            handleException(e);
447            throw new AssertionError();
448        }
449    }
450
451    /**
452     * If my underlying {@link Statement} is not a {@code DelegatingStatement}, returns it, otherwise recursively
453     * invokes this method on my delegate.
454     * <p>
455     * Hence this method will return the first delegate that is not a {@code DelegatingStatement} or {@code null} when
456     * no non-{@code DelegatingStatement} delegate can be found by traversing this chain.
457     * </p>
458     * <p>
459     * This method is useful when you may have nested {@code DelegatingStatement}s, and you want to make sure to obtain
460     * a "genuine" {@link Statement}.
461     * </p>
462     *
463     * @return The innermost delegate.
464     *
465     * @see #getDelegate
466     */
467    @SuppressWarnings("resource")
468    public Statement getInnermostDelegate() {
469        Statement s = statement;
470        while (s != null && s instanceof DelegatingStatement) {
471            s = ((DelegatingStatement) s).getDelegate();
472            if (this == s) {
473                return null;
474            }
475        }
476        return s;
477    }
478
479    /**
480     * @since 2.5.0
481     */
482    @Override
483    public long getLargeMaxRows() throws SQLException {
484        checkOpen();
485        try {
486            return statement.getLargeMaxRows();
487        } catch (final SQLException e) {
488            handleException(e);
489            return 0;
490        }
491    }
492
493    /**
494     * @since 2.5.0
495     */
496    @Override
497    public long getLargeUpdateCount() throws SQLException {
498        checkOpen();
499        try {
500            return statement.getLargeUpdateCount();
501        } catch (final SQLException e) {
502            handleException(e);
503            return 0;
504        }
505    }
506
507    @Override
508    public int getMaxFieldSize() throws SQLException {
509        checkOpen();
510        try {
511            return statement.getMaxFieldSize();
512        } catch (final SQLException e) {
513            handleException(e);
514            return 0;
515        }
516    }
517
518    @Override
519    public int getMaxRows() throws SQLException {
520        checkOpen();
521        try {
522            return statement.getMaxRows();
523        } catch (final SQLException e) {
524            handleException(e);
525            return 0;
526        }
527    }
528
529    @Override
530    public boolean getMoreResults() throws SQLException {
531        checkOpen();
532        try {
533            return statement.getMoreResults();
534        } catch (final SQLException e) {
535            handleException(e);
536            return false;
537        }
538    }
539
540    @Override
541    public boolean getMoreResults(final int current) throws SQLException {
542        checkOpen();
543        try {
544            return statement.getMoreResults(current);
545        } catch (final SQLException e) {
546            handleException(e);
547            return false;
548        }
549    }
550
551    @Override
552    public int getQueryTimeout() throws SQLException {
553        checkOpen();
554        try {
555            return statement.getQueryTimeout();
556        } catch (final SQLException e) {
557            handleException(e);
558            return 0;
559        }
560    }
561
562    @Override
563    public ResultSet getResultSet() throws SQLException {
564        checkOpen();
565        try {
566            return DelegatingResultSet.wrapResultSet(this, statement.getResultSet());
567        } catch (final SQLException e) {
568            handleException(e);
569            throw new AssertionError();
570        }
571    }
572
573    @Override
574    public int getResultSetConcurrency() throws SQLException {
575        checkOpen();
576        try {
577            return statement.getResultSetConcurrency();
578        } catch (final SQLException e) {
579            handleException(e);
580            return 0;
581        }
582    }
583
584    @Override
585    public int getResultSetHoldability() throws SQLException {
586        checkOpen();
587        try {
588            return statement.getResultSetHoldability();
589        } catch (final SQLException e) {
590            handleException(e);
591            return 0;
592        }
593    }
594
595    @Override
596    public int getResultSetType() throws SQLException {
597        checkOpen();
598        try {
599            return statement.getResultSetType();
600        } catch (final SQLException e) {
601            handleException(e);
602            return 0;
603        }
604    }
605
606    @Override
607    public int getUpdateCount() throws SQLException {
608        checkOpen();
609        try {
610            return statement.getUpdateCount();
611        } catch (final SQLException e) {
612            handleException(e);
613            return 0;
614        }
615    }
616
617    @Override
618    public SQLWarning getWarnings() throws SQLException {
619        checkOpen();
620        try {
621            return statement.getWarnings();
622        } catch (final SQLException e) {
623            handleException(e);
624            throw new AssertionError();
625        }
626    }
627
628    protected void handleException(final SQLException e) throws SQLException {
629        if (connection != null) {
630            connection.handleException(e);
631        } else {
632            throw e;
633        }
634    }
635
636    /*
637     * Note: This method was protected prior to JDBC 4.
638     */
639    @Override
640    public boolean isClosed() throws SQLException {
641        return closed;
642    }
643
644    protected boolean isClosedInternal() {
645        return closed;
646    }
647
648    @Override
649    public boolean isCloseOnCompletion() throws SQLException {
650        checkOpen();
651        try {
652            return Jdbc41Bridge.isCloseOnCompletion(statement);
653        } catch (final SQLException e) {
654            handleException(e);
655            return false;
656        }
657    }
658
659    @Override
660    public boolean isPoolable() throws SQLException {
661        checkOpen();
662        try {
663            return statement.isPoolable();
664        } catch (final SQLException e) {
665            handleException(e);
666            return false;
667        }
668    }
669
670    @Override
671    public boolean isWrapperFor(final Class<?> iface) throws SQLException {
672        if (iface.isAssignableFrom(getClass())) {
673            return true;
674        } else if (iface.isAssignableFrom(statement.getClass())) {
675            return true;
676        } else {
677            return statement.isWrapperFor(iface);
678        }
679    }
680
681    /**
682     *
683     * @throws SQLException
684     *             thrown by the delegating statement.
685     * @since 2.4.0 made public, was protected in 2.3.0.
686     */
687    public void passivate() throws SQLException {
688        if (statement instanceof DelegatingStatement) {
689            ((DelegatingStatement) statement).passivate();
690        }
691    }
692
693    protected void setClosedInternal(final boolean closed) {
694        this.closed = closed;
695    }
696
697    @Override
698    public void setCursorName(final String name) throws SQLException {
699        checkOpen();
700        try {
701            statement.setCursorName(name);
702        } catch (final SQLException e) {
703            handleException(e);
704        }
705    }
706
707    /**
708     * Sets my delegate.
709     *
710     * @param statement
711     *            my delegate.
712     */
713    public void setDelegate(final Statement statement) {
714        this.statement = statement;
715    }
716
717    @Override
718    public void setEscapeProcessing(final boolean enable) throws SQLException {
719        checkOpen();
720        try {
721            statement.setEscapeProcessing(enable);
722        } catch (final SQLException e) {
723            handleException(e);
724        }
725    }
726
727    @Override
728    public void setFetchDirection(final int direction) throws SQLException {
729        checkOpen();
730        try {
731            statement.setFetchDirection(direction);
732        } catch (final SQLException e) {
733            handleException(e);
734        }
735    }
736
737    @Override
738    public void setFetchSize(final int rows) throws SQLException {
739        checkOpen();
740        try {
741            statement.setFetchSize(rows);
742        } catch (final SQLException e) {
743            handleException(e);
744        }
745    }
746
747    /**
748     * @since 2.5.0
749     */
750    @Override
751    public void setLargeMaxRows(final long max) throws SQLException {
752        checkOpen();
753        try {
754            statement.setLargeMaxRows(max);
755        } catch (final SQLException e) {
756            handleException(e);
757        }
758    }
759
760    private void setLastUsedInParent() {
761        if (connection != null) {
762            connection.setLastUsed();
763        }
764    }
765
766    @Override
767    public void setMaxFieldSize(final int max) throws SQLException {
768        checkOpen();
769        try {
770            statement.setMaxFieldSize(max);
771        } catch (final SQLException e) {
772            handleException(e);
773        }
774    }
775
776    @Override
777    public void setMaxRows(final int max) throws SQLException {
778        checkOpen();
779        try {
780            statement.setMaxRows(max);
781        } catch (final SQLException e) {
782            handleException(e);
783        }
784    }
785
786    @Override
787    public void setPoolable(final boolean poolable) throws SQLException {
788        checkOpen();
789        try {
790            statement.setPoolable(poolable);
791        } catch (final SQLException e) {
792            handleException(e);
793        }
794    }
795
796    @Override
797    public void setQueryTimeout(final int seconds) throws SQLException {
798        checkOpen();
799        try {
800            statement.setQueryTimeout(seconds);
801        } catch (final SQLException e) {
802            handleException(e);
803        }
804    }
805
806    /**
807     * Returns a String representation of this object.
808     *
809     * @return String
810     */
811    @Override
812    public synchronized String toString() {
813        return statement == null ? "NULL" : statement.toString();
814    }
815
816    @Override
817    public <T> T unwrap(final Class<T> iface) throws SQLException {
818        if (iface.isAssignableFrom(getClass())) {
819            return iface.cast(this);
820        } else if (iface.isAssignableFrom(statement.getClass())) {
821            return iface.cast(statement);
822        } else {
823            return statement.unwrap(iface);
824        }
825    }
826}