/*
 * 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.CallableStatement;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Savepoint;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.jboss.logging.Logger;

/**
 * A wrapper for a connection.
 * 
 * @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.7 $
 */

public class WrappedConnection implements Connection
{

   private static final Logger log = Logger.getLogger(WrappedConnection.class);

   private BaseWrapperManagedConnection mc;

   private HashMap statements;

   private boolean closed = false;

   private int trackStatements;
   
   public WrappedConnection(final BaseWrapperManagedConnection mc)
   {
      this.mc = mc;
      if (mc != null)
         trackStatements = mc.getTrackStatements();
   }

   void setManagedConnection(final BaseWrapperManagedConnection mc)
   {
      this.mc = mc;
      if (mc != null)
         trackStatements = mc.getTrackStatements();
   }

   // implementation of java.sql.Connection interface

   /**
     * @param param1 <description>
     * @exception java.sql.SQLException <description>
     */
   public void setReadOnly(boolean readOnly) throws SQLException
   {
      checkStatus();
      mc.setJdbcReadOnly(readOnly);
   }

   /**
     * @return <description>
     * @exception java.sql.SQLException <description>
     */
   public boolean isReadOnly() throws SQLException
   {
      checkStatus();
      return mc.isJdbcReadOnly();
   }

   /**
     * @exception java.sql.SQLException <description>
     */
   public void close() throws SQLException
   {
      closed = true;
      if (mc != null)
      {
         if (trackStatements != BaseWrapperManagedConnectionFactory.TRACK_STATEMENTS_FALSE_INT)
         {
            synchronized (this)
            {
               if (statements != null)
               {
                  for (Iterator i = statements.entrySet().iterator(); i.hasNext(); )
                  {
                     Map.Entry entry = (Map.Entry) i.next();
                     WrappedStatement ws = (WrappedStatement) entry.getKey();
                     if (trackStatements == BaseWrapperManagedConnectionFactory.TRACK_STATEMENTS_TRUE_INT)
                     {
                        Exception stackTrace = (Exception) entry.getValue();
                        log.warn("Closing a statement you left open, please do your own housekeeping", stackTrace);
                     }
                     try
                     {
                        ws.internalClose();
                     }
                     catch (Throwable t)
                     {
                        log.warn("Exception trying to close statement:", t);
                     }
                  }
               }
            }
         }
         mc.closeHandle(this);
      } // end of if ()
      mc = null;
   }

   /**
     * @return <description>
     * @exception java.sql.SQLException <description>
     */
   public boolean isClosed() throws SQLException
   {
      return closed;
   }

   /**
     * @return <description>
     * @exception java.sql.SQLException <description>
     */
   public Statement createStatement() throws SQLException
   {
      checkTransaction();
      try
      {
         return new WrappedStatement(this, mc.getConnection().createStatement());
      }
      catch (SQLException e)
      {
         checkException(e);
         return null;
      } // end of try-catch
   }

   /**
     * @param param1 <description>
     * @param param2 <description>
     * @return <description>
     * @exception java.sql.SQLException <description>
     */
   public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException
   {
      checkTransaction();
      try
      {
         return new WrappedStatement(this, mc.getConnection().createStatement(resultSetType, resultSetConcurrency));
      }
      catch (SQLException e)
      {
         checkException(e);
         return null;
      } // end of try-catch
   }

   /**
     * @param param1 <description>
     * @param param2 <description>
     * @param param3 <description>
     * @return <description>
     * @exception java.sql.SQLException <description>
     */
   public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
         throws SQLException
   {

      checkTransaction();
      try
      {
         return new WrappedStatement(this, mc.getConnection()
               .createStatement(resultSetType, resultSetConcurrency, resultSetHoldability));
      }
      catch (SQLException e)
      {
         checkException(e);
         return null;
      } // end of try-catch
      /*
         * throw new SQLException("JDK1.4 method not available in JDK1.3");
         */
   }

   /**
     * @param param1 <description>
     * @return <description>
     * @exception java.sql.SQLException <description>
     */
   public PreparedStatement prepareStatement(String sql) throws SQLException
   {
      checkTransaction();
      try
      {
         return new WrappedPreparedStatement(this, mc.prepareStatement(sql));
      }
      catch (SQLException e)
      {
         checkException(e);
         return null;
      } // end of try-catch
   }

   /**
     * @param param1 <description>
     * @param param2 <description>
     * @param param3 <description>
     * @return <description>
     * @exception java.sql.SQLException <description>
     */
   public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
         throws SQLException
   {
      checkTransaction();
      try
      {
         return new WrappedPreparedStatement(this, mc.getConnection()
               .prepareStatement(sql, resultSetType, resultSetConcurrency));
      }
      catch (SQLException e)
      {
         checkException(e);
         return null;
      } // end of try-catch
   }

   /**
     * @param param1 <description>
     * @param param2 <description>
     * @param param3 <description>
     * @param param4 <description>
     * @return <description>
     * @exception java.sql.SQLException <description>
     */
   public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency,
         int resultSetHoldability) throws SQLException
   {

      checkTransaction();
      try
      {
         return new WrappedPreparedStatement(this, mc.getConnection()
               .prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability));
      }
      catch (SQLException e)
      {
         checkException(e);
         return null;
      } // end of try-catch
      /*
         * throw new SQLException("JDK1.4 method not available in JDK1.3");
         */
   }

   /**
     * @param param1 <description>
     * @param param2 <description>
     * @return <description>
     * @exception java.sql.SQLException <description>
     */
   public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException
   {

      checkTransaction();
      try
      {
         return new WrappedPreparedStatement(this, mc.getConnection().prepareStatement(sql, autoGeneratedKeys));
      }
      catch (SQLException e)
      {
         checkException(e);
         return null;
      } // end of try-catch
      /*
         * throw new SQLException("JDK1.4 method not available in JDK1.3");
         */
   }

   /**
     * @param param1 <description>
     * @param param2 <description>
     * @return <description>
     * @exception java.sql.SQLException <description>
     */
   public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException
   {

      checkTransaction();
      try
      {
         return new WrappedPreparedStatement(this, mc.getConnection().prepareStatement(sql, columnIndexes));
      }
      catch (SQLException e)
      {
         checkException(e);
         return null;
      } // end of try-catch
      /*
         * throw new SQLException("JDK1.4 method not available in JDK1.3");
         */
   }

   /**
     * @param param1 <description>
     * @param param2 <description>
     * @return <description>
     * @exception java.sql.SQLException <description>
     */
   public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException
   {

      checkTransaction();
      try
      {
         return new WrappedPreparedStatement(this, mc.getConnection().prepareStatement(sql, columnNames));
      }
      catch (SQLException e)
      {
         checkException(e);
         return null;
      } // end of try-catch
      /*
         * throw new SQLException("JDK1.4 method not available in JDK1.3");
         */
   }

   /**
     * @param param1 <description>
     * @return <description>
     * @exception java.sql.SQLException <description>
     */
   public CallableStatement prepareCall(String sql) throws SQLException
   {
      checkTransaction();
      try
      {
         return new WrappedCallableStatement(this, mc.getConnection().prepareCall(sql));
      }
      catch (SQLException e)
      {
         checkException(e);
         return null;
      } // end of try-catch
   }

   /**
     * @param param1 <description>
     * @param param2 <description>
     * @param param3 <description>
     * @return <description>
     * @exception java.sql.SQLException <description>
     */
   public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException
   {
      checkTransaction();
      try
      {
         return new WrappedCallableStatement(this, mc.getConnection()
               .prepareCall(sql, resultSetType, resultSetConcurrency));
      }
      catch (SQLException e)
      {
         checkException(e);
         return null;
      } // end of try-catch
   }

   /**
     * @param param1 <description>
     * @param param2 <description>
     * @param param3 <description>
     * @param param4 <description>
     * @return <description>
     * @exception java.sql.SQLException <description>
     */
   public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency,
         int resultSetHoldability) throws SQLException
   {

      checkTransaction();
      try
      {
         return new WrappedCallableStatement(this, mc.getConnection()
               .prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability));
      }
      catch (SQLException e)
      {
         checkException(e);
         return null;
      } // end of try-catch
      /*
         * throw new SQLException("JDK1.4 method not available in JDK1.3");
         */
   }

   /**
     * @param param1 <description>
     * @return <description>
     * @exception java.sql.SQLException <description>
     */
   public String nativeSQL(String sql) throws SQLException
   {
      checkTransaction();
      try
      {
         return mc.getConnection().nativeSQL(sql);
      }
      catch (SQLException e)
      {
         checkException(e);
         return null;
      } // end of try-catch
   }

   /**
     * @param param1 <description>
     * @exception java.sql.SQLException <description>
     */
   public void setAutoCommit(boolean autocommit) throws SQLException
   {
      checkStatus();
      mc.setJdbcAutoCommit(autocommit);
   }

   /**
     * @return <description>
     * @exception java.sql.SQLException <description>
     */
   public boolean getAutoCommit() throws SQLException
   {
      checkStatus();
      return mc.isJdbcAutoCommit();
   }

   /**
     * @exception java.sql.SQLException <description>
     */
   public void commit() throws SQLException
   {
      checkTransaction();
      mc.jdbcCommit();
   }

   /**
     * @exception java.sql.SQLException <description>
     */
   public void rollback() throws SQLException
   {
      checkStatus();
      mc.jdbcRollback();
   }

   /**
     * @param param1 <description>
     * @exception java.sql.SQLException <description>
     */
   public void rollback(Savepoint savepoint) throws SQLException
   {
      checkTransaction();
      mc.jdbcRollback(savepoint);
   }

   /**
     * @return <description>
     * @exception java.sql.SQLException <description>
     */
   public DatabaseMetaData getMetaData() throws SQLException
   {
      checkTransaction();
      try
      {
         return mc.getConnection().getMetaData();
      }
      catch (SQLException e)
      {
         checkException(e);
         return null;
      } // end of try-catch
   }

   /**
     * @param param1 <description>
     * @exception java.sql.SQLException <description>
     */
   public void setCatalog(String catalog) throws SQLException
   {
      checkTransaction();
      try
      {
         mc.getConnection().setCatalog(catalog);
      }
      catch (SQLException e)
      {
         checkException(e);
      } // end of try-catch
   }

   /**
     * @return <description>
     * @exception java.sql.SQLException <description>
     */
   public String getCatalog() throws SQLException
   {
      checkTransaction();
      try
      {
         return mc.getConnection().getCatalog();
      }
      catch (SQLException e)
      {
         checkException(e);
         return null;
      } // end of try-catch
   }

   /**
     * @param param1 <description>
     * @exception java.sql.SQLException <description>@todo check we are not in
     *               a managed transaction.
     */
   public void setTransactionIsolation(int isolationLevel) throws SQLException
   {
      checkStatus();
      mc.setJdbcTransactionIsolation(isolationLevel);
   }

   /**
     * @return <description>
     * @exception java.sql.SQLException <description>
     */
   public int getTransactionIsolation() throws SQLException
   {
      checkStatus();
      return mc.getJdbcTransactionIsolation();
   }

   /**
     * @return <description>
     * @exception java.sql.SQLException <description>
     */
   public SQLWarning getWarnings() throws SQLException
   {
      checkTransaction();
      return mc.getConnection().getWarnings();
   }

   /**
     * @exception java.sql.SQLException <description>
     */
   public void clearWarnings() throws SQLException
   {
      checkTransaction();
      mc.getConnection().clearWarnings();
   }

   /**
     * @return <description>
     * @exception java.sql.SQLException <description>
     */
   public Map getTypeMap() throws SQLException
   {
      checkTransaction();
      try
      {
         return mc.getConnection().getTypeMap();
      }
      catch (SQLException e)
      {
         checkException(e);
         return null;
      } // end of try-catch
   }

   /**
     * @param param1 <description>
     * @exception java.sql.SQLException <description>
     */
   public void setTypeMap(Map typeMap) throws SQLException
   {
      checkTransaction();
      try
      {
         mc.getConnection().setTypeMap(typeMap);
      }
      catch (SQLException e)
      {
         checkException(e);
      } // end of try-catch
   }

   /**
     * @param param1 <description>
     * @exception java.sql.SQLException <description>
     */
   public void setHoldability(int holdability) throws SQLException
   {

      checkTransaction();
      try
      {
         mc.getConnection().setHoldability(holdability);
      }
      catch (SQLException e)
      {
         checkException(e);
      } // end of try-catch
      /*
         * throw new SQLException("JDK1.4 method not available in JDK1.3");
         */
   }

   /**
     * @return <description>
     * @exception java.sql.SQLException <description>
     */
   public int getHoldability() throws SQLException
   {

      checkTransaction();
      try
      {
         return mc.getConnection().getHoldability();
      }
      catch (SQLException e)
      {
         checkException(e);
         throw e;
      } // end of try-catch
      /*
         * throw new SQLException("JDK1.4 method not available in JDK1.3");
         */
   }

   /**
     * @return <description>
     * @exception java.sql.SQLException <description>
     */
   public Savepoint setSavepoint() throws SQLException
   {

      checkTransaction();
      try
      {
         return mc.getConnection().setSavepoint();
      }
      catch (SQLException e)
      {
         checkException(e);
         throw e;
      } // end of try-catch
      /*
         * throw new SQLException("JDK1.4 method not available in JDK1.3");
         */
   }

   /**
     * @param param1 <description>
     * @return <description>
     * @exception java.sql.SQLException <description>
     */
   public Savepoint setSavepoint(String name) throws SQLException
   {

      checkTransaction();
      try
      {
         return mc.getConnection().setSavepoint(name);
      }
      catch (SQLException e)
      {
         checkException(e);
         throw e;
      } // end of try-catch
      /*
         * throw new SQLException("JDK1.4 method not available in JDK1.3");
         */
   }

   /**
     * @param param1 <description>
     * @exception java.sql.SQLException <description>
     */
   public void releaseSavepoint(Savepoint savepoint) throws SQLException
   {

      checkTransaction();
      try
      {
         mc.getConnection().releaseSavepoint(savepoint);
      }
      catch (SQLException e)
      {
         checkException(e);
      } // end of try-catch
      /*
         * throw new SQLException("JDK1.4 method not available in JDK1.3");
         */
   }
   //Public non-jdbc methods

   public Connection getUnderlyingConnection() throws SQLException
   {
      checkTransaction();
      return mc.getConnection();
   }

   //package methods

   void checkTransaction() throws SQLException
   {
      checkStatus();
      mc.checkTransaction();
   }

   //protected methods

   /**
     * The checkStatus method checks that the handle has not been closed and
     * that it is associated with a managed connection.
     * 
     * @exception SQLException if an error occurs
     */
   protected void checkStatus() throws SQLException
   {
      if (closed)
      {
         throw new SQLException("Connection handle has been closed and is unusable");
      } // end of if ()
      if (mc == null)
      {
         throw new SQLException("Connection handle is not currently associated with a ManagedConnection");
      } // end of if ()
   }

   /**
     * The base checkException method rethrows the supplied exception, informing
     * the ManagedConnection of the error. Subclasses may override this to
     * filter exceptions based on their severity.
     * 
     * @param e a <code>SQLException</code> value
     * @exception Exception if an error occurs
     */
   protected void checkException(SQLException e) throws SQLException
   {
      if (mc != null)
      {
         mc.connectionError(e);
      } // end of if ()
      throw e;
   }

   int getTrackStatements()
   {
      return trackStatements;
   }
   
   void registerStatement(WrappedStatement ws)
   {
      if (trackStatements == BaseWrapperManagedConnectionFactory.TRACK_STATEMENTS_FALSE_INT)
         return;
      synchronized (this)
      {
         if (statements == null)
            statements = new HashMap();
         statements.put(ws, new Exception("STACKTRACE"));
      }
   }

   void unregisterStatement(WrappedStatement ws)
   {
      if (trackStatements == BaseWrapperManagedConnectionFactory.TRACK_STATEMENTS_FALSE_INT)
         return;
      synchronized (this)
      {
         if (statements != null)
            statements.remove(ws);
      }
   }

   Logger getLogger()
   {
      return log;
   }
   
}