View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.dbcp2;
18  
19  import java.sql.Connection;
20  import java.sql.ResultSet;
21  import java.sql.SQLException;
22  import java.sql.SQLWarning;
23  import java.sql.Statement;
24  import java.util.ArrayList;
25  import java.util.List;
26  import java.util.Objects;
27  
28  /**
29   * A base delegating implementation of {@link Statement}.
30   * <p>
31   * All of the methods from the {@link Statement} interface simply check to see that the {@link Statement} is active, and
32   * call the corresponding method on the "delegate" provided in my constructor.
33   * <p>
34   * Extends AbandonedTrace to implement Statement tracking and logging of code which created the Statement. Tracking the
35   * Statement ensures that the Connection which created it can close any open Statement's on Connection close.
36   *
37   * @since 2.0
38   */
39  public class DelegatingStatement extends AbandonedTrace implements Statement {
40  
41      /** My delegate. */
42      private Statement statement;
43  
44      /** The connection that created me. **/
45      private DelegatingConnection<?> connection;
46  
47      private boolean closed;
48  
49      /**
50       * Create a wrapper for the Statement which traces this Statement to the Connection which created it and the code
51       * which created it.
52       *
53       * @param statement
54       *            the {@link Statement} to delegate all calls to.
55       * @param connection
56       *            the {@link DelegatingConnection} that created this statement.
57       */
58      public DelegatingStatement(final DelegatingConnection<?> connection, final Statement statement) {
59          super(connection);
60          this.statement = statement;
61          this.connection = connection;
62      }
63  
64      /**
65       *
66       * @throws SQLException
67       *             thrown by the delegating statement.
68       * @since 2.4.0 made public, was protected in 2.3.0.
69       */
70      public void activate() throws SQLException {
71          if (statement instanceof DelegatingStatement) {
72              ((DelegatingStatement) statement).activate();
73          }
74      }
75  
76      @Override
77      public void addBatch(final String sql) throws SQLException {
78          checkOpen();
79          try {
80              statement.addBatch(sql);
81          } catch (final SQLException e) {
82              handleException(e);
83          }
84      }
85  
86      @Override
87      public void cancel() throws SQLException {
88          checkOpen();
89          try {
90              statement.cancel();
91          } catch (final SQLException e) {
92              handleException(e);
93          }
94      }
95  
96      protected void checkOpen() throws SQLException {
97          if (isClosed()) {
98              throw new SQLException(this.getClass().getName() + " with address: \"" + toString() + "\" is closed.");
99          }
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 }