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    }