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; 026import java.util.Objects; 027 028/** 029 * A base delegating implementation of {@link Statement}. 030 * <p> 031 * All of the methods from the {@link Statement} interface simply check to see that the {@link Statement} is active, and 032 * call the corresponding method on the "delegate" provided in my constructor. 033 * <p> 034 * Extends AbandonedTrace to implement Statement tracking and logging of code which created the Statement. Tracking the 035 * Statement ensures that the Connection which created it can close any open Statement's on Connection close. 036 * 037 * @since 2.0 038 */ 039public class DelegatingStatement extends AbandonedTrace implements Statement { 040 041 /** My delegate. */ 042 private Statement statement; 043 044 /** The connection that created me. **/ 045 private DelegatingConnection<?> connection; 046 047 private boolean closed; 048 049 /** 050 * Create a wrapper for the Statement which traces this Statement to the Connection which created it and the code 051 * which created it. 052 * 053 * @param statement 054 * the {@link Statement} to delegate all calls to. 055 * @param connection 056 * the {@link DelegatingConnection} that created this statement. 057 */ 058 public DelegatingStatement(final DelegatingConnection<?> connection, final Statement statement) { 059 super(connection); 060 this.statement = statement; 061 this.connection = connection; 062 } 063 064 /** 065 * 066 * @throws SQLException 067 * thrown by the delegating statement. 068 * @since 2.4.0 made public, was protected in 2.3.0. 069 */ 070 public void activate() throws SQLException { 071 if (statement instanceof DelegatingStatement) { 072 ((DelegatingStatement) statement).activate(); 073 } 074 } 075 076 @Override 077 public void addBatch(final String sql) throws SQLException { 078 checkOpen(); 079 try { 080 statement.addBatch(sql); 081 } catch (final SQLException e) { 082 handleException(e); 083 } 084 } 085 086 @Override 087 public void cancel() throws SQLException { 088 checkOpen(); 089 try { 090 statement.cancel(); 091 } catch (final SQLException e) { 092 handleException(e); 093 } 094 } 095 096 protected void checkOpen() throws SQLException { 097 if (isClosed()) { 098 throw new SQLException(this.getClass().getName() + " with address: \"" + toString() + "\" is closed."); 099 } 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}