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