package org.objectweb.jonas.jtests.beans.jdbc;

import java.rmi.RemoteException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.sql.SQLException;
import java.sql.ResultSet;
import java.util.Hashtable;
import java.util.Iterator;

import javax.ejb.CreateException;
import javax.ejb.EJBException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import javax.ejb.SessionSynchronization;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.rmi.PortableRemoteObject;
import javax.sql.DataSource;

import org.objectweb.jonas.common.Log;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;


/**
 * Stateful Session Bean that manages directly jdbc connections
 */
public class ManagerSY implements SessionBean, SessionSynchronization {

    static private Logger logger = null;
    SessionContext ejbContext;
    String tableName = "jdbcSF";

    // ------------------------------------------------------------------
    // The state of this Stateful Session Bean
    // ------------------------------------------------------------------
    int conb = 100;
    InitialContext ictx = null;
    DataSource ds = null;

    /**
     * List of Connections
     */
    Hashtable clist = new Hashtable();

    // ------------------------------------------------------------------
    // SessionSynchronization implementation
    // ------------------------------------------------------------------

    public void afterBegin() {
        logger.log(BasicLevel.DEBUG, "");
    }

    public void beforeCompletion() {
        logger.log(BasicLevel.DEBUG, "");
    }

    public void afterCompletion(boolean committed) {
        logger.log(BasicLevel.DEBUG, "");
    }

    // ------------------------------------------------------------------
    // SessionBean implementation
    // ------------------------------------------------------------------

    /**
     * Make here all initialisations needed in this stateful session bean
     */
    public void setSessionContext(SessionContext ctx) {
        if (logger == null) {
            logger = Log.getLogger("org.objectweb.jonas_tests");
        }
        logger.log(BasicLevel.DEBUG, "");
        ejbContext = ctx;
        ds = getDataSource("java:comp/env/jdbc/mydb");
        // Get a Connection now
        try {
            Connection c = ds.getConnection();
            clist.put(new Integer(0), c);
        } catch (SQLException e) {
            throw new EJBException("Cannot get first Connection:"+e);
        }
    }
	
    public void ejbRemove() {
        logger.log(BasicLevel.DEBUG, "");
        // Close all opened connections
        for (Iterator it = clist.values().iterator(); it.hasNext();) {
            Connection c = (Connection) it.next();
            try {
                c.close();
            }  catch (SQLException e) {
                logger.log(BasicLevel.ERROR, "Cannot close Connection:" + e);
            }
        }
    }
	
    public void ejbCreate() throws CreateException {
        logger.log(BasicLevel.DEBUG, "");
    }

    public void ejbPassivate() {
        logger.log(BasicLevel.DEBUG, "");
    }

    public void ejbActivate() {
        logger.log(BasicLevel.DEBUG, "");
    }
    
    // ------------------------------------------------------------------
    // Manager implementation
    // ------------------------------------------------------------------

    /**
     * Access a closed connection
     * @return true if all was OK
     */
    public boolean accessCloseConnection() throws RemoteException {
        logger.log(BasicLevel.DEBUG, "");
        Connection c;
        try {
            c = ds.getConnection();
        } catch (SQLException e) {
            logger.log(BasicLevel.ERROR, "Cannot get a Connection:"+e);
            return false;
        }
        try {
            c.close();
        } catch (SQLException e) {
            logger.log(BasicLevel.ERROR, "Cannot close Connection:"+e);
            return false;
        }
        PreparedStatement stmt = null;
        try {
            stmt = c.prepareStatement("update "+tableName+" set c_value=? where c_pk=?");
            stmt.setString(2, "pk3");
            stmt.setInt(1, 122);
            stmt.executeUpdate();
        } catch (SQLException e) {
            return true;
        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "SQLException should be raised, not :" + e);
            return false;
        } finally {
            try {
                if (stmt != null) {
                    stmt.close();
                }
            } catch (SQLException ignore) {
            }
        }
        logger.log(BasicLevel.ERROR, "Should not access a close Connection:");
        return false;
    }
            
    /**
     * open and close a Connection
     * @return true if all was OK
     * @throws RemoteException fail on SQL command.
     */
    public boolean openCloseConnection() throws RemoteException {
        logger.log(BasicLevel.DEBUG, "");
        Connection c;
        try {
            c = ds.getConnection();
        } catch (SQLException e) {
            logger.log(BasicLevel.ERROR, "Cannot get a Connection:"+e);
            return false;
        }
        try {
            c.close();
        } catch (SQLException e) {
            logger.log(BasicLevel.ERROR, "Cannot close Connection:"+e);
            return false;
        }
        return true;
    }

    /**
     * return the number associated to the connection opened,
     * or 0 if it failed.
     * @return connection ident (int)
     * @throws RemoteException fail on SQL command.
     */
    public int openConnection() throws RemoteException {
        logger.log(BasicLevel.DEBUG, "");
        Connection c;
        try {
            c = ds.getConnection();
        } catch (SQLException e) {
            logger.log(BasicLevel.ERROR, "Cannot get a Connection:"+e);
            return 0;
        }
        // choose a connection number
        conb++;
        clist.put(new Integer(conb), c);
        logger.log(BasicLevel.DEBUG, "connection number="+conb);
        return conb;
    }

    /**
     * Close a Connection previously opened.
     * @param nb ident of the Connection
     * @return true if close Connection worked
     * @throws RemoteException fail on SQL command.
     */
    public boolean closeConnection(int nb) throws RemoteException {
        logger.log(BasicLevel.DEBUG, "connection number="+conb);
        Connection c = (Connection) clist.remove(new Integer(nb));
        if (c == null) {
            logger.log(BasicLevel.ERROR, "Unknown Connection");
            return false;
        }
        try {
            c.close();
        } catch (SQLException e) {
            logger.log(BasicLevel.ERROR, "Cannot close Connection:"+e);
            return false;
        }
        boolean isclosed;
        try {
            isclosed = c.isClosed();
        } catch (SQLException e) {
            logger.log(BasicLevel.ERROR, "Cannot check if closed:"+e);
            return false;
        }
        return isclosed;
    }

    /**
     * Store the value for the pk entry
     * @param pk primary key
     * @param val value associated to this pk.
     * @throws RemoteException fail on SQL command.
     */
    public void storeValue(String pk, int val) throws RemoteException {
        Connection conn;
        try {
            conn = ds.getConnection();
        } catch(Exception e) {
            throw new RemoteException("Cannot get Connection");
        }
        PreparedStatement stmt = null;
        try {
            stmt = conn.prepareStatement("update "+tableName+" set c_value=? where c_pk=?");
            stmt.setString(2, pk);
            stmt.setInt(1, val);
            stmt.executeUpdate();
        } catch (SQLException e) {
            throw new RemoteException("Cannot set value to pk: " + pk);
        } finally {
            try {
                if (stmt != null) {
                    stmt.close();
                }
                conn.close();
            } catch (SQLException ignore) {
            }
        }
    }

    /**
     * return the value stored for this pk entry
     * @param pk primary key
     * @return value associated to this pk.
     * @throws RemoteException fail on SQL command.
     */
    public int getValue(String pk) throws RemoteException {
        int value = 0;
        Connection conn;
        try {
            conn = ds.getConnection();
        } catch(Exception e) {
            throw new RemoteException("Cannot get Connection");
        }
        PreparedStatement stmt = null;
        try {
            stmt = conn.prepareStatement("select c_value from "+tableName+" where c_pk=?");
            stmt.setString(1, pk);
            ResultSet rs = stmt.executeQuery();
            if (! rs.next()) {
                throw new RemoteException("No such value");
            }
            value = rs.getInt(1);
        } catch (SQLException e) {
            throw new RemoteException("Cannot get value from pk: " + pk);
        } finally {
            try {
                if (stmt != null) {
                    stmt.close();
                }
                conn.close();
            } catch (SQLException ignore) {
            }
        }
        return value;
    }

    // ------------------------------------------------------------------
    // private methods
    // ------------------------------------------------------------------

    /*
     * get the DataSource given its name in JNDI
     */
    private DataSource getDataSource(String db) {

        // lookup the DataSource in the initial context
        DataSource ds;
        try {
            // get initial context
            if (ictx == null) {
                ictx = new InitialContext();
            }
            ds = (DataSource) PortableRemoteObject.narrow(ictx.lookup(db), DataSource.class);
        } catch (NamingException e) {
            logger.log(BasicLevel.ERROR, "Cannot lookup datasource "+db+": "+e);
            throw new EJBException("Cannot access DataSource");
        }

        // return it
        return ds;
    }

}
