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 }