/*
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.resource.adapter.jdbc;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * A wrapper for a statement.
 *
 * @todo remove the org.jboss.ejb.plugins.cmp.jdbc.WrappedStatement dependency
 *
 * @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a>
 * @author <a href="mailto:adrian@jboss.com">Adrian Brock</a>
 * @version $Revision: 1.8.4.4 $
 */
public class WrappedStatement implements Statement, StatementAccess,
   org.jboss.ejb.plugins.cmp.jdbc.WrappedStatement
{
   private final WrappedConnection lc;
   private final Statement s;

   /** The result sets */
   private HashMap resultSets;

   public WrappedStatement(final WrappedConnection lc, Statement s)
   {
      this.lc = lc;
      this.s = s;
      lc.registerStatement(this);
   }

   public void close() throws SQLException
   {
      lc.unregisterStatement(this);
      internalClose();
   }

   public boolean execute(String sql) throws SQLException
   {
      checkTransaction();
      try
      {
         checkConfiguredQueryTimeout();
         return s.execute(sql);
      }
      catch (SQLException e)
      {
         checkException(e);
         return false;
      }
   }

   public boolean execute(String sql, int autoGeneratedKeys) throws SQLException
   {
      checkTransaction();
      try
      {
         checkConfiguredQueryTimeout();
         return s.execute(sql, autoGeneratedKeys);
      }
      catch (SQLException e)
      {
         checkException(e);
         return false;
      }
   }

   public boolean execute(String sql, int[] columnIndexes) throws SQLException
   {
      checkTransaction();
      try
      {
         checkConfiguredQueryTimeout();
         return s.execute(sql, columnIndexes);
      }
      catch (SQLException e)
      {
         checkException(e);
         return false;
      }
   }

   public boolean execute(String sql, String[]columnNames ) throws SQLException
   {
      checkTransaction();
      try
      {
         checkConfiguredQueryTimeout();
         return s.execute(sql, columnNames);
      }
      catch (SQLException e)
      {
         checkException(e);
         return false;
      }
   }

   public Connection getConnection() throws SQLException
   {
      return lc;
   }

   public SQLWarning getWarnings() throws SQLException
   {
      try
      {
         return s.getWarnings();
      }
      catch (SQLException e)
      {
         checkException(e);
         return null;
      }
   }

   public void clearWarnings() throws SQLException
   {
      try
      {
         s.clearWarnings();
      }
      catch (SQLException e)
      {
         checkException(e);
      }
   }

   public ResultSet executeQuery(String sql) throws SQLException
   {
      checkTransaction();
      try
      {
         checkConfiguredQueryTimeout();
         ResultSet result = s.executeQuery(sql);
         return registerResultSet(result);
      }
      catch (SQLException e)
      {
         checkException(e);
         return null;
      }
   }

   public int executeUpdate(String sql) throws SQLException
   {
      checkTransaction();
      try
      {
         checkConfiguredQueryTimeout();
         return s.executeUpdate(sql);
      }
      catch (SQLException e)
      {
         checkException(e);
         return 0;
      }
   }

   public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException
   {
      checkTransaction();
      try
      {
         checkConfiguredQueryTimeout();
         return s.executeUpdate(sql, autoGeneratedKeys);
      }
      catch (SQLException e)
      {
         checkException(e);
         return 0;
      }
   }

   public int executeUpdate(String sql, int[] columnIndexes) throws SQLException
   {
      checkTransaction();
      try
      {
         checkConfiguredQueryTimeout();
         return s.executeUpdate(sql, columnIndexes);
      }
      catch (SQLException e)
      {
         checkException(e);
         return 0;
      }
   }

   public int executeUpdate(String sql, String[] columnNames) throws SQLException
   {
      checkTransaction();
      try
      {
         checkConfiguredQueryTimeout();
         return s.executeUpdate(sql, columnNames);
      }
      catch (SQLException e)
      {
         checkException(e);
         return 0;
      }
   }

   public int getMaxFieldSize() throws SQLException
   {
      try
      {
         return s.getMaxFieldSize();
      }
      catch (SQLException e)
      {
         checkException(e);
         return 0;
      }
   }

   public void setMaxFieldSize(int max) throws SQLException
   {
      try
      {
         s.setMaxFieldSize(max);
      }
      catch (SQLException e)
      {
         checkException(e);
      }
   }

   public int getMaxRows() throws SQLException
   {
      try
      {
         return s.getMaxRows();
      }
      catch (SQLException e)
      {
         checkException(e);
         return 0;
      }
   }

   public void setMaxRows(int max) throws SQLException
   {
      try
      {
         s.setMaxRows(max);
      }
      catch (SQLException e)
      {
         checkException(e);
      }
   }

   public void setEscapeProcessing(boolean enable) throws SQLException
   {
      try
      {
         s.setEscapeProcessing(enable);
      }
      catch (SQLException e)
      {
         checkException(e);
      }
   }

   public int getQueryTimeout() throws SQLException
   {
      try
      {
         return s.getQueryTimeout();
      }
      catch (SQLException e)
      {
         checkException(e);
         return 0;
      }
   }

   public void setQueryTimeout(int timeout) throws SQLException
   {
      try
      {
         s.setQueryTimeout(timeout);
      }
      catch (SQLException e)
      {
         checkException(e);
      }
   }

   public void cancel() throws SQLException
   {
      try
      {
         s.cancel();
      }
      catch (SQLException e)
      {
         checkException(e);
      }
   }

   public void setCursorName(String name) throws SQLException
   {
      try
      {
         s.setCursorName(name);
      }
      catch (SQLException e)
      {
         checkException(e);
      }
   }

   public ResultSet getResultSet() throws SQLException
   {
      try
      {
         ResultSet result = s.getResultSet();
         if (result == null)
            return null;
         else
            return registerResultSet(result);
      }
      catch (SQLException e)
      {
         checkException(e);
         return null;
      }
   }

   public int getUpdateCount() throws SQLException
   {
      try
      {
         return s.getUpdateCount();
      }
      catch (SQLException e)
      {
         checkException(e);
         return 0;
      }
   }

   public boolean getMoreResults() throws SQLException
   {
      try
      {
         return s.getMoreResults();
      }
      catch (SQLException e)
      {
         checkException(e);
         return false;
      }
   }

   public boolean getMoreResults(int current) throws SQLException
   {
      try
      {
         return s.getMoreResults(current);
      }
      catch (SQLException e)
      {
         checkException(e);
         return false;
      }
   }

   public void setFetchDirection(int direction) throws SQLException
   {
      try
      {
         s.setFetchDirection(direction);
      }
      catch (SQLException e)
      {
         checkException(e);
      }
   }

   public int getFetchDirection() throws SQLException
   {
      try
      {
         return s.getFetchDirection();
      }
      catch (SQLException e)
      {
         checkException(e);
         return 0;
      }
   }

   public void setFetchSize(int rows) throws SQLException
   {
      try
      {
         s.setFetchSize(rows);
      }
      catch (SQLException e)
      {
         checkException(e);
      }
   }

   public int getFetchSize() throws SQLException
   {
      try
      {
         return s.getFetchSize();
      }
      catch (SQLException e)
      {
         checkException(e);
         return 0;
      }
   }

   public int getResultSetConcurrency() throws SQLException
   {
      try
      {
         return s.getResultSetConcurrency();
      }
      catch (SQLException e)
      {
         checkException(e);
         return 0;
      }
   }

   public int getResultSetType() throws SQLException
   {
      try
      {
         return s.getResultSetType();
      }
      catch (SQLException e)
      {
         checkException(e);
         return 0;
      }
   }

   public void addBatch(String sql) throws SQLException
   {
      try
      {
         s.addBatch(sql);
      }
      catch (SQLException e)
      {
         checkException(e);
      }
   }

   public void clearBatch() throws SQLException
   {
      try
      {
         s.clearBatch();
      }
      catch (SQLException e)
      {
         checkException(e);
      }
   }

   public int[] executeBatch() throws SQLException
   {
      try
      {
         checkConfiguredQueryTimeout();
         return s.executeBatch();
      }
      catch (SQLException e)
      {
         checkException(e);
         return null;
      }
   }

   public ResultSet getGeneratedKeys() throws SQLException
   {
      try
      {
         ResultSet resultSet = s.getGeneratedKeys();
         return registerResultSet(resultSet);
      }
      catch (SQLException e)
      {
         checkException(e);
         return null;
      }
   }

   public int getResultSetHoldability() throws SQLException
   {
      try
      {
         return s.getResultSetHoldability();
      }
      catch (SQLException e)
      {
         checkException(e);
         return 0;
      }
   }

   public Statement getUnderlyingStatement()
   {
      return s;
   }

   protected void checkException(SQLException e)
      throws SQLException
   {
      lc.checkException(e);
   }

   protected void checkTransaction()
      throws SQLException
   {
      lc.checkTransaction();
   }

   protected void checkConfiguredQueryTimeout() throws SQLException
   {
      lc.checkConfiguredQueryTimeout(this);
   }
   
   protected void internalClose() throws SQLException
   {
      try
      {
         closeResultSets();
         s.close();
      }
      catch (SQLException e)
      {
         checkException(e);
      }
   }

   protected ResultSet registerResultSet(ResultSet resultSet)
   {
      if (resultSet != null)
         resultSet = new WrappedResultSet(this, resultSet);
      
      if (lc.getTrackStatements() == BaseWrapperManagedConnectionFactory.TRACK_STATEMENTS_FALSE_INT)
         return resultSet;

      synchronized (this)
      {
         if (resultSets == null)
            resultSets = new HashMap();
         
         if (lc.getTrackStatements() == BaseWrapperManagedConnectionFactory.TRACK_STATEMENTS_TRUE_INT)
            resultSets.put(resultSet, new Throwable("STACKTRACE"));
         else
            resultSets.put(resultSet, null);
      }
      return resultSet;
   }

   protected void unregisterResultSet(WrappedResultSet resultSet)
   {
      if (lc.getTrackStatements() == BaseWrapperManagedConnectionFactory.TRACK_STATEMENTS_FALSE_INT)
         return;

      synchronized (this)
      {
         if (resultSets != null)
            resultSets.remove(resultSet);
      }
   }

   protected void closeResultSets()
   {
      if (lc.getTrackStatements() == BaseWrapperManagedConnectionFactory.TRACK_STATEMENTS_FALSE_INT)
         return;

      synchronized (this)
      {
         if (resultSets == null)
            return;
         for (Iterator i = resultSets.entrySet().iterator(); i.hasNext();)
         {
            Map.Entry entry = (Map.Entry) i.next();
            WrappedResultSet resultSet = (WrappedResultSet) entry.getKey();
            if (lc.getTrackStatements() == BaseWrapperManagedConnectionFactory.TRACK_STATEMENTS_TRUE_INT)
            {
               Throwable stackTrace = (Throwable) entry.getValue();
               lc.getLogger().warn("Closing a result set you left open! Please close it yourself.", stackTrace);
            }
            try
            {
               resultSet.internalClose();
            }
            catch (Throwable t)
            {
               lc.getLogger().warn("Error closing a result set you left open! Please close it yourself.", t);
            }
         }
         resultSets.clear();
      }
   }
}
