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  
27  /**
28   * A base delegating implementation of {@link Statement}.
29   * <p>
30   * All of the methods from the {@link Statement} interface simply check to see that the {@link Statement} is active, and
31   * call the corresponding method on the "delegate" provided in my constructor.
32   * <p>
33   * Extends AbandonedTrace to implement Statement tracking and logging of code which created the Statement. Tracking the
34   * Statement ensures that the Connection which created it can close any open Statement's on Connection close.
35   *
36   * @since 2.0
37   */
38  public class DelegatingStatement extends AbandonedTrace implements Statement {
39  
40      /** My delegate. */
41      private Statement statement;
42  
43      /** The connection that created me. **/
44      private DelegatingConnection<?> connection;
45  
46      private boolean closed;
47  
48      /**
49       * Create a wrapper for the Statement which traces this Statement to the Connection which created it and the code
50       * which created it.
51       *
52       * @param statement
53       *            the {@link Statement} to delegate all calls to.
54       * @param connection
55       *            the {@link DelegatingConnection} that created this statement.
56       */
57      public DelegatingStatement(final DelegatingConnection<?> connection, final Statement statement) {
58          super(connection);
59          this.statement = statement;
60          this.connection = connection;
61      }
62  
63      /**
64       *
65       * @throws SQLException
66       *             thrown by the delegating statement.
67       * @since 2.4.0 made public, was protected in 2.3.0.
68       */
69      public void activate() throws SQLException {
70          if (statement instanceof DelegatingStatement) {
71              ((DelegatingStatement) statement).activate();
72          }
73      }
74  
75      @Override
76      public void addBatch(final String sql) throws SQLException {
77          checkOpen();
78          try {
79              statement.addBatch(sql);
80          } catch (final SQLException e) {
81              handleException(e);
82          }
83      }
84  
85      @Override
86      public void cancel() throws SQLException {
87          checkOpen();
88          try {
89              statement.cancel();
90          } catch (final SQLException e) {
91              handleException(e);
92          }
93      }
94  
95      protected void checkOpen() throws SQLException {
96          if (isClosed()) {
97              throw new SQLException(this.getClass().getName() + " with address: \"" + this.toString() + "\" is closed.");
98          }
99      }
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 }