package com.iplanet.server.http.session;

import java.lang.*;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.Vector;
import java.io.File;
import java.io.FileInputStream;
import java.io.BufferedInputStream;
import java.io.ObjectInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.BufferedOutputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import javax.servlet.ServletContext;
import com.iplanet.server.http.util.FileFilterImpl;
import com.iplanet.server.http.servlet.NSServletLoader;
import com.iplanet.server.http.servlet.VirtualServer;

/**
 * Contains methods to send log data to error log
 */
import com.iplanet.server.http.util.LogUtil;

/**
 * Contains methods to send internationalized messages to log
 */
import com.iplanet.server.http.util.ResUtil;

/**
 * iPlanet Web Server 6.0's file session store implementation.
 *
 * @deprecated
 */
class FileStore extends SessionDataStore 
{
    /**
     * Extension for all session files
     */
    private static final String _ext = ".iws";

    /**
     * Suffix used in naming files that store session timeout information
     */
    private static final String _timeoutSuffix = "-timeout";

    /**
     * The directory in which to save session information as files
     * for persistence.
     */
    private String _dir = null;

    /**
     * FileFilter for retrieving all .iws files (in session-data-dir) that
     * contain session timeout information.
     *
     * This is used by the reap method for deleting expired sessions.
     */
    private FileFilterImpl _timeoutFilesFilter;

    /**
     * A utility class which takes care of internationalizing the
     * messages sent to the error log.
     */
    private static ResUtil _res = ResUtil.getDefaultResUtil();


    public FileStore()
    {
        super();
        _timeoutFilesFilter = new FileFilterImpl(null, _timeoutSuffix, _ext);
    }

    public FileStore(IWSHttpSessionManager mgr)
    {
        super(mgr);
    }

    public boolean init(Properties config)
    {
        boolean status = false;
        _dir = config.getProperty(VirtualServer.SESSION_DATA_DIR);
        if (_dir != null)
        {
            status = true;
            File dir = new File(_dir);
            if (dir.isDirectory() == false)
            {
                if (dir.mkdirs() == false)
                {
                    LogUtil.logInfo(_res.getProp("session.FileStore.msg_MkdirError", _dir));
                    status = false;
                }
            }

            if (status && (!dir.canWrite() || !dir.canRead()))
            {
                LogUtil.logInfo(_res.getProp("session.FileStore.msg_DirPermsError", _dir));
                status = false;
            }

            if (status == false)
                _dir = null;
            else
            {
                // append a trailing / if there isn't one
                if (!_dir.endsWith(File.separator))
                    _dir = _dir.concat(File.separator);

                if (LogUtil.enableTrace)
                    LogUtil.TRACE(7, "FileStore: dir=" + _dir);
            }
        }
        else
        {
            LogUtil.logWarning(_res.getProp("session.FileStore.msg_DirError"));
        }
        return status;
    }

    public IWSHttpSession load(IWSHttpSession session)
    {
        IWSHttpSession newSession = null;

        String id = session.getMangledId();
        String sessionFilename = getExistingSessionFile(id);
        if (sessionFilename != null)
        {
            ServletContext context = session.getServletContext();

            if (LogUtil.enableTrace)
                LogUtil.TRACE(7, "FileStore: loading from " + sessionFilename);
            newSession = loadFromFile(sessionFilename, context);
        }

        return newSession;
    }

    /**
     * Serializes the session object and saves it to a file. 
     *
     * Also saves just the timeout information in a separate file, so
     * that the reaper thread can load just these files to determine
     * which sessions are to be reaped.
     */
    public boolean save(IWSHttpSession session)
    {
        if (session == null)
            return false;


        if (_dir == null)
            return false;
        
        String id = session.getMangledId();
        String sessionFilename = getSessionFilename(id);
        String timeoutFilename = getSessionTimeoutFilename(id);

        if (sessionFilename == null)
            return false;

        ObjectOutputStream objStream = null;
        ObjectOutputStream timeoutObjStream = null;

        boolean saveStatus = false;
        try
        {
            // serialize the object and write it to disk
            objStream = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(sessionFilename)));
            objStream.writeObject(session);

            // write just the timeout information in a separate file
            timeoutObjStream = new ObjectOutputStream(new FileOutputStream(timeoutFilename));
            timeoutObjStream.writeInt(session.getTimeout());
        
            saveStatus = true;

            if (LogUtil.enableTrace)
                LogUtil.TRACE(7, "FileStore: saved to " + sessionFilename);
        }
        catch (FileNotFoundException e1)
        {
            LogUtil.logInfo(_res.getProp("session.FileStore.msg_FileNotFound",
                            sessionFilename));
        }
        catch (SecurityException e2)
        {
            LogUtil.logInfo(_res.getProp("session.FileStore.msg_AccessError",
                            sessionFilename, LogUtil.getStackTrace(e2)));
        }
        catch (Exception e3)
        {
            LogUtil.logInfo(LogUtil.getStackTrace(e3));
        }
        finally
        {
            try
            {
                if (timeoutObjStream != null)
                    timeoutObjStream.close();   // flushes and closes

                if (objStream != null)
                    objStream.close();          // flushes and closes
            }
            catch (IOException ioe)
            {
                // do nothing
            }
        }
        return saveStatus;
    }

    /**
     * Removes all the files that store information about a session.
     *
     * For each session, there are 2 files that store information about
     * it. The file that stores the serialized session object and the
     * file that stores just the timeout information for the session. Both
     * these files are deleted.
     */
    public boolean remove(IWSHttpSession session)
    {
        if (session == null)
            return false;

        String id = session.getMangledId();

        if (id == null || _dir == null)
            return false;

        return removeFiles(id);
    }

    /**
     * Do a privileged delete if a security manager is installed.
     */
    private boolean deleteFile(final File deleteThis) {
        if (System.getSecurityManager() != null) {
            Boolean ret = (Boolean)
                    AccessController.doPrivileged(
                        new PrivilegedAction() {
                            public Object run() {
                                return (new Boolean(deleteThis.delete()));
                            }
                        }
                    );
            return ret.booleanValue();
        } else {
            return deleteThis.delete();
        }
    }

    /**
     * Physically removes the files corresponding to the specified SessionID
     */
    private boolean removeFiles(String id)
    {
        boolean removeStatus = false;
        // Remove the file containing the serialized session
        String fileName = getSessionFilename(id);

        if (fileName != null)
        {
            File sessionFile = new File(fileName);
            if (sessionFile.isFile())
            {
//                removeStatus = sessionFile.delete();
                removeStatus = deleteFile(sessionFile);
                if (LogUtil.enableTrace)
                    LogUtil.TRACE(7, "FileStore: deleted " +
                                  sessionFile.getAbsolutePath());
            }
            else
            {
                LogUtil.logInfo(_res.getProp("session.FileStore.msg_FileNotFound", sessionFile.getAbsolutePath()));
            }
        }

        // Remove the file containing the timeout information
        fileName = getSessionTimeoutFilename(id);

        if (fileName != null)
        {
            File sessionFile = new File(fileName);
            if (sessionFile.isFile())
            {
//                removeStatus = sessionFile.delete();
                removeStatus = deleteFile(sessionFile);
                if (LogUtil.enableTrace)
                    LogUtil.TRACE(7, "FileStore: deleted " +
                                  sessionFile.getAbsolutePath());
            }
            else
            {
                LogUtil.logInfo(_res.getProp("session.FileStore.msg_FileNotFound", sessionFile.getAbsolutePath()));
            }
        }

        return removeStatus;
    }

    /**
     * Removes files associated with sessions that have expired.
     */
    public void reap(long currentTime)
    {
        if (_dir == null)
            return;

        File sessionDir = new File(_dir);
        File[] files = sessionDir.listFiles(_timeoutFilesFilter);

        for (int i = 0; i < files.length; i++)
        {
            String id = getSessionId(files[i].getName());

            // Deny others access to this session id as the session
            // may have expired and so the reaper will delete the associated
            // files
            _sessionMgr.lock(id);
            long elapsed = currentTime - files[i].lastModified();

            // get the session timeout (in milliseconds)
            long timeout = getTimeout(files[i].getAbsolutePath()) * 1000;

            if ((elapsed > timeout) && (timeout != Integer.MAX_VALUE))
            {
                if (LogUtil.enableTrace)
                    LogUtil.TRACE(7, "FileStore: reaped " + id);
                removeFiles(id);

                _sessionMgr.unlock(id);
                // Allow the session manager to perform cleanup related
                // to the reaped session
                _sessionMgr.reaped(id);
            }
            else
                _sessionMgr.unlock(id);   // Unexpired sessions, allow others
                                          // to access its data

        }
    }
    
    private IWSHttpSession loadFromFile(String sessionFilename,
                                        ServletContext context)
    {
        IWSHttpSession newSession = null;
        ObjectInputStream objStream = null;
        try
        {
            if (context != null)
            {
                ClassLoader loader = (ClassLoader) context.getAttribute(IWS_SERVLET_CLASSLOADER);

                objStream = new IWSHttpSessionInputStream(new BufferedInputStream(new FileInputStream(sessionFilename)), loader);
            }
            else
            {
                objStream = new ObjectInputStream(new BufferedInputStream(new FileInputStream(sessionFilename)));
            }

            newSession = (IWSHttpSession) objStream.readObject();

            if (LogUtil.enableTrace)
                LogUtil.TRACE(7, "FileStore: loaded from " + sessionFilename);
        }
        catch (FileNotFoundException e1)
        {
            if (LogUtil.enableTrace)
            {
                LogUtil.TRACE(7,
                              _res.getProp("session.FileStore.msg_FileNotFound",
                                           sessionFilename));
            }
        }
        catch (SecurityException e2)
        {
            LogUtil.logInfo(_res.getProp("session.FileStore.msg_AccessError",
                            sessionFilename, LogUtil.getStackTrace(e2)));
        }
        catch (Exception e3)
        {
            LogUtil.logInfo(LogUtil.getStackTrace(e3));
        }
        finally
        {
            try
            {
                if (objStream != null)
                    objStream.close();
            }
            catch (IOException ioe)
            {
                // do nothing
            }
        }
        return newSession;
    }

    /**
     * Returns the absolute pathname to the persistent file store for the
     * specified session object.
     *
     * Session filename = "$dir/$sessionID.iws"
     */
    private String getSessionFilename(String id)
    {
        StringBuffer sb = new StringBuffer(_dir);
        sb.append(id).append(_ext);

        return sb.toString();
    }

    /**
     * Returns the absolute pathname to the file containing the timeout
     * value for the specified session object.
     *
     * Session filename = "$dir/$sessionID-timeout.iws"
     */
    private String getSessionTimeoutFilename(String id)
    {
        StringBuffer sb = new StringBuffer(_dir);
        sb.append(id).append(_timeoutSuffix).append(_ext);

        return sb.toString();
    }

    /**
     * Returns the absolute pathname of the persistent file for the
     * specified Session ID or null if no persistent file could be found.
     */
    private String getExistingSessionFile(String id)
    {
        // Look for the file corresponding to this session
        String fileName = getSessionFilename(id);
        if (fileName != null)
        {
            File sessionFile = new File(fileName);
            if (sessionFile.isFile() == false)
                fileName = null;
        }
        
        return fileName;
    }

    /**
     * Extracts the timeout value (in seconds) from the filename of a 
     * session that has been persisted to the file system.
     */
    private int getTimeout(String filename)
    {
        int timeout = 0;
        if (filename != null)
        {
            ObjectInputStream objStream = null;
            try
            {
                objStream = new ObjectInputStream(new FileInputStream(filename));
                timeout = objStream.readInt();
            }
            catch (FileNotFoundException e1)
            {
                if (LogUtil.enableTrace)
                {
                    LogUtil.TRACE(7, _res.getProp("session.FileStore.msg_FileNotFound", filename));
                }
            }
            catch (SecurityException e2)
            {
                LogUtil.logInfo(_res.getProp("session.FileStore.msg_AccessError", filename, LogUtil.getStackTrace(e2)));
            }
            catch (Exception e3)
            {
                LogUtil.logInfo(LogUtil.getStackTrace(e3));
            }
            finally
            {
                try
                {
                    if (objStream != null)
                        objStream.close();
                }
                catch (IOException ioe)
                {
                    // do nothing
                }
            }
        }
        return timeout;
    }

    /**
     * Extract the session id from the name of the file containing timeout
     * information.
     */
    private String getSessionId(String filename)
    {
        int start = filename.indexOf(_timeoutSuffix);
        if (start != -1)
            return filename.substring(0, start);
        else
            return filename;
    }
}
