/*


    ========== licence begin GPL
    Copyright (C) 2002-2003 SAP AG

    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.
    ========== licence end


*/

package com.sap.dbtech.jdbcext;

import java.lang.ref.WeakReference;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;

import javax.sql.PooledConnection;

import com.sap.dbtech.jdbc.ConnectionSapDB;
import com.sap.dbtech.jdbc.StatementSapDB;

/**
 * This class implements a pooled connection for SAP DB. A pooled connection
 * is represents a <i>a single physical connection</i>. The instance of the
 * pooled connection is cached by the application server,
 */
public class PooledConnectionSapDB
    extends ConnectionListenerBag
    implements PooledConnection
{
    Connection physicalConnection;
    WeakReference lastClientConnection;

    PooledConnectionSapDB(Connection physicalConnection)
    {
        this.physicalConnection=physicalConnection;
        this.lastClientConnection=null;
    }

    private void sendErrorEvent(SQLException sqlEx)
    {
        super.sendErrorEvent(this, sqlEx);
    }

    private void sendCloseEvent()
    {
        super.sendCloseEvent(this);
    }

    private PooledConnectionSapDB self()
    {
        return this;
    }

    public synchronized Connection getConnection()
        throws SQLException
    {
        // at first look for the weak reference whether
        // we have to close the client connection (forced close).
        Object lastClient=null;
        if(this.lastClientConnection!=null && ((lastClient=this.lastClientConnection.get())!=null)) {
            PooledClientConnectionSapDB pcc=(PooledClientConnectionSapDB)lastClient;
            ConnectionSapDB c=null;
            try {
                c = (ConnectionSapDB)physicalConnection;
            } catch(ClassCastException ccx) {
                // TODO: make this work if trace is enabled.
            }

            int sz = pcc.statementContainer.size();
            for (int i = 0; i < sz; i++) {
                Statement st = (Statement)((WeakReference)pcc.statementContainer.get(i)).get();
                if (st != null){
                    st.close();
                }
            }
            if(c!=null) {
                c.reinitialize();
            }
            pcc.rollback();
            pcc.closed=true;
            // no close event, though
        }

        PooledClientConnectionSapDB result=new PooledClientConnectionSapDB();
        lastClientConnection = new WeakReference(result);
        return result;
    }

    public synchronized void close()
        throws SQLException
    {
        physicalConnection.close(); // client connection will suffer automatically
    }

    /**
     * The client connection that is handed out to the application.
     * It will use the physical connection for operation.
     */
    private class PooledClientConnectionSapDB
        extends ClientConnectionSapDB
    {
        boolean closed=false;
        java.util.ArrayList statementContainer = new java.util.ArrayList();;

        public Connection getPhysicalConnection()
        {
            return physicalConnection;
        }

        public void exceptionOccurred(SQLException sqlEx)
        {
            if(sqlEx instanceof com.sap.dbtech.jdbc.exceptions.SQLExceptionSapDB) {
                com.sap.dbtech.jdbc.exceptions.SQLExceptionSapDB sapEx
                    =(com.sap.dbtech.jdbc.exceptions.SQLExceptionSapDB)sqlEx;
                if(sapEx.isConnectionReleasing()) {
                    sendErrorEvent(sqlEx);
                }
            } else {
                sendErrorEvent(sqlEx);
            }
        }

        private ConnectionSapDB getSapDBConnection(Connection c)
        {
            ConnectionSapDB result=null;
            try {
                result=(ConnectionSapDB)physicalConnection;
                return result;
            } catch(ClassCastException ccx) {
                try {
                    com.sap.dbtech.jdbc.trace.Connection tc=(com.sap.dbtech.jdbc.trace.Connection)physicalConnection;
                    result=(ConnectionSapDB)tc.getInner();
                    return result;
                } catch(ClassCastException ccx2) {
                    return null;
                }
            }
        }


        public void close()
            throws SQLException
        {
            // do not send unnecessary events
            if(closed) {
                return;
            }
            synchronized(self()) {
                // ... close the open statements ...

                int sz = this.statementContainer.size();
                for (int i = 0; i < sz; i++) {
                  StatementSapDB st = (StatementSapDB)((WeakReference)this.statementContainer.get(i)).get();
                  if (st != null){
                    st.close();
                  }
                }
                ConnectionSapDB c=getSapDBConnection(physicalConnection);
                if(c!=null) {
                    c.reinitialize();
                }
                // make a rollback if the autocommit is off.
                if(!getAutoCommit()) {
                    this.rollback();
                }
                this.closed=true;
                sendCloseEvent();
            }
        }

        public boolean isClosed()
            throws SQLException
        {
            if(this.closed) {
                return true;
            } else {
                return super.isClosed();
            }
        }

        public void finalize()
        {
            try {
                this.close();
            } catch(SQLException sqlEx) {

            }
        }
        private void add2StatementContainer(Object obj){
           synchronized(this.statementContainer){
             this.statementContainer.add(new java.lang.ref.WeakReference(obj));
          }
        }

        public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws java.sql.SQLException {
          PreparedStatement ps = super.prepareStatement (sql,autoGeneratedKeys);
          this.add2StatementContainer(ps);
          return ps;
        }
        public PreparedStatement prepareStatement(String parm1, int[] parm2) throws java.sql.SQLException {
          PreparedStatement ps = super.prepareStatement (parm1,parm2);
          this.add2StatementContainer(ps);
          return ps;
        }
        public PreparedStatement prepareStatement(String sql) throws java.sql.SQLException {
          PreparedStatement ps = super.prepareStatement (sql);
          this.add2StatementContainer(ps);
          return ps;
        }
        public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws java.sql.SQLException {
          PreparedStatement ps = super.prepareStatement (sql,resultSetType,resultSetConcurrency,resultSetHoldability);
          this.add2StatementContainer(ps);
          return ps;
        }
        public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws java.sql.SQLException {
          PreparedStatement ps = super.prepareStatement (sql,resultSetType,resultSetConcurrency);
          this.add2StatementContainer(ps);
          return ps;
        }
        public PreparedStatement prepareStatement(String parm1, String[] parm2) throws java.sql.SQLException {
          PreparedStatement ps = super.prepareStatement (parm1, parm2);
          this.add2StatementContainer(ps);
          return ps;
        }
        public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws java.sql.SQLException {
          CallableStatement ps = super.prepareCall (sql,resultSetType, resultSetConcurrency, resultSetHoldability);
          this.add2StatementContainer(ps);
          return ps;
        }
        public CallableStatement prepareCall(String sql) throws java.sql.SQLException {
          CallableStatement ps = super.prepareCall (sql);
          this.add2StatementContainer(ps);
          return ps;
        }
        public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws java.sql.SQLException {
          CallableStatement ps = super.prepareCall (sql,resultSetType,resultSetConcurrency);
          this.add2StatementContainer(ps);
          return ps;
        }
        public Statement createStatement() throws java.sql.SQLException {
          Statement ps = super.createStatement ();
          this.add2StatementContainer(ps);
          return ps;
        }
        public Statement createStatement(int resultSetType, int resultSetConcurrency) throws java.sql.SQLException {
          Statement ps = super.createStatement (resultSetType,resultSetConcurrency);
          this.add2StatementContainer(ps);
          return ps;
        }
        public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws java.sql.SQLException {
          Statement ps = super.createStatement (resultSetType,resultSetConcurrency,resultSetHoldability);
          this.add2StatementContainer(ps);
          return ps;
        }
    }
}
