001 /** 002 * Copyright 2007-2008 Arthur Blake 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package net.sf.log4jdbc; 017 018 import java.io.InputStream; 019 import java.io.Reader; 020 import java.math.BigDecimal; 021 import java.net.URL; 022 import java.sql.Array; 023 import java.sql.Blob; 024 import java.sql.Clob; 025 import java.sql.Date; 026 import java.sql.ParameterMetaData; 027 import java.sql.PreparedStatement; 028 import java.sql.Ref; 029 import java.sql.ResultSet; 030 import java.sql.ResultSetMetaData; 031 import java.sql.SQLException; 032 import java.sql.Time; 033 import java.sql.Timestamp; 034 import java.util.ArrayList; 035 import java.util.Calendar; 036 import java.util.StringTokenizer; 037 import java.util.List; 038 039 /** 040 * Wraps a PreparedStatement and reports method calls, returns and exceptions. 041 * 042 * @author Arthur Blake 043 */ 044 public class PreparedStatementSpy extends StatementSpy implements PreparedStatement 045 { 046 047 private final SpyLogDelegator log; 048 049 /** 050 * holds list of bind variables for tracing 051 */ 052 protected final List argTrace = new ArrayList(); 053 054 // a way to turn on and off type help... 055 // todo: make this a configurable parameter 056 // todo, debug arrays and streams in a more useful manner.... if possible 057 private static final boolean showTypeHelp = false; 058 059 /** 060 * Store an argument (bind variable) into the argTrace list (above) for later dumping. 061 * 062 * @param i index of argument being set. 063 * @param typeHelper optional additional info about the type that is being set in the arg 064 * @param arg argument being bound. 065 */ 066 protected void argTraceSet(int i, String typeHelper, Object arg) 067 { 068 i--; // make the index 0 based 069 synchronized (argTrace) 070 { 071 // if an object is being inserted out of sequence, fill up missing values with null... 072 while (i >= argTrace.size()) 073 { 074 argTrace.add(argTrace.size(), null); 075 } 076 if (!showTypeHelp || typeHelper == null) 077 { 078 typeHelper = ""; 079 } 080 if (arg != null) 081 { 082 argTrace.set(i, typeHelper + arg.toString()); 083 } 084 else 085 { 086 argTrace.set(i, typeHelper + "null"); 087 } 088 } 089 } 090 091 private String sql; 092 093 protected String dumpedSql() 094 { 095 StringBuffer dumpSql = new StringBuffer(); 096 int lastPos = 0; 097 int Qpos = sql.indexOf('?', lastPos); // find position of first question mark 098 int argIdx = 0; 099 String arg; 100 101 while (Qpos != -1) 102 { 103 // get stored argument 104 synchronized (argTrace) 105 { 106 try 107 { 108 arg = (String) argTrace.get(argIdx); 109 } 110 catch (IndexOutOfBoundsException e) 111 { 112 arg = "?"; 113 } 114 } 115 if (arg == null) 116 { 117 arg = "?"; 118 } 119 120 argIdx++; 121 122 dumpSql.append(sql.substring(lastPos, Qpos)); // dump segment of sql up to question mark. 123 lastPos = Qpos + 1; 124 Qpos = sql.indexOf('?', lastPos); 125 dumpSql.append(arg); 126 } 127 if (lastPos < sql.length()) 128 { 129 dumpSql.append(sql.substring(lastPos, sql.length())); // dump last segment 130 } 131 132 // insert line breaks into sql to make it more readable 133 StringBuffer output = new StringBuffer(); 134 StringTokenizer st = new StringTokenizer(dumpSql.toString()); 135 136 String token; 137 int linelength = 0; 138 139 while (st.hasMoreElements()) 140 { 141 token = (String) st.nextElement(); 142 143 output.append(token); 144 linelength += token.length(); 145 output.append(" "); 146 linelength++; 147 if (linelength > 90) 148 { 149 output.append("\n"); 150 linelength = 0; 151 } 152 } 153 154 return output.toString(); 155 } 156 157 protected void reportAllReturns(String methodCall, String msg) 158 { 159 log.methodReturned(this, methodCall, msg); 160 } 161 162 /** 163 * The real PreparedStatement that this PreparedStatementSpy wraps. 164 */ 165 protected PreparedStatement realPreparedStatement; 166 167 /** 168 * RdbmsSpecifics for formatting SQL for the given RDBMS. 169 */ 170 protected RdbmsSpecifics rdbmsSpecifics; 171 172 /** 173 * Create a prepared statement spy for logging activity of another PreparedStatement. 174 * 175 * @param sql SQL for the prepared statement that is being spied upon. 176 * @param connectionSpy ConnectionSpy that was called to produce this PreparedStatement. 177 * @param realPreparedStatement The actual PreparedStatement that is being spied upon. 178 */ 179 public PreparedStatementSpy(String sql, ConnectionSpy connectionSpy, PreparedStatement realPreparedStatement) 180 { 181 super(connectionSpy, realPreparedStatement); // does null check for us 182 this.sql = sql; 183 this.realPreparedStatement = realPreparedStatement; 184 log = SpyLogFactory.getSpyLogDelegator(); 185 rdbmsSpecifics = connectionSpy.getRdbmsSpecifics(); 186 } 187 188 public String getClassType() 189 { 190 return "PreparedStatement"; 191 } 192 193 // forwarding methods 194 195 public void setTime(int parameterIndex, Time x) throws SQLException 196 { 197 String methodCall = "setTime(" + parameterIndex + ", " + x + ")"; 198 argTraceSet(parameterIndex, "(Time)", x); 199 try 200 { 201 realPreparedStatement.setTime(parameterIndex, x); 202 } 203 catch (SQLException s) 204 { 205 reportException(methodCall, s); 206 throw s; 207 } 208 reportReturn(methodCall); 209 } 210 211 public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException 212 { 213 String methodCall = "setTime(" + parameterIndex + ", " + x + ", " + cal + ")"; 214 argTraceSet(parameterIndex, "(Time)", x); 215 try 216 { 217 realPreparedStatement.setTime(parameterIndex, x, cal); 218 } 219 catch (SQLException s) 220 { 221 reportException(methodCall, s); 222 throw s; 223 } 224 reportReturn(methodCall); 225 } 226 227 public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException 228 { 229 String methodCall = "setCharacterStream(" + parameterIndex + ", " + reader + ", " + length + ")"; 230 argTraceSet(parameterIndex, "(Reader)", "<Reader of length " + length + ">"); 231 try 232 { 233 realPreparedStatement.setCharacterStream(parameterIndex, reader, length); 234 } 235 catch (SQLException s) 236 { 237 reportException(methodCall, s); 238 throw s; 239 } 240 reportReturn(methodCall); 241 } 242 243 public void setNull(int parameterIndex, int sqlType) throws SQLException 244 { 245 String methodCall = "setNull(" + parameterIndex + ", " + sqlType + ")"; 246 argTraceSet(parameterIndex, null, "null"); 247 try 248 { 249 realPreparedStatement.setNull(parameterIndex, sqlType); 250 } 251 catch (SQLException s) 252 { 253 reportException(methodCall, s); 254 throw s; 255 } 256 reportReturn(methodCall); 257 } 258 259 public void setNull(int paramIndex, int sqlType, String typeName) throws SQLException 260 { 261 String methodCall = "setNull(" + paramIndex + ", " + sqlType + ", " + typeName + ")"; 262 argTraceSet(paramIndex, null, "null"); 263 try 264 { 265 realPreparedStatement.setNull(paramIndex, sqlType, typeName); 266 } 267 catch (SQLException s) 268 { 269 reportException(methodCall, s); 270 throw s; 271 } 272 reportReturn(methodCall); 273 } 274 275 public void setRef(int i, Ref x) throws SQLException 276 { 277 String methodCall = "setRef(" + i + ", " + x + ")"; 278 argTraceSet(i, "(Ref)", x); 279 try 280 { 281 realPreparedStatement.setRef(i, x); 282 } 283 catch (SQLException s) 284 { 285 reportException(methodCall, s); 286 throw s; 287 } 288 reportReturn(methodCall); 289 } 290 291 public void setBoolean(int parameterIndex, boolean x) throws SQLException 292 { 293 String methodCall = "setBoolean(" + parameterIndex + ", " + x + ")"; 294 argTraceSet(parameterIndex, "(boolean)", Boolean.toString(x)); 295 try 296 { 297 realPreparedStatement.setBoolean(parameterIndex, x); 298 } 299 catch (SQLException s) 300 { 301 reportException(methodCall, s); 302 throw s; 303 } 304 reportReturn(methodCall); 305 } 306 307 public void setBlob(int i, Blob x) throws SQLException 308 { 309 String methodCall = "setBlob(" + i + ", " + x + ")"; 310 argTraceSet(i, "(Blob)", "<Blob of size " + x.length() + ">"); 311 try 312 { 313 realPreparedStatement.setBlob(i, x); 314 } 315 catch (SQLException s) 316 { 317 reportException(methodCall, s); 318 throw s; 319 } 320 reportReturn(methodCall); 321 } 322 323 public void setClob(int i, Clob x) throws SQLException 324 { 325 String methodCall = "setClob(" + i + ", " + x + ")"; 326 argTraceSet(i, "(Clob)", "<Clob of size " + x.length() + ">"); 327 try 328 { 329 realPreparedStatement.setClob(i, x); 330 } 331 catch (SQLException s) 332 { 333 reportException(methodCall, s); 334 throw s; 335 } 336 reportReturn(methodCall); 337 } 338 339 public void setArray(int i, Array x) throws SQLException 340 { 341 String methodCall = "setArray(" + i + ", " + x + ")"; 342 argTraceSet(i, "(Array)", "<Array>"); 343 try 344 { 345 realPreparedStatement.setArray(i, x); 346 } 347 catch (SQLException s) 348 { 349 reportException(methodCall, s); 350 throw s; 351 } 352 reportReturn(methodCall); 353 } 354 355 public void setByte(int parameterIndex, byte x) throws SQLException 356 { 357 String methodCall = "setByte(" + parameterIndex + ", " + x + ")"; 358 argTraceSet(parameterIndex, "(byte)", Byte.toString(x)); 359 try 360 { 361 realPreparedStatement.setByte(parameterIndex, x); 362 } 363 catch (SQLException s) 364 { 365 reportException(methodCall, s); 366 throw s; 367 } 368 reportReturn(methodCall); 369 } 370 371 /** 372 * @deprecated 373 */ 374 public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException 375 { 376 String methodCall = "setUnicodeStream(" + parameterIndex + ", " + x + ", " + length + ")"; 377 argTraceSet(parameterIndex, "(Unicode InputStream)", "<Unicode InputStream of length " + length + ">"); 378 try 379 { 380 realPreparedStatement.setUnicodeStream(parameterIndex, x, length); 381 } 382 catch (SQLException s) 383 { 384 reportException(methodCall, s); 385 throw s; 386 } 387 reportReturn(methodCall); 388 } 389 390 public void setShort(int parameterIndex, short x) throws SQLException 391 { 392 String methodCall = "setShort(" + parameterIndex + ", " + x + ")"; 393 argTraceSet(parameterIndex, "(short)", Short.toString(x)); 394 try 395 { 396 realPreparedStatement.setShort(parameterIndex, x); 397 } 398 catch (SQLException s) 399 { 400 reportException(methodCall, s); 401 throw s; 402 } 403 reportReturn(methodCall); 404 } 405 406 public boolean execute() throws SQLException 407 { 408 String methodCall = "execute()"; 409 String dumpedSql = dumpedSql(); 410 reportSql(dumpedSql, methodCall); 411 long tstart = System.currentTimeMillis(); 412 try 413 { 414 boolean result = realPreparedStatement.execute(); 415 reportSqlTiming(System.currentTimeMillis() - tstart, dumpedSql, methodCall); 416 return reportReturn(methodCall, result); 417 } 418 catch (SQLException s) 419 { 420 reportException(methodCall, s, dumpedSql, System.currentTimeMillis() - tstart); 421 throw s; 422 } 423 } 424 425 public void setInt(int parameterIndex, int x) throws SQLException 426 { 427 String methodCall = "setInt(" + parameterIndex + ", " + x + ")"; 428 argTraceSet(parameterIndex, "(int)", Integer.toString(x)); 429 try 430 { 431 realPreparedStatement.setInt(parameterIndex, x); 432 } 433 catch (SQLException s) 434 { 435 reportException(methodCall, s); 436 throw s; 437 } 438 reportReturn(methodCall); 439 } 440 441 public void setLong(int parameterIndex, long x) throws SQLException 442 { 443 String methodCall = "setLong(" + parameterIndex + ", " + x + ")"; 444 argTraceSet(parameterIndex, "(long)", Long.toString(x)); 445 try 446 { 447 realPreparedStatement.setLong(parameterIndex, x); 448 } 449 catch (SQLException s) 450 { 451 reportException(methodCall, s); 452 throw s; 453 } 454 reportReturn(methodCall); 455 } 456 457 public void setFloat(int parameterIndex, float x) throws SQLException 458 { 459 String methodCall = "setFloat(" + parameterIndex + ", " + x + ")"; 460 argTraceSet(parameterIndex, "(float)", Float.toString(x)); 461 try 462 { 463 realPreparedStatement.setFloat(parameterIndex, x); 464 } 465 catch (SQLException s) 466 { 467 reportException(methodCall, s); 468 throw s; 469 } 470 reportReturn(methodCall); 471 } 472 473 public void setDouble(int parameterIndex, double x) throws SQLException 474 { 475 String methodCall = "setDouble(" + parameterIndex + ", " + x + ")"; 476 argTraceSet(parameterIndex, "(double)", Double.toString(x)); 477 try 478 { 479 realPreparedStatement.setDouble(parameterIndex, x); 480 } 481 catch (SQLException s) 482 { 483 reportException(methodCall, s); 484 throw s; 485 } 486 reportReturn(methodCall); 487 } 488 489 public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException 490 { 491 String methodCall = "setBigDecimal(" + parameterIndex + ", " + x + ")"; 492 argTraceSet(parameterIndex, "(BigDecimal)", x); 493 try 494 { 495 realPreparedStatement.setBigDecimal(parameterIndex, x); 496 } 497 catch (SQLException s) 498 { 499 reportException(methodCall, s); 500 throw s; 501 } 502 reportReturn(methodCall); 503 } 504 505 public void setURL(int parameterIndex, URL x) throws SQLException 506 { 507 String methodCall = "setURL(" + parameterIndex + ", " + x + ")"; 508 509 argTraceSet(parameterIndex, "(URL)", x); 510 511 try 512 { 513 realPreparedStatement.setURL(parameterIndex, x); 514 } 515 catch (SQLException s) 516 { 517 reportException(methodCall, s); 518 throw s; 519 } 520 reportReturn(methodCall); 521 } 522 523 public void setString(int parameterIndex, String x) throws SQLException 524 { 525 String methodCall = "setString(" + parameterIndex + ", \"" + x + "\")"; 526 527 argTraceSet(parameterIndex, "(String)", rdbmsSpecifics.formatParameterObject(x)); 528 529 try 530 { 531 realPreparedStatement.setString(parameterIndex, x); 532 } 533 catch (SQLException s) 534 { 535 reportException(methodCall, s); 536 throw s; 537 } 538 reportReturn(methodCall); 539 } 540 541 public void setBytes(int parameterIndex, byte[] x) throws SQLException 542 { 543 String methodCall = "setBytes(" + parameterIndex + ", " + x + ")"; 544 argTraceSet(parameterIndex, "(byte[])", "<byte[]>"); 545 try 546 { 547 realPreparedStatement.setBytes(parameterIndex, x); 548 } 549 catch (SQLException s) 550 { 551 reportException(methodCall, s); 552 throw s; 553 } 554 reportReturn(methodCall); 555 } 556 557 public void setDate(int parameterIndex, Date x) throws SQLException 558 { 559 String methodCall = "setDate(" + parameterIndex + ", " + x + ")"; 560 argTraceSet(parameterIndex, "(Date)", rdbmsSpecifics.formatParameterObject(x)); 561 try 562 { 563 realPreparedStatement.setDate(parameterIndex, x); 564 } 565 catch (SQLException s) 566 { 567 reportException(methodCall, s); 568 throw s; 569 } 570 reportReturn(methodCall); 571 } 572 573 public ParameterMetaData getParameterMetaData() throws SQLException 574 { 575 String methodCall = "getParameterMetaData()"; 576 try 577 { 578 return (ParameterMetaData) reportReturn(methodCall, realPreparedStatement.getParameterMetaData()); 579 } 580 catch (SQLException s) 581 { 582 reportException(methodCall, s); 583 throw s; 584 } 585 } 586 587 public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException 588 { 589 String methodCall = "setDate(" + parameterIndex + ", " + x + ", " + cal + ")"; 590 argTraceSet(parameterIndex, "(Date)", rdbmsSpecifics.formatParameterObject(x)); 591 592 try 593 { 594 realPreparedStatement.setDate(parameterIndex, x, cal); 595 } 596 catch (SQLException s) 597 { 598 reportException(methodCall, s); 599 throw s; 600 } 601 reportReturn(methodCall); 602 } 603 604 public ResultSet executeQuery() throws SQLException 605 { 606 String methodCall = "executeQuery()"; 607 String dumpedSql = dumpedSql(); 608 reportSql(dumpedSql, methodCall); 609 long tstart = System.currentTimeMillis(); 610 try 611 { 612 ResultSet r = realPreparedStatement.executeQuery(); 613 reportSqlTiming(System.currentTimeMillis() - tstart, dumpedSql, methodCall); 614 ResultSetSpy rsp = new ResultSetSpy(this, r); 615 return (ResultSet) reportReturn(methodCall, rsp); 616 } 617 catch (SQLException s) 618 { 619 reportException(methodCall, s, dumpedSql, System.currentTimeMillis() - tstart); 620 throw s; 621 } 622 } 623 624 private String getTypeHelp(Object x) 625 { 626 if (x==null) 627 { 628 return "(null)"; 629 } 630 else 631 { 632 return "(" + x.getClass().getName() + ")"; 633 } 634 } 635 636 public void setObject(int parameterIndex, Object x, int targetSqlType, int scale) throws SQLException 637 { 638 String methodCall = "setObject(" + parameterIndex + ", " + x + ", " + targetSqlType + ", " + scale + ")"; 639 argTraceSet(parameterIndex, getTypeHelp(x), rdbmsSpecifics.formatParameterObject(x)); 640 641 try 642 { 643 realPreparedStatement.setObject(parameterIndex, x, targetSqlType, scale); 644 } 645 catch (SQLException s) 646 { 647 reportException(methodCall, s); 648 throw s; 649 } 650 reportReturn(methodCall); 651 } 652 653 public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException 654 { 655 String methodCall = "setObject(" + parameterIndex + ", " + x + ", " + targetSqlType + ")"; 656 argTraceSet(parameterIndex, getTypeHelp(x), rdbmsSpecifics.formatParameterObject(x)); 657 try 658 { 659 realPreparedStatement.setObject(parameterIndex, x, targetSqlType); 660 } 661 catch (SQLException s) 662 { 663 reportException(methodCall, s); 664 throw s; 665 } 666 reportReturn(methodCall); 667 } 668 669 public void setObject(int parameterIndex, Object x) throws SQLException 670 { 671 String methodCall = "setObject(" + parameterIndex + ", " + x + ")"; 672 argTraceSet(parameterIndex, getTypeHelp(x), rdbmsSpecifics.formatParameterObject(x)); 673 try 674 { 675 realPreparedStatement.setObject(parameterIndex, x); 676 } 677 catch (SQLException s) 678 { 679 reportException(methodCall, s); 680 throw s; 681 } 682 reportReturn(methodCall); 683 } 684 685 public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException 686 { 687 String methodCall = "setTimestamp(" + parameterIndex + ", " + x + ")"; 688 argTraceSet(parameterIndex, "(Date)", rdbmsSpecifics.formatParameterObject(x)); 689 try 690 { 691 realPreparedStatement.setTimestamp(parameterIndex, x); 692 } 693 catch (SQLException s) 694 { 695 reportException(methodCall, s); 696 throw s; 697 } 698 reportReturn(methodCall); 699 } 700 701 public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException 702 { 703 String methodCall = "setTimestamp(" + parameterIndex + ", " + x + ", " + cal + ")"; 704 argTraceSet(parameterIndex, "(Timestamp)", rdbmsSpecifics.formatParameterObject(x)); 705 try 706 { 707 realPreparedStatement.setTimestamp(parameterIndex, x, cal); 708 } 709 catch (SQLException s) 710 { 711 reportException(methodCall, s); 712 throw s; 713 } 714 reportReturn(methodCall); 715 } 716 717 public int executeUpdate() throws SQLException 718 { 719 String methodCall = "executeUpdate()"; 720 String dumpedSql = dumpedSql(); 721 reportSql(dumpedSql, methodCall); 722 long tstart = System.currentTimeMillis(); 723 try 724 { 725 int result = realPreparedStatement.executeUpdate(); 726 reportSqlTiming(System.currentTimeMillis() - tstart, dumpedSql, methodCall); 727 return reportReturn(methodCall, result); 728 } 729 catch (SQLException s) 730 { 731 reportException(methodCall, s, dumpedSql, System.currentTimeMillis() - tstart); 732 throw s; 733 } 734 } 735 736 public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException 737 { 738 String methodCall = "setAsciiStream(" + parameterIndex + ", " + x + ", " + length + ")"; 739 argTraceSet(parameterIndex, "(Ascii InputStream)", "<Ascii InputStream of length " + x + ">"); 740 try 741 { 742 realPreparedStatement.setAsciiStream(parameterIndex, x, length); 743 } 744 catch (SQLException s) 745 { 746 reportException(methodCall, s); 747 throw s; 748 } 749 reportReturn(methodCall); 750 } 751 752 public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException 753 { 754 String methodCall = "setBinaryStream(" + parameterIndex + ", " + x + ", " + length + ")"; 755 argTraceSet(parameterIndex, "(Binary InputStream)", "<Binary InputStream of length " + length + ">"); 756 try 757 { 758 realPreparedStatement.setBinaryStream(parameterIndex, x, length); 759 } 760 catch (SQLException s) 761 { 762 reportException(methodCall, s); 763 throw s; 764 } 765 reportReturn(methodCall); 766 } 767 768 public void clearParameters() throws SQLException 769 { 770 String methodCall = "clearParameters()"; 771 772 synchronized (argTrace) 773 { 774 argTrace.clear(); 775 } 776 777 try 778 { 779 realPreparedStatement.clearParameters(); 780 } 781 catch (SQLException s) 782 { 783 reportException(methodCall, s); 784 throw s; 785 } 786 reportReturn(methodCall); 787 } 788 789 public ResultSetMetaData getMetaData() throws SQLException 790 { 791 String methodCall = "getMetaData()"; 792 try 793 { 794 return (ResultSetMetaData) reportReturn(methodCall, realPreparedStatement.getMetaData()); 795 } 796 catch (SQLException s) 797 { 798 reportException(methodCall, s); 799 throw s; 800 } 801 } 802 803 public void addBatch() throws SQLException 804 { 805 String methodCall = "addBatch()"; 806 currentBatch.add(dumpedSql()); 807 try 808 { 809 realPreparedStatement.addBatch(); 810 } 811 catch (SQLException s) 812 { 813 reportException(methodCall, s); 814 throw s; 815 } 816 reportReturn(methodCall); 817 } 818 }