/FileExtraction/Java_unseen/dlitz_resin/modules/resin/src/com/caucho/sql/spy/SpyConnection.java
/*
 * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source 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.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.sql.spy;

import com.caucho.sql.DriverConfig;
import com.caucho.util.Alarm;
import com.caucho.util.L10N;

import java.sql.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.logging.*;

/**
 * Spying on a connection.
 */
public class SpyConnection implements java.sql.Connection {
  protected final static Logger log
    = Logger.getLogger(SpyConnection.class.getName());
  protected final static Logger logXA =
    Logger.getLogger(SpyConnection.class.getName() + ".XA");
  protected final static L10N L = new L10N(SpyConnection.class);

  private SpyDataSource _spyDataSource;
  
  private String _id;
  private int _stmtIdCount;

  // The underlying connection
  private Connection _conn;

  /**
   * Creates a new SpyConnection.
   */
  public SpyConnection(Connection conn, 
                       SpyDataSource spyDataSource,
                       DriverConfig driver)
  {
    _spyDataSource = spyDataSource;
    _conn = conn;
    _id = _spyDataSource.createConnectionId(driver);
  }

  /**
   * Creates a new SpyConnection.
   */
  public SpyConnection(Connection conn,
                       SpyDataSource spyDataSource,
                       String id)
  {
    _spyDataSource = spyDataSource;
    _conn = conn;
    _id = id;
  }

  /**
   * Returns the underlying connection.
   */
  public Connection getConnection()
  {
    return _conn;
  }

  /**
   * Returns the id.
   */
  public String getId()
  {
    return _id;
  }

  /**
   * Returns a new statement id.
   */
  public String createStatementId()
  {
    return getId() + "." + _stmtIdCount++;
  }

  /**
   * JDBC api to return the connection's catalog.
   *
   * @return the JDBC catalog.
   */
  public String getCatalog()
    throws SQLException
  {
    try {
      String catalog = _conn.getCatalog();

      log.fine(getId() + ":getCatalog() -> " + catalog);
      
      return catalog;
    } catch (SQLException e) {
      log.fine(getId() + ":exn-getCatalog(" + e + ")");
      
      throw e;
    }
  }

  /**
   * Sets the JDBC catalog.
   */
  public void setCatalog(String catalog)
    throws SQLException
  {
    try {
      log.fine(getId() + ":setCatalog(" + catalog + ")");
    
      _conn.setCatalog(catalog);
    } catch (SQLException e) {
      log.fine(getId() + ":exn-setCatalog(" + e + ")");
      throw e;
    }
  }

  /**
   * Gets the connection's metadata.
   */
  public DatabaseMetaData getMetaData()
    throws SQLException
  {
    try {
      DatabaseMetaData metaData = _conn.getMetaData();

      // Avoid logging result of metaData.toString() here
      // because it may not be overloaded. The default
      // toString() result contains a hex representation
      // of the memory location that can't be matched in a
      // regression test.

      String ident = metaData.getClass().getName();

      log.fine(getId() + ":getMetaData() -> " + ident);

      return metaData;
    } catch (SQLException e) {
      log.fine(getId() + ":exn-getMetaData(" + e + ")");
      throw e;
    }
  }

  /**
   * Returns the connection's type map.
   */
  public Map<String,Class<?>> getTypeMap()
    throws SQLException
  {
    try {
      Map<String,Class<?>> map = _conn.getTypeMap();

      log.fine(getId() + ":getTypeMap() -> " + map);
      
      return map;
    } catch (SQLException e) {
      log.fine(getId() + ":exn-getTypeMap(" + e + ")");
      throw e;
    }
  }

  /**
   * Sets the connection's type map.
   */
  public void setTypeMap(Map<String,Class<?>> map)
    throws SQLException
  {
    try {
      log.fine(getId() + ":setTypeMap(" + map + ")");
      
      _conn.setTypeMap(map);
    } catch (SQLException e) {
      log.fine(getId() + ":exn-setTypeMap(" + e + ")");

      throw e;
    }
  }

  /**
   * Calls the nativeSQL method for the connection.
   */
  public String nativeSQL(String sql)
    throws SQLException
  {
    try {
      String nativeSQL = _conn.nativeSQL(sql);

      log.fine(getId() + ":nativeSQL() -> " + nativeSQL);
      
      return nativeSQL;
    } catch (SQLException e) {
      log.fine(getId() + ":exn-nativeSQL(" + e + ")");

      throw e;
    }
  }

  public int getTransactionIsolation()
    throws SQLException
  {
    try {
      int isolation = _conn.getTransactionIsolation();

      log.fine(getId() + ":getTransactionIsolation() -> " + isolation);
      
      return isolation;
    } catch (SQLException e) {
      log.fine(getId() + ":exn-getTransactionIsolation(" + e + ")");

      throw e;
    }
  }

  public void setTransactionIsolation(int isolation)
    throws SQLException
  {
    try {
      log.fine(getId() + ":setTransactionIsolation(" + isolation + ")");
      
      _conn.setTransactionIsolation(isolation);
    } catch (SQLException e) {
      log.fine(getId() + ":exn-setTransactionIsolation(" + e + ")");

      throw e;
    }
  }

  public SQLWarning getWarnings()
    throws SQLException
  {
    try {
      SQLWarning warning = _conn.getWarnings();
      
      log.fine(getId() + ":getWarnings() -> " + warning);
      
      return warning;
    } catch (SQLException e) {
      log.fine(getId() + ":exn-getWarnings(" + e + ")");

      throw e;
    }
  }

  public void clearWarnings()
    throws SQLException
  {
    try {
      log.fine(getId() + ":clearWarnings()");

      _conn.clearWarnings();
    } catch (SQLException e) {
      log.fine(getId() + ":exn-clearWarnings(" + e + ")");

      throw e;
    }
  }

  public void setReadOnly(boolean readOnly)
    throws SQLException
  {
    try {
      log.fine(getId() + ":setReadOnly(" + readOnly + ")");
      
      _conn.setReadOnly(readOnly);
    } catch (SQLException e) {
      log.fine(getId() + ":exn-setReadOnly(" + e + ")");

      throw e;
    }
  }

  public boolean isReadOnly()
    throws SQLException
  {
    try {
      boolean isReadOnly = _conn.isReadOnly();

      log.fine(getId() + "isReadOnly() -> " + isReadOnly);

      return isReadOnly;

    } catch (SQLException e) {
      log.fine(getId() + ":exn-isReadOnly(" + e + ")");

      throw e;
    }
  }

  /**
   * JDBC api to create a new statement.  Any SQL exception thrown here
   * will make the connection invalid, i.e. it can't be put back into
   * the pool.
   *
   * @return a new JDBC statement.
   */
  public Statement createStatement()
    throws SQLException
  {
    try {
      String stmtId = null;

      if (log.isLoggable(Level.FINE)) {
        stmtId = createStatementId();
        log.fine(stmtId + ":createStatement()");
      }
      
      Statement stmt;
      
      stmt = _conn.createStatement();
      
      return new SpyStatement(stmtId, this, stmt);
    } catch (SQLException e) {
      log.fine(getId() + ":exn-createStatement(" + e + ")");
      throw e;
    }
  }

  /**
   * JDBC api to create a new statement.  Any SQL exception thrown here
   * will make the connection invalid, i.e. it can't be put back into
   * the pool.
   *
   * @return a new JDBC statement.
   */
  public Statement createStatement(int resultSetType, int resultSetConcurrency)
    throws SQLException
  {
    try {
      String stmtId = null;

      if (log.isLoggable(Level.FINE)) {
        stmtId = createStatementId();
      
        log.fine(stmtId + ":createStatement(type=" + resultSetType +
                 ",concurrency=" + resultSetConcurrency + ")");
      }
      
      Statement stmt;
      
      stmt = _conn.createStatement(resultSetType, resultSetConcurrency);
      
      return new SpyStatement(stmtId, this, stmt);
    } catch (SQLException e) {
      log.fine(getId() + ":exn-createStatement(" + e + ")");
      throw e;
    }
  }
  
  public Statement createStatement(int resultSetType,
                                   int resultSetConcurrency,
                                   int resultSetHoldability)
    throws SQLException
  {
    try {
      String stmtId = null;

      if (log.isLoggable(Level.FINE)) {
        stmtId = createStatementId();
      
        log.fine(stmtId + ":createStatement(type=" + resultSetType +
                 ",concurrency=" + resultSetConcurrency +
                 ",holdability=" + resultSetHoldability + ")");
      }
      
      Statement stmt;
      
      stmt = _conn.createStatement(resultSetType,
                                   resultSetConcurrency,
                                   resultSetHoldability);
      
      return new SpyStatement(stmtId, this, stmt);
    } catch (SQLException e) {
      log.fine(getId() + ":exn-createStatement(" + e + ")");
      throw e;
    }
  }


  public PreparedStatement prepareStatement(String sql)
    throws SQLException
  {
    try {
      String stmtId = null;

      if (log.isLoggable(Level.FINE)) {
        stmtId = createStatementId();
        log.fine(stmtId + ":prepareStatement(" + sql + ")");
      }
      
      PreparedStatement stmt;
      
      stmt = _conn.prepareStatement(sql);
      
      return new SpyPreparedStatement(stmtId, this, stmt, sql);
    } catch (SQLException e) {
      log.fine(getId() + ":exn-prepareStatement(" + e + ")");

      throw e;
    }
  }
  
  public PreparedStatement prepareStatement(String sql,
                                            int resultSetType)
    throws SQLException
  {
    try {
      String stmtId = null;

      if (log.isLoggable(Level.FINE)) {
        stmtId = createStatementId();
        log.fine(stmtId + ":prepareStatement(" + sql + ",type=" + resultSetType + ")");
      }
      
      PreparedStatement stmt;
      
      stmt = _conn.prepareStatement(sql, resultSetType);
      
      return new SpyPreparedStatement(stmtId, this, stmt, sql);
    } catch (SQLException e) {
      log.fine(getId() + ":exn-prepareStatement(" + e + ")");

      throw e;
    }
  }

  public PreparedStatement prepareStatement(String sql, int resultSetType,
                                            int resultSetConcurrency)
    throws SQLException
  {
    try {
      String stmtId = null;

      if (log.isLoggable(Level.FINE)) {
        stmtId = createStatementId();

        log.fine(stmtId + ":prepareStatement(" + sql + ",type=" + resultSetType +
                 ",concurrency=" + resultSetConcurrency + ")");
      }
      
      PreparedStatement stmt;
      
      stmt = _conn.prepareStatement(sql, resultSetType, resultSetConcurrency);
      
      return new SpyPreparedStatement(stmtId, this, stmt, sql);
    } catch (SQLException e) {
      log.fine(getId() + ":exn-prepareStatement(" + e + ")");

      throw e;
    }
  }

  public PreparedStatement prepareStatement(String sql,
                                            int resultSetType,
                                            int resultSetConcurrency,
                                            int resultSetHoldability)
    throws SQLException
  {
    try {
      if (log.isLoggable(Level.FINE))
        log.fine(L.l(
          "{0}:prepareStatement({1}, resultSetType={2}, resultSetConcurrency={3}, resultSetHoldability={4})",
          getId(),
          sql,
          resultSetType,
          resultSetConcurrency,
          resultSetHoldability));

      PreparedStatement stmt;

      stmt = _conn.prepareStatement(sql,
                                    resultSetType,
                                    resultSetConcurrency,
                                    resultSetHoldability);

      return stmt;
    } catch (SQLException e) {
      log.fine(getId() + ":exn-prepareStatement(" + e + ")");

      throw e;
    }
  }

  public PreparedStatement prepareStatement(String sql,
                                            int []columnIndexes)
    throws SQLException
  {
    try {
      if (log.isLoggable(Level.FINE)) {
        List<Integer> list = new ArrayList<Integer>();
        for (int i : columnIndexes)
          list.add(i);

        log.fine(L.l("{0}:prepareStatement({1}, columnIndexes={2})", getId(), sql, list));
      }

      PreparedStatement stmt;

      stmt = _conn.prepareStatement(sql, columnIndexes);

      return stmt;
    } catch (SQLException e) {
      log.fine(getId() + ":exn-prepareStatement(" + e + ")");

      throw e;
    }
  }
  
  public PreparedStatement prepareStatement(String sql,
                                            String []columnNames)
    throws SQLException
  {
    try {
      if (log.isLoggable(Level.FINE)) {
        log.fine(L.l("{0}:prepareStatement({1}, columnNames={2})",
                     getId(),
                     sql,
                     Arrays.asList(columnNames)));
      }

      PreparedStatement stmt;

      stmt = _conn.prepareStatement(sql, columnNames);

      return stmt;
    } catch (SQLException e) {
      log.fine(getId() + ":exn-prepareStatement(" + e + ")");

      throw e;
    }
  }

  public CallableStatement prepareCall(String sql)
    throws SQLException
  {
    try {
      if (log.isLoggable(Level.FINE))
        log.fine(getId() + ":prepareCall(" + sql + ")");
      
      CallableStatement stmt;
      
      stmt = _conn.prepareCall(sql);
      
      return stmt;
    } catch (SQLException e) {
      log.fine(getId() + ":exn-prepareCall(" + e + ")");

      throw e;
    }
  }

  public CallableStatement prepareCall(String sql, int resultSetType,
                                       int resultSetConcurrency)
    throws SQLException
  {
    try {
      if (log.isLoggable(Level.FINE))
        log.fine(getId() + ":prepareCall(" + sql + ",type=" + resultSetType +
              ",concurrency=" + resultSetConcurrency + ")");
      
      CallableStatement stmt;
      
      stmt = _conn.prepareCall(sql);
      
      return stmt;
    } catch (SQLException e) {
      log.fine(getId() + ":exn-prepareCall(" + e + ")");

      throw e;
    }
  }

  public CallableStatement prepareCall(String sql,
                                       int resultSetType,
                                       int resultSetConcurrency,
                                       int resultSetHoldability)
    throws SQLException
  {
    try {
      if (log.isLoggable(Level.FINE))
        log.fine(getId() + ":prepareCall(" + sql + ",type=" + resultSetType +
              ",concurrency=" + resultSetConcurrency + ")");

      CallableStatement stmt;

      stmt = _conn.prepareCall(sql,
                               resultSetType,
                               resultSetConcurrency,
                               resultSetHoldability);

      return stmt;
    } catch (SQLException e) {
      log.fine(getId() + ":exn-prepareCall(" + e + ")");

      throw e;
    }
  }

  @Override
  public boolean getAutoCommit()
    throws SQLException
  {
    try {
      boolean autoCommit = _conn.getAutoCommit();
      
      log.fine(getId() + ":getAutoCommit() -> " + autoCommit);
      
      return autoCommit;
    } catch (SQLException e) {
      log.fine(getId() + ":exn-getAutoCommit(" + e + ")");

      throw e;
    }
  }

  @Override
  public void setAutoCommit(boolean autoCommit)
    throws SQLException
  {
    try {
      logXA.fine(getId() + ":setAutoCommit(" + autoCommit + ")");
      
      _conn.setAutoCommit(autoCommit);
    } catch (SQLException e) {
      logXA.fine(getId() + ":exn-setAutoCommit(" + e + ")");

      throw e;
    }
  }

  @Override
  public void commit()
    throws SQLException
  {
    long start = start();
    
    try {
      _conn.commit();
      
      if (logXA.isLoggable(Level.FINE))
        logXA(start, "commit()");
    } catch (SQLException e) {
      logXA(start, "exn-commit(" + e + ")");

      throw e;
    }
  }

  @Override
  public void rollback()
    throws SQLException
  {
    long start = start();
    
    try {
      _conn.rollback();
      
      if (logXA.isLoggable(Level.FINE))
        logXA(start, "rollback()");
    } catch (SQLException e) {
      logXA(start, "exn-rollback(" + e + ")");

      throw e;
    }
  }

  /**
   * Returns true if the connection is closed.
   */
  public boolean isClosed()
    throws SQLException
  {
    try {
      boolean isClosed = _conn.isClosed();

      log.fine(getId() + ":isClosed() -> " + isClosed);
      
      return isClosed;
    } catch (SQLException e) {
      log.fine(getId() + ":exn-isClosed(" + e + ")");

      throw e;
    }
  }

  /**
   * Reset the connection and return the underlying JDBC connection to
   * the pool.
   */
  @Override
  public void close() throws SQLException
  {
    long start = start();

    try {
      _conn.close();
      
      log(start, "close()");
    } catch (SQLException e) {
      log(start, "exn-close(" + e + ")");

      throw e;
    }
  }

  public void setHoldability(int hold)
    throws SQLException
  {
    log.fine(getId() + ":setHoldability(" + hold + ")");

    try {
      _conn.setHoldability(hold);
    } catch (SQLException e) {
      log.fine(getId() + ":exn-setHoldability(" + e + ")");

      throw e;
    }
  }

  public int getHoldability()
    throws SQLException
  {
    try {
      int holdability = _conn.getHoldability();

      log.fine(getId() + ":getHoldability() -> " + holdability);

      return holdability;
    } catch (SQLException e) {
      log.fine(getId() + ":exn-getHoldability(" + e + ")");

      throw e;
    }
  }

  public Savepoint setSavepoint()
    throws SQLException
  {
    log.fine(getId() + ":setSavepoint()");

    try {
      return _conn.setSavepoint();
    } catch (SQLException e) {
      log.fine(getId() + ":exn-setSavepoint(" + e + ")");

      throw e;
    }
  }

  public Savepoint setSavepoint(String name)
    throws SQLException
  {
    log.fine(getId() + ":setSavepoint(" + name + ")");

    try {
      return _conn.setSavepoint(name);
    } catch (SQLException e) {
      log.fine(getId() + ":exn-setSavepoint(" + e + ")");

      throw e;
    }
  }

  public void releaseSavepoint(Savepoint savepoint)
    throws SQLException
  {
    log.fine(getId() + ":releaseSavepoint(" + savepoint + ")");

    try {
      _conn.releaseSavepoint(savepoint);
    } catch (SQLException e) {
      log.fine(getId() + ":exn-releaseSavepoint(" + e + ")");

      throw e;
    }
  }

  public void rollback(Savepoint savepoint)
    throws SQLException
  {
    log.fine(getId() + ":rollback(" + savepoint + ")");
    
    try {
      _conn.rollback(savepoint);
    } catch (SQLException e) {
      log.fine(getId() + ":exn-rollback(" + e + ")");

      throw e;
    }
  }

  public Clob createClob()
    throws SQLException
  {
    try {
      Clob clob = _conn.createClob();

      log.fine(getId() + ":createClob() -> " + clob);

      return clob;
    } catch (SQLException e) {
      log.fine(getId() + ":exn-createClob(" + e + ")");

      throw e;
    }
  }

  public Blob createBlob()
    throws SQLException
  {
    try {
      Blob blob = _conn.createBlob();

      log.fine(getId() + ":createBlob() -> " + blob);

      return blob;
    } catch (SQLException e) {
      log.fine(getId() + ":exn-createBlob(" + e + ")");

      throw e;
    }
  }

  public NClob createNClob()
    throws SQLException
  {
    try {
      NClob nclob = _conn.createNClob();

      log.fine(getId() + ":createNClob() -> " + nclob);

      return nclob;
    } catch (SQLException e) {
      log.fine(getId() + ":exn-createNClob(" + e + ")");

      throw e;
    }
  }

  public SQLXML createSQLXML()
    throws SQLException
  {
    try {
      SQLXML xml = _conn.createSQLXML();

      log.fine(getId() + ":createSQLXML() -> " + xml);

      return xml;
    } catch (SQLException e) {
      log.fine(getId() + ":exn-createSQLXML(" + e + ")");

      throw e;
    }
  }

  public Array createArrayOf(String typeName, Object[] elements)
    throws SQLException
  {
    try {
      Array array = _conn.createArrayOf(typeName, elements);

      log.fine(getId()
        + ":createArrayOf(typeName="
        + typeName
        + ", elements="
        + Arrays.asList(elements)
        + ") -> "
        + array);

      return array;
    } catch (SQLException e) {
      log.fine(getId() + ":exn-createArrayOf(" + e + ")");

      throw e;
    }
  }

  public Struct createStruct(String typeName, Object[] attributes)
    throws SQLException
  {
    try {
      Struct struct = _conn.createStruct(typeName, attributes);

      log.fine(getId()
        + ":createStruct(typeName="
        + typeName
        + ", attributes="
        + Arrays.asList(attributes)
        + ") -> "
        + struct);

      return struct;
    } catch (SQLException e) {
      log.fine(getId() + ":exn-createStruct(" + e + ")");

      throw e;
    }
  }

  public boolean isValid(int timeout)
    throws SQLException
  {
    try {
      boolean valid = _conn.isValid(timeout);

      log.fine(getId() + ":isValid(" + timeout + ") -> " + valid);

      return valid;
    } catch (SQLException e) {
      log.fine(getId() + ":exn-isValid(" + e + ")");

      throw e;
    }
  }

  public void setClientInfo(String name, String value)
    throws SQLClientInfoException
  {
    log.fine(getId() + ":setClientInfo(" + name + "=" + value + ")");

    try {
      _conn.setClientInfo(name, value);
    } catch (SQLClientInfoException e) {
      log.fine(getId() + ":exn-setClientInfo(" + e + ")");
      throw e;
    }
  }

  public void setClientInfo(Properties properties)
    throws SQLClientInfoException
  {
    try {
      log.fine(getId() + ":setClientInfo(" + properties + ")");

      _conn.setClientInfo(properties);
    } catch (SQLClientInfoException e) {
      log.fine(getId() + ":exn-setClientInfo(" + e + ")");
      
      throw e;
    }
  }

  public String getClientInfo(String name)
    throws SQLException
  {
    try {
      String value = _conn.getClientInfo(name);

      log.fine(getId() + ":getClientInfo(" + name + ") -> " + value);

      return value;
    } catch (SQLException e) {
      log.fine(getId() + ":getClientInfo");

      throw e;
    }
  }

  public Properties getClientInfo()
    throws SQLException
  {
    try {
      Properties clientInfo = _conn.getClientInfo();

      log.fine(getId() + ":getClientInfo() -> " + clientInfo);

      return clientInfo;
    } catch (SQLException e) {
      log.fine(getId() + ":exn-getClientInfo(" + e + ")");

      throw e;
    }
  }

  public <T> T unwrap(Class<T> iface)
    throws SQLException
  {
    try {
      T t = _conn.unwrap(iface);

      log.fine(getId() + ":unwrap(" + iface + ") -> " + t);

      return t;
    } catch (SQLException e) {
      log.fine(getId() + ":exn-unwrap(" + e + ")");

      throw e;
    }
  }

  public boolean isWrapperFor(Class<?> iface)
    throws SQLException
  {
    try {
      boolean isWrapper = _conn.isWrapperFor(iface);

      return isWrapper;
    } catch (SQLException e) {
      log.fine(getId() + ":exn-isWrapperFor(" + e + ")");

      throw e;
    }
  }
  
  protected long start()
  {
    return Alarm.getExactTime();
  }
  
  protected void logXA(long start, String msg)
  {
    long delta = Alarm.getExactTime() - start;
    
    logXA.fine("[" + delta + "ms] " + getId() + ":" + msg);
  }
  
  protected void log(long start, String msg)
  {
    long delta = Alarm.getExactTime() - start;
    
    log.fine("[" + delta + "ms] " + getId() + ":" + msg);
  }

  public String toString()
  {
    return "SpyConnection[id=" + getId() + ",conn=" + _conn + "]";
  }
}
