/*
* Craftsman Spy.
* Copyright (C) 2005 S�bastien LECACHEUR
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package craftsman.spy;
import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.Date;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
/**
* The classe used to represent a precompiled SQL statement.
*
* @author S�bastien LECACHEUR
*/
public class SpyPreparedStatement extends SpyStatement implements PreparedStatement {
/**
* The real prepared statement instance.
*/
private PreparedStatement real = null;
/**
* The current prepared SQL.
*/
protected String preparedSql = null;
/**
* The list of all the parameters.
*/
private ArrayList parameters = null;
/**
* Constructs a new Spy JDBC prepared statement.
*
* @param c Connection The used connection.
* @param pstm PreparedStatement The real JDBC prepared statement.
* @param sql String The prepared SQL.
*/
protected SpyPreparedStatement ( Connection c, PreparedStatement pstm, String sql) {
super ( c, pstm);
real = pstm;
preparedSql = sql;
// Initializes capacity with the number of '?' in sql string
parameters = new ArrayList(1);
addParametersList();
}
/**
* Retrieves the estimation of the number of parameters for
* the prepared SQL.
*
* @return int The number of parameters.
*/
protected int getEstimatedParametersCount() {
int count = 0, index = 0;
while ( (index = preparedSql.indexOf('?', index+1)) != -1) count++;
return count;
}
/**
* Adds a new parameters list for the next SQL or batch.
*/
private void addParametersList() {
// Adds a new parameters list for the next batch
parameters.add(new ArrayList(getEstimatedParametersCount()+1));
// Initializes the new parameters list
ArrayList nextParameters = (ArrayList)parameters.get(parameters.size()-1);
if ( nextParameters!=null) {
for (int i = 0; i < getEstimatedParametersCount()+1; i++) nextParameters.add(i,null);
} else {
if ( log.isFatalEnabled()) log.fatal("the next parameters list is null (current size is "+parameters.size()+")");
}
}
/**
* Registers the given input parameter.
*
* @param paramIndex int The index position of the parameter.
* @param parameter Object The value of the parameter.
*/
private void registerInputParameter (int paramIndex, Object parameter) {
// Check if the current parameters list is initialized
if (parameters.size()==0 || parameters.get(parameters.size()-1)==null) {
addParametersList();
}
ArrayList currentParameters = (ArrayList)parameters.get(parameters.size()-1);
if ( currentParameters!=null) {
if ( currentParameters.size()<=paramIndex) {
currentParameters.ensureCapacity(paramIndex+1);
for ( int i = currentParameters.size(); i < paramIndex; i++) currentParameters.add(i,null);
currentParameters.add(paramIndex,parameter);
} else {
currentParameters.set(paramIndex,parameter);
}
} else {
if ( log.isFatalEnabled()) log.fatal("the current parameters list is null (current size is "+parameters.size()+")");
}
}
/**
* Retrieves the string representation with parameters of
* a prepared SQL.
*
* @param index int The index of the prepared SQL.
* @return String The string representation with parameters
* of the prepared SQL.
*/
private String getDisplayableSql(int index) {
StringBuffer displayableSql = new StringBuffer(preparedSql.length());
if ( parameters!=null) {
int i = 1, limit = 0, base = 0;
while ((limit = preparedSql.indexOf('?',limit)) != -1) {
displayableSql.append(preparedSql.substring(base,limit));
if ( ((ArrayList)parameters.get(index)).get(i) instanceof String ) {
displayableSql.append("'");
displayableSql.append(((ArrayList)parameters.get(index)).get(i));
displayableSql.append("'");
} else if ( ((ArrayList)parameters.get(index)).get(i)==null ) {
displayableSql.append("NULL");
} else {
displayableSql.append(((ArrayList)parameters.get(index)).get(i));
}
i++;
limit++;
base = limit;
}
if (base < preparedSql.length()) {
displayableSql.append(preparedSql.substring(base));
}
}
return displayableSql.toString();
}
/**
* Logs the success of a SQL execution.
*
* @param result String The string representation of the SQL result.
* @param time long The execution time.
*/
private void logSql(String result, long time) {
if ( log.isInfoEnabled()) {
for (int i = 0; i < parameters.size(); i++) {
log.info(getId()+":"+getDisplayableSql(i)+" => "+result+" ("+(time)+" ms)");
}
}
}
/**
* Logs the failure of a SQL execution.
*
* @param e SQLException The throwed SQL exception by the execution.
* @param time long The execution time.
*/
private void logSql(SQLException e, long time) {
if ( log.isErrorEnabled()) {
for (int i = 0; i < parameters.size(); i++) {
log.error(getId()+":"+getDisplayableSql(i)+" => state="+e.getSQLState()+",code="+e.getErrorCode()+" ("+(time)+" ms)",e);
}
}
}
/**
* @see PreparedStatement#executeUpdate()
*/
public int executeUpdate() throws SQLException {
long end, start = System.currentTimeMillis();
int result = 0;
try {
result = real.executeUpdate();
end = System.currentTimeMillis();
logSql(String.valueOf(result),end-start);
} catch ( SQLException e) {
end = System.currentTimeMillis();
logSql(e,end-start);
throw e;
}
return result;
}
/**
* @see PreparedStatement#addBatch()
*/
public void addBatch() throws SQLException {
addParametersList();
real.addBatch();
}
/**
* @see PreparedStatement#clearParameters()
*/
public void clearParameters() throws SQLException {
for ( int i = 0; i < parameters.size(); i++) parameters.remove(i);
real.clearParameters();
}
/**
* @see PreparedStatement#execute()
*/
public boolean execute() throws SQLException {
long end, start = System.currentTimeMillis();
boolean result = false;
try {
result = real.execute();
end = System.currentTimeMillis();
logSql(String.valueOf(result),end-start);
} catch ( SQLException e) {
end = System.currentTimeMillis();
logSql(e,end-start);
throw e;
}
return result;
}
/**
* @see PreparedStatement#setByte(int, byte)
*/
public void setByte(int parameterIndex, byte x) throws SQLException {
registerInputParameter(parameterIndex,new Byte(x).toString());
real.setByte(parameterIndex,x);
}
/**
* @see PreparedStatement#setDouble(int, double)
*/
public void setDouble(int parameterIndex, double x) throws SQLException {
registerInputParameter(parameterIndex,new Double(x));
real.setDouble(parameterIndex,x);
}
/**
* @see PreparedStatement#setFloat(int, float)
*/
public void setFloat(int parameterIndex, float x) throws SQLException {
registerInputParameter(parameterIndex,new Float(x));
real.setFloat(parameterIndex,x);
}
/**
* @see PreparedStatement#setInt(int, int)
*/
public void setInt(int parameterIndex, int x) throws SQLException {
registerInputParameter(parameterIndex,new Integer(x));
real.setInt(parameterIndex,x);
}
/**
* @see PreparedStatement#setNull(int, int)
*/
public void setNull(int parameterIndex, int sqlType) throws SQLException {
registerInputParameter(parameterIndex,null);
real.setNull(parameterIndex,sqlType);
}
/**
* @see PreparedStatement#setLong(int, long)
*/
public void setLong(int parameterIndex, long x) throws SQLException {
registerInputParameter(parameterIndex,new Long(x));
real.setLong(parameterIndex,x);
}
/**
* @see PreparedStatement#setShort(int, short)
*/
public void setShort(int parameterIndex, short x) throws SQLException {
registerInputParameter(parameterIndex,new Short(x));
real.setShort(parameterIndex,x);
}
/**
* @see PreparedStatement#setBoolean(int, boolean)
*/
public void setBoolean(int parameterIndex, boolean x) throws SQLException {
registerInputParameter(parameterIndex,Boolean.valueOf(x).toString());
real.setBoolean(parameterIndex,x);
}
/**
* @see PreparedStatement#setBytes(int, byte[])
*/
public void setBytes(int parameterIndex, byte[] x) throws SQLException {
registerInputParameter(parameterIndex,x.toString());
real.setBytes(parameterIndex,x);
}
/**
* @see PreparedStatement#setAsciiStream(int, java.io.InputStream, int)
*/
public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException {
registerInputParameter(parameterIndex,x.toString());
real.setAsciiStream(parameterIndex,x,length);
}
/**
* @see PreparedStatement#setBinaryStream(int, java.io.InputStream, int)
*/
public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {
registerInputParameter(parameterIndex,x.toString());
real.setBinaryStream(parameterIndex,x,length);
}
/**
* @see PreparedStatement#setUnicodeStream(int, java.io.InputStream, int)
*/
public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {
registerInputParameter(parameterIndex,x.toString());
real.setUnicodeStream(parameterIndex,x,length);
}
/**
* @see PreparedStatement#setCharacterStream(int, java.io.Reader, int)
*/
public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException {
registerInputParameter(parameterIndex,reader.toString());
real.setCharacterStream(parameterIndex,reader,length);
}
/**
* @see PreparedStatement#setObject(int, Object)
*/
public void setObject(int parameterIndex, Object x) throws SQLException {
registerInputParameter(parameterIndex,x.toString());
real.setObject(parameterIndex,x);
}
/**
* @see PreparedStatement#setObject(int, Object, int)
*/
public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
registerInputParameter(parameterIndex,x.toString());
real.setObject(parameterIndex,x,targetSqlType);
}
/**
* @see PreparedStatement#setObject(int, Object, int, int)
*/
public void setObject(int parameterIndex, Object x, int targetSqlType, int scale) throws SQLException {
registerInputParameter(parameterIndex,x.toString());
real.setObject(parameterIndex,x,targetSqlType,scale);
}
/**
* @see PreparedStatement#setNull(int, int, String)
*/
public void setNull(int paramIndex, int sqlType, String typeName) throws SQLException {
registerInputParameter(paramIndex,null);
real.setNull(paramIndex,sqlType,typeName);
}
/**
* @see PreparedStatement#setString(int, String)
*/
public void setString(int parameterIndex, String x) throws SQLException {
registerInputParameter(parameterIndex,x);
real.setString(parameterIndex,x);
}
/**
* @see PreparedStatement#setBigDecimal(int, java.math.BigDecimal)
*/
public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
registerInputParameter(parameterIndex,x.toString());
real.setBigDecimal(parameterIndex,x);
}
/**
* @see PreparedStatement#setURL(int, java.net.URL)
*/
public void setURL(int parameterIndex, URL x) throws SQLException {
registerInputParameter(parameterIndex,x.toString());
real.setURL(parameterIndex,x);
}
/**
* @see PreparedStatement#setArray(int, Array)
*/
public void setArray(int i, Array x) throws SQLException {
registerInputParameter(i,x.toString());
real.setArray(i,x);
}
/**
* @see PreparedStatement#setBlob(int, Blob)
*/
public void setBlob(int i, Blob x) throws SQLException {
registerInputParameter(i,x.toString());
real.setBlob(i,x);
}
/**
* @see PreparedStatement#setClob(int, Clob)
*/
public void setClob(int i, Clob x) throws SQLException {
registerInputParameter(i,x.toString());
real.setClob(i,x);
}
/**
* @see PreparedStatement#setDate(int, Date)
*/
public void setDate(int parameterIndex, Date x) throws SQLException {
registerInputParameter(parameterIndex,x.toString());
real.setDate(parameterIndex,x);
}
/**
* @see PreparedStatement#getParameterMetaData()
*/
public ParameterMetaData getParameterMetaData() throws SQLException {
return real.getParameterMetaData();
}
/**
* @see PreparedStatement#setRef(int, Ref)
*/
public void setRef(int i, Ref x) throws SQLException {
registerInputParameter(i,x.toString());
real.setRef(i,x);
}
/**
* @see PreparedStatement#executeQuery()
*/
public ResultSet executeQuery() throws SQLException {
long end, start = System.currentTimeMillis();
ResultSet result = null;
try {
result = new SpyResultSet ( getConnection(), this, real.executeQuery());
end = System.currentTimeMillis();
logSql("...",end-start);
} catch ( SQLException e) {
end = System.currentTimeMillis();
logSql(e,end-start);
throw e;
}
return result;
}
/**
* @see PreparedStatement#getMetaData()
*/
public ResultSetMetaData getMetaData() throws SQLException {
return real.getMetaData();
}
/**
* @see PreparedStatement#setTime(int, Time)
*/
public void setTime(int parameterIndex, Time x) throws SQLException {
registerInputParameter(parameterIndex,x.toString());
real.setTime(parameterIndex,x);
}
/**
* @see PreparedStatement#setTimestamp(int, Timestamp)
*/
public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
registerInputParameter(parameterIndex,x.toString());
real.setTimestamp(parameterIndex,x);
}
/**
* @see PreparedStatement#setDate(int, Date, Calendar)
*/
public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
registerInputParameter(parameterIndex,x.toString());
real.setDate(parameterIndex,x,cal);
}
/**
* @see PreparedStatement#setTime(int, Time, Calendar)
*/
public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
registerInputParameter(parameterIndex,x.toString());
real.setTime(parameterIndex,x,cal);
}
/**
* @see PreparedStatement#setTimestamp(int, Timestamp, Calendar)
*/
public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {
registerInputParameter(parameterIndex,x.toString());
real.setTimestamp(parameterIndex,x,cal);
}
/**
* @see java.sql.Statement#executeBatch()
*/
public int[] executeBatch() throws SQLException {
// Adds all the prepared statements in the logged batches
for (int i = 0; i < parameters.size() - 1; i++) {
batch.add(getDisplayableSql(i));
}
// Executes (and logs) all the batches
return super.executeBatch();
}
}