001: /*
002: * Craftsman Spy.
003: * Copyright (C) 2005 Sébastien LECACHEUR
004: *
005: * This program is free software; you can redistribute it and/or modify
006: * it under the terms of the GNU General Public License as published by
007: * the Free Software Foundation; either version 2 of the License, or
008: * (at your option) any later version.
009: *
010: * This program is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013: * GNU General Public License for more details.
014: *
015: * You should have received a copy of the GNU General Public License
016: * along with this program; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
018: */
019: package craftsman.spy;
020:
021: import java.io.InputStream;
022: import java.io.Reader;
023: import java.math.BigDecimal;
024: import java.net.URL;
025: import java.sql.Array;
026: import java.sql.Blob;
027: import java.sql.Clob;
028: import java.sql.Connection;
029: import java.sql.Date;
030: import java.sql.ParameterMetaData;
031: import java.sql.PreparedStatement;
032: import java.sql.Ref;
033: import java.sql.ResultSet;
034: import java.sql.ResultSetMetaData;
035: import java.sql.SQLException;
036: import java.sql.Time;
037: import java.sql.Timestamp;
038: import java.util.ArrayList;
039: import java.util.Calendar;
040:
041: /**
042: * The classe used to represent a precompiled SQL statement.
043: *
044: * @author Sébastien LECACHEUR
045: */
046: public class SpyPreparedStatement extends SpyStatement implements
047: PreparedStatement {
048: /**
049: * The real prepared statement instance.
050: */
051: private PreparedStatement real = null;
052:
053: /**
054: * The current prepared SQL.
055: */
056: protected String preparedSql = null;
057:
058: /**
059: * The list of all the parameters.
060: */
061: private ArrayList parameters = null;
062:
063: /**
064: * Constructs a new Spy JDBC prepared statement.
065: *
066: * @param c Connection The used connection.
067: * @param pstm PreparedStatement The real JDBC prepared statement.
068: * @param sql String The prepared SQL.
069: */
070: protected SpyPreparedStatement(Connection c,
071: PreparedStatement pstm, String sql) {
072: super (c, pstm);
073: real = pstm;
074: preparedSql = sql;
075:
076: // Initializes capacity with the number of '?' in sql string
077: parameters = new ArrayList(1);
078: addParametersList();
079: }
080:
081: /**
082: * Retrieves the estimation of the number of parameters for
083: * the prepared SQL.
084: *
085: * @return int The number of parameters.
086: */
087: protected int getEstimatedParametersCount() {
088: int count = 0, index = 0;
089: while ((index = preparedSql.indexOf('?', index + 1)) != -1)
090: count++;
091:
092: return count;
093: }
094:
095: /**
096: * Adds a new parameters list for the next SQL or batch.
097: */
098: private void addParametersList() {
099: // Adds a new parameters list for the next batch
100: parameters
101: .add(new ArrayList(getEstimatedParametersCount() + 1));
102:
103: // Initializes the new parameters list
104: ArrayList nextParameters = (ArrayList) parameters
105: .get(parameters.size() - 1);
106: if (nextParameters != null) {
107: for (int i = 0; i < getEstimatedParametersCount() + 1; i++)
108: nextParameters.add(i, null);
109: } else {
110: if (log.isFatalEnabled())
111: log
112: .fatal("the next parameters list is null (current size is "
113: + parameters.size() + ")");
114: }
115: }
116:
117: /**
118: * Registers the given input parameter.
119: *
120: * @param paramIndex int The index position of the parameter.
121: * @param parameter Object The value of the parameter.
122: */
123: private void registerInputParameter(int paramIndex, Object parameter) {
124: // Check if the current parameters list is initialized
125: if (parameters.size() == 0
126: || parameters.get(parameters.size() - 1) == null) {
127: addParametersList();
128: }
129:
130: ArrayList currentParameters = (ArrayList) parameters
131: .get(parameters.size() - 1);
132: if (currentParameters != null) {
133: if (currentParameters.size() <= paramIndex) {
134: currentParameters.ensureCapacity(paramIndex + 1);
135: for (int i = currentParameters.size(); i < paramIndex; i++)
136: currentParameters.add(i, null);
137: currentParameters.add(paramIndex, parameter);
138: } else {
139: currentParameters.set(paramIndex, parameter);
140: }
141: } else {
142: if (log.isFatalEnabled())
143: log
144: .fatal("the current parameters list is null (current size is "
145: + parameters.size() + ")");
146: }
147: }
148:
149: /**
150: * Retrieves the string representation with parameters of
151: * a prepared SQL.
152: *
153: * @param index int The index of the prepared SQL.
154: * @return String The string representation with parameters
155: * of the prepared SQL.
156: */
157: private String getDisplayableSql(int index) {
158: StringBuffer displayableSql = new StringBuffer(preparedSql
159: .length());
160:
161: if (parameters != null) {
162: int i = 1, limit = 0, base = 0;
163: while ((limit = preparedSql.indexOf('?', limit)) != -1) {
164: displayableSql.append(preparedSql
165: .substring(base, limit));
166: if (((ArrayList) parameters.get(index)).get(i) instanceof String) {
167: displayableSql.append("'");
168: displayableSql.append(((ArrayList) parameters
169: .get(index)).get(i));
170: displayableSql.append("'");
171: } else if (((ArrayList) parameters.get(index)).get(i) == null) {
172: displayableSql.append("NULL");
173: } else {
174: displayableSql.append(((ArrayList) parameters
175: .get(index)).get(i));
176: }
177: i++;
178: limit++;
179: base = limit;
180: }
181:
182: if (base < preparedSql.length()) {
183: displayableSql.append(preparedSql.substring(base));
184: }
185: }
186:
187: return displayableSql.toString();
188: }
189:
190: /**
191: * Logs the success of a SQL execution.
192: *
193: * @param result String The string representation of the SQL result.
194: * @param time long The execution time.
195: */
196: private void logSql(String result, long time) {
197: if (log.isInfoEnabled()) {
198: for (int i = 0; i < parameters.size(); i++) {
199: log.info(getId() + ":" + getDisplayableSql(i) + " => "
200: + result + " (" + (time) + " ms)");
201: }
202: }
203: }
204:
205: /**
206: * Logs the failure of a SQL execution.
207: *
208: * @param e SQLException The throwed SQL exception by the execution.
209: * @param time long The execution time.
210: */
211: private void logSql(SQLException e, long time) {
212: if (log.isErrorEnabled()) {
213: for (int i = 0; i < parameters.size(); i++) {
214: log.error(getId() + ":" + getDisplayableSql(i)
215: + " => state=" + e.getSQLState() + ",code="
216: + e.getErrorCode() + " (" + (time) + " ms)", e);
217: }
218: }
219: }
220:
221: /**
222: * @see PreparedStatement#executeUpdate()
223: */
224: public int executeUpdate() throws SQLException {
225: long end, start = System.currentTimeMillis();
226: int result = 0;
227:
228: try {
229: result = real.executeUpdate();
230: end = System.currentTimeMillis();
231: logSql(String.valueOf(result), end - start);
232: } catch (SQLException e) {
233: end = System.currentTimeMillis();
234: logSql(e, end - start);
235: throw e;
236: }
237:
238: return result;
239: }
240:
241: /**
242: * @see PreparedStatement#addBatch()
243: */
244: public void addBatch() throws SQLException {
245: addParametersList();
246: real.addBatch();
247: }
248:
249: /**
250: * @see PreparedStatement#clearParameters()
251: */
252: public void clearParameters() throws SQLException {
253: for (int i = 0; i < parameters.size(); i++)
254: parameters.remove(i);
255: real.clearParameters();
256: }
257:
258: /**
259: * @see PreparedStatement#execute()
260: */
261: public boolean execute() throws SQLException {
262: long end, start = System.currentTimeMillis();
263: boolean result = false;
264:
265: try {
266: result = real.execute();
267: end = System.currentTimeMillis();
268: logSql(String.valueOf(result), end - start);
269: } catch (SQLException e) {
270: end = System.currentTimeMillis();
271: logSql(e, end - start);
272: throw e;
273: }
274:
275: return result;
276: }
277:
278: /**
279: * @see PreparedStatement#setByte(int, byte)
280: */
281: public void setByte(int parameterIndex, byte x) throws SQLException {
282: registerInputParameter(parameterIndex, new Byte(x).toString());
283: real.setByte(parameterIndex, x);
284: }
285:
286: /**
287: * @see PreparedStatement#setDouble(int, double)
288: */
289: public void setDouble(int parameterIndex, double x)
290: throws SQLException {
291: registerInputParameter(parameterIndex, new Double(x));
292: real.setDouble(parameterIndex, x);
293: }
294:
295: /**
296: * @see PreparedStatement#setFloat(int, float)
297: */
298: public void setFloat(int parameterIndex, float x)
299: throws SQLException {
300: registerInputParameter(parameterIndex, new Float(x));
301: real.setFloat(parameterIndex, x);
302: }
303:
304: /**
305: * @see PreparedStatement#setInt(int, int)
306: */
307: public void setInt(int parameterIndex, int x) throws SQLException {
308: registerInputParameter(parameterIndex, new Integer(x));
309: real.setInt(parameterIndex, x);
310: }
311:
312: /**
313: * @see PreparedStatement#setNull(int, int)
314: */
315: public void setNull(int parameterIndex, int sqlType)
316: throws SQLException {
317: registerInputParameter(parameterIndex, null);
318: real.setNull(parameterIndex, sqlType);
319: }
320:
321: /**
322: * @see PreparedStatement#setLong(int, long)
323: */
324: public void setLong(int parameterIndex, long x) throws SQLException {
325: registerInputParameter(parameterIndex, new Long(x));
326: real.setLong(parameterIndex, x);
327: }
328:
329: /**
330: * @see PreparedStatement#setShort(int, short)
331: */
332: public void setShort(int parameterIndex, short x)
333: throws SQLException {
334: registerInputParameter(parameterIndex, new Short(x));
335: real.setShort(parameterIndex, x);
336: }
337:
338: /**
339: * @see PreparedStatement#setBoolean(int, boolean)
340: */
341: public void setBoolean(int parameterIndex, boolean x)
342: throws SQLException {
343: registerInputParameter(parameterIndex, Boolean.valueOf(x)
344: .toString());
345: real.setBoolean(parameterIndex, x);
346: }
347:
348: /**
349: * @see PreparedStatement#setBytes(int, byte[])
350: */
351: public void setBytes(int parameterIndex, byte[] x)
352: throws SQLException {
353: registerInputParameter(parameterIndex, x.toString());
354: real.setBytes(parameterIndex, x);
355: }
356:
357: /**
358: * @see PreparedStatement#setAsciiStream(int, java.io.InputStream, int)
359: */
360: public void setAsciiStream(int parameterIndex, InputStream x,
361: int length) throws SQLException {
362: registerInputParameter(parameterIndex, x.toString());
363: real.setAsciiStream(parameterIndex, x, length);
364: }
365:
366: /**
367: * @see PreparedStatement#setBinaryStream(int, java.io.InputStream, int)
368: */
369: public void setBinaryStream(int parameterIndex, InputStream x,
370: int length) throws SQLException {
371: registerInputParameter(parameterIndex, x.toString());
372: real.setBinaryStream(parameterIndex, x, length);
373: }
374:
375: /**
376: * @see PreparedStatement#setUnicodeStream(int, java.io.InputStream, int)
377: */
378: public void setUnicodeStream(int parameterIndex, InputStream x,
379: int length) throws SQLException {
380: registerInputParameter(parameterIndex, x.toString());
381: real.setUnicodeStream(parameterIndex, x, length);
382: }
383:
384: /**
385: * @see PreparedStatement#setCharacterStream(int, java.io.Reader, int)
386: */
387: public void setCharacterStream(int parameterIndex, Reader reader,
388: int length) throws SQLException {
389: registerInputParameter(parameterIndex, reader.toString());
390: real.setCharacterStream(parameterIndex, reader, length);
391: }
392:
393: /**
394: * @see PreparedStatement#setObject(int, Object)
395: */
396: public void setObject(int parameterIndex, Object x)
397: throws SQLException {
398: registerInputParameter(parameterIndex, x.toString());
399: real.setObject(parameterIndex, x);
400: }
401:
402: /**
403: * @see PreparedStatement#setObject(int, Object, int)
404: */
405: public void setObject(int parameterIndex, Object x,
406: int targetSqlType) throws SQLException {
407: registerInputParameter(parameterIndex, x.toString());
408: real.setObject(parameterIndex, x, targetSqlType);
409: }
410:
411: /**
412: * @see PreparedStatement#setObject(int, Object, int, int)
413: */
414: public void setObject(int parameterIndex, Object x,
415: int targetSqlType, int scale) throws SQLException {
416: registerInputParameter(parameterIndex, x.toString());
417: real.setObject(parameterIndex, x, targetSqlType, scale);
418: }
419:
420: /**
421: * @see PreparedStatement#setNull(int, int, String)
422: */
423: public void setNull(int paramIndex, int sqlType, String typeName)
424: throws SQLException {
425: registerInputParameter(paramIndex, null);
426: real.setNull(paramIndex, sqlType, typeName);
427: }
428:
429: /**
430: * @see PreparedStatement#setString(int, String)
431: */
432: public void setString(int parameterIndex, String x)
433: throws SQLException {
434: registerInputParameter(parameterIndex, x);
435: real.setString(parameterIndex, x);
436: }
437:
438: /**
439: * @see PreparedStatement#setBigDecimal(int, java.math.BigDecimal)
440: */
441: public void setBigDecimal(int parameterIndex, BigDecimal x)
442: throws SQLException {
443: registerInputParameter(parameterIndex, x.toString());
444: real.setBigDecimal(parameterIndex, x);
445: }
446:
447: /**
448: * @see PreparedStatement#setURL(int, java.net.URL)
449: */
450: public void setURL(int parameterIndex, URL x) throws SQLException {
451: registerInputParameter(parameterIndex, x.toString());
452: real.setURL(parameterIndex, x);
453: }
454:
455: /**
456: * @see PreparedStatement#setArray(int, Array)
457: */
458: public void setArray(int i, Array x) throws SQLException {
459: registerInputParameter(i, x.toString());
460: real.setArray(i, x);
461: }
462:
463: /**
464: * @see PreparedStatement#setBlob(int, Blob)
465: */
466: public void setBlob(int i, Blob x) throws SQLException {
467: registerInputParameter(i, x.toString());
468: real.setBlob(i, x);
469: }
470:
471: /**
472: * @see PreparedStatement#setClob(int, Clob)
473: */
474: public void setClob(int i, Clob x) throws SQLException {
475: registerInputParameter(i, x.toString());
476: real.setClob(i, x);
477: }
478:
479: /**
480: * @see PreparedStatement#setDate(int, Date)
481: */
482: public void setDate(int parameterIndex, Date x) throws SQLException {
483: registerInputParameter(parameterIndex, x.toString());
484: real.setDate(parameterIndex, x);
485: }
486:
487: /**
488: * @see PreparedStatement#getParameterMetaData()
489: */
490: public ParameterMetaData getParameterMetaData() throws SQLException {
491: return real.getParameterMetaData();
492: }
493:
494: /**
495: * @see PreparedStatement#setRef(int, Ref)
496: */
497: public void setRef(int i, Ref x) throws SQLException {
498: registerInputParameter(i, x.toString());
499: real.setRef(i, x);
500: }
501:
502: /**
503: * @see PreparedStatement#executeQuery()
504: */
505: public ResultSet executeQuery() throws SQLException {
506: long end, start = System.currentTimeMillis();
507: ResultSet result = null;
508:
509: try {
510: result = new SpyResultSet(getConnection(), this , real
511: .executeQuery());
512: end = System.currentTimeMillis();
513: logSql("...", end - start);
514: } catch (SQLException e) {
515: end = System.currentTimeMillis();
516: logSql(e, end - start);
517: throw e;
518: }
519:
520: return result;
521: }
522:
523: /**
524: * @see PreparedStatement#getMetaData()
525: */
526: public ResultSetMetaData getMetaData() throws SQLException {
527: return real.getMetaData();
528: }
529:
530: /**
531: * @see PreparedStatement#setTime(int, Time)
532: */
533: public void setTime(int parameterIndex, Time x) throws SQLException {
534: registerInputParameter(parameterIndex, x.toString());
535: real.setTime(parameterIndex, x);
536: }
537:
538: /**
539: * @see PreparedStatement#setTimestamp(int, Timestamp)
540: */
541: public void setTimestamp(int parameterIndex, Timestamp x)
542: throws SQLException {
543: registerInputParameter(parameterIndex, x.toString());
544: real.setTimestamp(parameterIndex, x);
545: }
546:
547: /**
548: * @see PreparedStatement#setDate(int, Date, Calendar)
549: */
550: public void setDate(int parameterIndex, Date x, Calendar cal)
551: throws SQLException {
552: registerInputParameter(parameterIndex, x.toString());
553: real.setDate(parameterIndex, x, cal);
554: }
555:
556: /**
557: * @see PreparedStatement#setTime(int, Time, Calendar)
558: */
559: public void setTime(int parameterIndex, Time x, Calendar cal)
560: throws SQLException {
561: registerInputParameter(parameterIndex, x.toString());
562: real.setTime(parameterIndex, x, cal);
563: }
564:
565: /**
566: * @see PreparedStatement#setTimestamp(int, Timestamp, Calendar)
567: */
568: public void setTimestamp(int parameterIndex, Timestamp x,
569: Calendar cal) throws SQLException {
570: registerInputParameter(parameterIndex, x.toString());
571: real.setTimestamp(parameterIndex, x, cal);
572: }
573:
574: /**
575: * @see java.sql.Statement#executeBatch()
576: */
577: public int[] executeBatch() throws SQLException {
578: // Adds all the prepared statements in the logged batches
579: for (int i = 0; i < parameters.size() - 1; i++) {
580: batch.add(getDisplayableSql(i));
581: }
582:
583: // Executes (and logs) all the batches
584: return super.executeBatch();
585: }
586: }
|