/**
 * @version $Revision: 1.1.2.2 $
 */
 
package com.iplanet.server.http.session;

import java.sql.*;
import java.util.Properties;
import java.io.IOException;
import java.io.ByteArrayInputStream;
import java.io.BufferedInputStream;
import java.io.ObjectInputStream;
import java.io.ByteArrayOutputStream;
import java.io.BufferedOutputStream;
import java.io.ObjectOutputStream;
import javax.servlet.ServletContext;

import com.iplanet.server.http.util.LogUtil;
import com.iplanet.server.http.util.ResUtil;

import com.iplanet.server.http.servlet.NSServletLoader;
import com.iplanet.server.http.servlet.NSServletRunner;

import com.iplanet.server.http.util.misc.ResourcePool;

/**
 * iPlanet Web Server 6.0's JDBC session store implementation.
 *
 * @deprecated
 */
public class JdbcStore extends SessionDataStore
{
    /**
     * A utility class which takes care of internationalizing the
     * messages sent to the error log.
     */
    private static ResUtil _res = ResUtil.getDefaultResUtil();

    private Connection _connection;

    // initialization properties:
    private static final String prop_jdbcDriver    = "provider";
    private static final String prop_jdbcUrl       = "url";
    private static final String prop_tableName     = "table";
    private static final String prop_username      = "username";
    private static final String prop_password      = "password";
    private static final String prop_reaperActive  = "reaperActive";
    
    private static final String prop_lookupPool = "lookupPool";
    private static final String prop_insertPool = "insertPool";
    private static final String prop_updatePool = "updatePool";
    private static final String prop_deletePool = "deletePool";
 
    private int _lookupPoolSize = 4;
    private int _insertPoolSize = 4;
    private int _updatePoolSize = 4;
    private int _deletePoolSize = 2;
    
    private String _username = null;
    private String _password = null;
    
    private String _jdbcDriver = "sun.jdbc.odbc.JdbcOdbcDriver";
    private String _jdbcUrl    = "jdbc:odbc:LocalServer";
    
    private StatementsPool _stmt_lookup;
    private StatementsPool _stmt_insert;
    private StatementsPool _stmt_update;
    private StatementsPool _stmt_delete;
 
    private PreparedStatement _stmt_reaper;
 
    // default column names
    private String _table         = "sessions";
    private String _accessTimeCol = "AccessTime";
    private String _timeOutCol = "TimeOut";
    private String _sessionIdCol  = "SessionID";
    private String _valueCol      = "Value";
    
    private boolean _initialized   = false;
    private boolean _reaper_active = true;
    
    public JdbcStore()
    {
        super();
    }

    public boolean init(Properties config)
    {
        if (config != null)
        {
            String s = config.getProperty(prop_lookupPool);
            if (s != null)
            {
                try
                {
                    int val = Integer.parseInt(s);
                    if (val < 1 || val > 100)
                    {
                        LogUtil.logWarning(_res.getProp("session.JdbcStore.msg_poolNotValid", prop_lookupPool));
                    }
                    else
                        _lookupPoolSize = val;
                }
                catch (NumberFormatException nfe)
                {
                    LogUtil.logWarning(_res.getProp("session.JdbcStore.msg_poolNotValid", prop_lookupPool));
                }
            }
        
            s = config.getProperty(prop_updatePool);
            if (s != null)
            {
                try
                {
                    int val = Integer.parseInt(s);
                    if (val < 1 || val > 100)
                    {
                        LogUtil.logWarning(_res.getProp("session.JdbcStore.msg_poolNotValid", prop_updatePool));
                    }
                    else
                        _updatePoolSize = val;
                }
                catch (NumberFormatException nfe)
                {
                    LogUtil.logWarning(_res.getProp("session.JdbcStore.msg_poolNotValid", prop_updatePool));
                }
            }
        
            s = config.getProperty(prop_insertPool);
            if (s != null)
            {
                try
                {
                    int val = Integer.parseInt(s);
                    if (val < 1 || val > 100)
                    {
                        LogUtil.logWarning(_res.getProp("session.JdbcStore.msg_poolNotValid", prop_insertPool));
                    }
                    else
                        _insertPoolSize = val;
                }
                catch (NumberFormatException nfe)
                {
                    LogUtil.logWarning(_res.getProp("session.JdbcStore.msg_poolNotValid", prop_insertPool));
                }
            }
        
            s = config.getProperty(prop_deletePool);
            if (s != null)
            {
                try
                {
                    int val = Integer.parseInt(s);
                    if (val < 1 || val > 100)
                    {
                        LogUtil.logWarning(_res.getProp("session.JdbcStore.msg_poolNotValid", prop_deletePool));
                    }
                    else
                        _deletePoolSize = val;
                }
                catch (NumberFormatException nfe)
                {
                    LogUtil.logWarning(_res.getProp("session.JdbcStore.msg_poolNotValid", prop_deletePool));
                }
            }
        
            s = config.getProperty("provider");
            if (s != null)
                _jdbcDriver = s;

            s = config.getProperty("url");
            if (s != null)
                _jdbcUrl = s;
        
            _username = config.getProperty("username");
            _password = config.getProperty("password");
        
            s = config.getProperty("table");
            if (s != null)
                _table = s;
        
            s = config.getProperty("accessTimeColumn");
            if (s != null)
                _accessTimeCol = s;
        
            s = config.getProperty("timeOutColumn");
            if (s != null)
                _timeOutCol = s;
        
            s = config.getProperty("sessionIdColumn");
            if (s != null)
                _sessionIdCol = s;
        
            s = config.getProperty("valueColumn");
            if (s != null)
                _valueCol = s;

            s = config.getProperty("reaperActive");
            if (s != null)
                _reaper_active = (Boolean.valueOf(s)).booleanValue();
        }
        
        // establish connection to the database and prepare queries
        
        try
        {
            Class.forName(_jdbcDriver);
            _connection  = DriverManager.getConnection(_jdbcUrl, 
                                                       _username, _password);
            _connection.setAutoCommit(true);           
            
            _stmt_lookup = new StatementsPool(_lookupPoolSize, "SELECT " +
                                              _valueCol + " FROM " + _table +
                                              " WHERE " + _sessionIdCol + 
                                              " = ?");
            
            _stmt_delete = new StatementsPool(_deletePoolSize, "DELETE FROM " +
                                              _table + " WHERE " +
                                              _sessionIdCol + " = ?");

            _stmt_update = new StatementsPool(_updatePoolSize, "UPDATE " +
                                              _table + " SET " + 
                                              _accessTimeCol + " = ?, " + 
                                              _timeOutCol + " = ?, " + 
                                              _valueCol + " = ? WHERE " + 
                                              _sessionIdCol + " = ?");

            _stmt_insert = new StatementsPool(_insertPoolSize, "INSERT INTO " +
                                              _table + " (" + _sessionIdCol + 
                                              "," + _accessTimeCol + "," + 
                                              _timeOutCol + "," + 
                                              _valueCol + ") VALUES (?,?,?,?)");

            _stmt_reaper = _connection.prepareStatement("DELETE FROM " +
                                                        _table + " WHERE (" +
                                                        _accessTimeCol + 
                                                        " + " + _timeOutCol + 
                                                        ") <= ? AND (" + 
                                                        _sessionIdCol + 
                                                        " NOT LIKE '" + 
                                                        NSServletRunner.contextSessionName + "%')");
            
            _initialized = true;
            
            LogUtil.logInfo(_res.getProp("session.JdbcStore.msg_jdbcStoreInit",
                            _jdbcUrl, _jdbcDriver));
        }
        catch (Exception se)
        {
            LogUtil.logFailure(
                        _res.getProp("session.JdbcStore.msg_jdbcStoreFailed", se));
        }
        return _initialized;
    }

    public boolean save(IWSHttpSession session)
    {
        String id = session.getMangledId();
        PreparedStatement stmt = null;
        PreparedStatement istmt = null;
        ByteArrayOutputStream baos = null;
        ObjectOutputStream oos = null;

        try
        {
            baos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(new BufferedOutputStream(baos));
            oos.writeObject(session);
            oos.flush();                    // flush the data into baos

            stmt = _stmt_update.acquireStatement();
            
            // <ruslan>
            // Sun JDBC-ODBC bridge has a bug where setLong doesn't work
            // cut precision down to minutes in here
            // </ruslan>
            int accessTime = (int) (session.getLastAccessedTime()/(1000 * 60));
            int timeOut = session.getTimeout()/60;
            stmt.setInt(1, accessTime);
            stmt.setInt(2, timeOut);
            stmt.setBytes(3, baos.toByteArray());
            stmt.setString(4, id);
            
            int rows = stmt.executeUpdate();
            
            if (LogUtil.enableTrace)    // this is to save 1 string operation
                LogUtil.TRACE(5, "JdbcStore:.save: id = " + id + ", executeUpdate = " + rows);
            
            if (rows == 0)
            {
                istmt = _stmt_insert.acquireStatement();
                istmt.setString(1, id);
                istmt.setInt(2, accessTime);
                istmt.setInt(3, timeOut);
                istmt.setBytes(4, baos.toByteArray());
                istmt.executeUpdate();
                if (LogUtil.enableTrace)
                    LogUtil.TRACE(5, "JdbcStore:inserting session " + id);
            }
            else
            {
                if (LogUtil.enableTrace)
                    LogUtil.TRACE(5, "JdbcStore:updating session " + id);
            }
        }
        catch (Exception se)
        {
            LogUtil.logWarning(_res.getProp("session.JdbcStore.msg_exceptionThrown", "save", id, se));
        }
        finally
        {
            try
            {
                if (oos != null)
                    oos.close();         // flushes and closes
                if (baos != null)
                    baos.close();
            }
            catch (IOException ioe)
            {
                // do nothing
            }
            if (stmt != null)
                _stmt_update.release(stmt);
            if (istmt != null)
                _stmt_insert.release(istmt);
        }

        return true;
    }


    public boolean remove(IWSHttpSession session)
    {
        if (!_initialized || session == null )
            return false;
        
        String id = session.getMangledId();

        PreparedStatement stmt = null;

        try
        {
            stmt = _stmt_delete.acquireStatement();
            
            stmt.setString(1, id);
            int rows = stmt.executeUpdate();

            if (LogUtil.enableTrace)
            {
                LogUtil.TRACE(5, "JdbcStore:.deleteSession(): id = " +
                              id + ", executeUpdate = " + rows);
            }
        }
        catch (SQLException se)
        {
            // ignore it
            LogUtil.logWarning(_res.getProp("session.JdbcStore.msg_exceptionThrown", "deleteSession ()", id, se));
        }
        finally
        {
            if (stmt != null)
                _stmt_delete.release(stmt);
        }
        return true;
    }

    public IWSHttpSession load(IWSHttpSession session)
    {
        IWSHttpSession newSession = null;
        
        if (_initialized && session != null)
        {
            PreparedStatement stmt = null;
            ServletContext context = session.getServletContext();
            ObjectInputStream ois = null;

            String id = session.getMangledId();
            try
            {
                stmt = _stmt_lookup.acquireStatement();
                
                stmt.setString(1, id);
                
                ResultSet rs = stmt.executeQuery();
                if (rs.next())
                {
                    byte [] snData = rs.getBytes(1);
                    if (snData != null)
                    {
                        if (context != null)
                        {
                            ClassLoader loader = (ClassLoader) context.getAttribute(IWS_SERVLET_CLASSLOADER);

                            ois = new IWSHttpSessionInputStream(new BufferedInputStream(new ByteArrayInputStream(snData)), loader);

                        }
                        else
                        {
                            ois = new ObjectInputStream(new BufferedInputStream(new ByteArrayInputStream(snData)));
                        }

                        newSession = (IWSHttpSession)ois.readObject();
                        newSession.unsetNew();
                    }
                }
                rs.close();
            }
            catch (SQLException se)
            {
                LogUtil.logWarning(_res.getProp("session.JdbcStore.msg_exceptionThrown", "load()", id, se));
            }
            catch (Exception e)
            {
                LogUtil.logInfo(LogUtil.getStackTrace(e));
            }
            finally
            {
                if (stmt != null)
                    _stmt_lookup.release(stmt);
                try
                {
                    if (ois != null)
                        ois.close();
                }
                catch (IOException ioe)
                {
                    // do nothing
                }
            }
        }
        return newSession;
    }

    public void reap(long currentTime)
    {
        if (_initialized && _reaper_active)
        {
            try
            {
                int now = (int) (currentTime/(1000*60));
                
                _stmt_reaper.setInt(1, now);
                int rows = _stmt_reaper.executeUpdate();
                
                if (rows > 0)
                    LogUtil.logInfo(_res.getProp("session.JdbcStore.msg_sessionsReaped", new Integer(rows)));
            }
            catch (SQLException se)
            {
                LogUtil.logWarning(_res.getProp("session.JdbcStore.msg_exceptionThrown", "reaper()", "none", se));
            }
        }
    }
    
    private class StatementsPool extends ResourcePool
    {
        StatementsPool(int size, String stmtString) throws SQLException
        {
            super(size);
   
            for (int i = 0; i < size; i++)
            {
                Connection con = DriverManager.getConnection(_jdbcUrl,
                                                             _username,
                                                             _password);
                con.setAutoCommit(true);
    
                PreparedStatement stmt = con.prepareStatement(stmtString);
                release(stmt);
            }
        }
  
        public PreparedStatement acquireStatement()
        {
            return (PreparedStatement) super.acquire();
        }

        public void release(PreparedStatement stmt)
        {
            super.release ((Object) stmt);
        }
    }
}
