package org.maachang.comet.httpd.engine.session ;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;

import org.maachang.util.FileUtil;
import org.maachang.util.RandomUtil;

/**
 * セッション管理.
 *
 * @version 2007/08/20
 * @author  masahito suzuki
 * @since   MaachangComet 1.00
 */
public class HttpdSessionManager {
    
    /**
     * セッション文字数.
     */
    public static final int SESSION_ID_LENGTH = 48 ;
    
    /**
     * デフォルトセッション時間.
     */
    private static final long DEF_DELETE_TIME = 1800000L ;
    
    /**
     * セッション要素群.
     */
    private HashMap<String,HttpdSession> sessions = null ;
    
    /**
     * セッション削除時間.
     */
    private long deleteSessionTime = -1L ;
    
    /**
     * スレッド監視用.
     */
    private SessionMonitorThread SessionMonitorThread = null ;
    
    /**
     * コンストラクタ.
     */
    public HttpdSessionManager() {
        this( DEF_DELETE_TIME ) ;
    }
    
    /**
     * コンストラクタ.
     * <BR><BR>
     * セッション条件を設定してオブジェクトを生成します.
     * <BR>
     * @param deleteSessionTime 対象のセッション削除時間を設定します.
     */
    public HttpdSessionManager( long deleteSessionTime ) {
        if( deleteSessionTime <= 0 ) {
            deleteSessionTime = DEF_DELETE_TIME ;
        }
        this.deleteSessionTime = deleteSessionTime ;
        this.sessions = new HashMap<String,HttpdSession>() ;
        this.SessionMonitorThread = new SessionMonitorThread( this ) ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() {
        this.destroy() ;
    }
    
    /**
     * オブジェクト破棄.
     * <BR><BR>
     * オブジェクトを破棄します.
     */
    public void destroy() {
        sessions = null ;
        deleteSessionTime = -1L ;
        if( SessionMonitorThread != null ) {
            SessionMonitorThread.setEndFlag() ;
        }
        SessionMonitorThread = null ;
    }
    
    /**
     * 新しいセッションを発行.
     * <BR><BR>
     * 新しいセッションを発行します.
     * <BR>
     * @return HttpdSession 新しいセッション情報が返されます.
     */
    public synchronized HttpdSession getNewSession() {
        String sessionId = null ;
        for( ;; ) {
            sessionId = RandomUtil.randomString( SESSION_ID_LENGTH,true,true,true ) ;
            if( sessions.get( sessionId ) == null ) {
                break ;
            }
            sessionId = null ;
        }
        HttpdSession session = new HttpdSession( sessionId ) ;
        sessions.put( sessionId,session ) ;
        return session ;
    }
    
    /**
     * セッションIDを指定して、セッションを取得.
     * <BR><BR>
     * セッションIDを指定して、セッションを取得します.
     * <BR>
     * @param id 対象のセッションIDを設定します.
     * @return HttpdSession 対象のセッション情報が返されます.
     */
    public synchronized HttpdSession getSession( String id ) {
        if( sessions.get( id ) == null ) {
            HttpdSession session = new HttpdSession( id ) ;
            sessions.put( id,session ) ;
            return session ;
        }
        return sessions.get( id ) ;
    }
    
    /**
     * 対象セッションを削除.
     * <BR><BR>
     * 対象のセッションを削除します.
     * <BR>
     * @param id 対象のセッションIDを設定します.
     */
    public synchronized void removeSession( String id ) {
        sessions.remove( id ) ;
    }
    
    /**
     * セッション削除時間を取得.
     * <BR><BR>
     * セッション削除時間を取得します.
     * <BR>
     * @return long セッション削除時間が返されます.
     */
    public synchronized long getDeleteTime() {
        return deleteSessionTime ;
    }
    
    /**
     * セッション数を取得.
     * <BR><BR>
     * 現在のセッション数を取得します.
     * <BR>
     * @return int セッション数を取得します.
     */
    public synchronized int size() {
        return sessions.size() ;
    }
    
    /**
     * セッションID一覧を取得.
     * <BR><BR>
     * セッションID一覧を取得します.
     * <BR>
     * @return String[] セッションID一覧が返されます.
     */
    protected synchronized String[] getSessionIds() {
        if( sessions != null && sessions.size() > 0 ) {
            int len = sessions.size() ;
            Object[] objs = sessions.keySet().toArray() ;
            String[] ret = new String[ len ] ;
            for( int i = 0 ; i < len ; i ++ ) {
                ret[ i ] = ( String )objs[ i ] ;
            }
            return ret ;
        }
        return null ;
    }
    
    /**
     * 指定ファイルからセッション情報をロード.
     * <BR><BR>
     * 指定ファイルからセッション情報をロードします.
     * <BR>
     * @param name 対象のファイル名を設定します.
     * @exception Exception 例外.
     */
    public synchronized void loadSession( String name )
        throws Exception {
        BufferedInputStream bi = null ;
        try {
            if( FileUtil.isFileExists( name ) == false ||
                FileUtil.isRead( name ) == false ) {
                return ;
            }
            SessionMonitorThread.setStopFlag( true ) ;// 監視スレッド停止.
            sleep() ;
            bi = new BufferedInputStream( new FileInputStream( name ) ) ;
            // 先頭4バイト情報を読み込みに失敗.
            if( bi.read() == -1 || bi.read() == -1 || bi.read() == -1 || bi.read() == -1 ) {
                // 情報は存在しないものとする.
                return ;
            }
            // 情報がなくなるまで読み込む.
            for( ;; ) {
                HttpdSession session = HttpdSession.loadSession( bi ) ;
                if( session == null ) {
                    break ;
                }
                if( sessions == null ) {
                    sessions = new HashMap<String,HttpdSession>() ;
                }
                sessions.put( session.getSessionId(),session ) ;
            }
            bi.close() ;
            bi = null ;
        } catch( Exception e ) {
            throw e ;
        } finally {
            SessionMonitorThread.setStopFlag( false ) ;// 監視スレッド再開.
            if( bi != null ) {
                try {
                    bi.close() ;
                } catch( Exception e ) {
                }
            }
            bi = null ;
        }
    }
    
    /**
     * 指定ファイルにセッション情報を保存.
     * <BR><BR>
     * 指定ファイルにセッション情報を保存します.
     * <BR>
     * @param name 対象のファイル名を設定します.
     * @exception Exception 例外.
     */
    public synchronized void saveSession( String name )
        throws Exception {
        this.saveSession( false,name ) ;
    }
    
    /**
     * 指定ファイルにセッション情報を保存.
     * <BR><BR>
     * 指定ファイルにセッション情報を保存します.
     * <BR>
     * @param mode [true]の場合、セッション保存後、セッション監視スレッドを開始します.
     * @param name 対象のファイル名を設定します.
     * @exception Exception 例外.
     */
    public synchronized void saveSession( boolean mode,String name )
        throws Exception {
        BufferedOutputStream bo = null ;
        try {
            // 書き込みできない場合.
            if( FileUtil.isFileExists( name ) == true &&
                FileUtil.isWrite( name ) == false ) {
                throw new IOException( "指定ファイル[" + name + 
                    "]は、書き込めません" ) ;
            }
            if( mode == true ) {
                // セッション監視スレッドをストップ.
                SessionMonitorThread.setStopFlag( true ) ;
            }
            else {
                // セッション監視スレッドを停止.
                SessionMonitorThread.setEndFlag() ;
            }
            
            // 少し停止.
            sleep() ;
            bo = new BufferedOutputStream( new FileOutputStream( name ) ) ;
            
            // セッション保存開始.
            String[] names = getSessionIds() ;
            // セッション内容が存在しない場合.
            if( names == null || names.length <= 0 ) {
                // セッション情報が存在しない場合.
                bo.write( new byte[]{ 0,0,0,0 } ) ;
                bo.flush() ;
                bo.close() ;
                bo = null ;
                return ;
            }
            
            // 各要素を保存.
            int len = names.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                HttpdSession value = sessions.get( names[ i ] ) ;
                value.saveSessoin( i,bo ) ;
            }
            
            bo.flush() ;
            bo.close() ;
            bo = null ;
        } catch( Exception e ) {
            throw e ;
        } finally {
            if( bo != null ) {
                try {
                    bo.close() ;
                } catch( Exception e ) {
                }
            }
            // セッション監視スレッドを開始.
            if( mode == true ) {
                SessionMonitorThread.setStopFlag( false ) ;
            }
        }
    }
    
    private static final void sleep() {
        try { Thread.sleep( 500L ) ; } catch( Exception e ) {}
    }
    
}

/**
 * セッション監視スレッド.
 */
class SessionMonitorThread extends Thread {
    private static final long NO_DATA_SESSION_TIMEOUT = 30000L ;
    private static final long WAIT = 5000 ;
    private HttpdSessionManager man = null ;
    private volatile boolean endFlag = false ;
    private volatile boolean stopFlag = false ;
    private SessionMonitorThread() {
    }
    public SessionMonitorThread( HttpdSessionManager man ) {
        this.man = man ;
        this.setDaemon( true ) ;
        this.start() ;
    }
    public synchronized void setEndFlag() {
        this.endFlag = true ;
    }
    public synchronized boolean isEndFlag() {
        return this.endFlag ;
    }
    public synchronized void setStopFlag( boolean mode ) {
        this.stopFlag = mode ;
    }
    public synchronized boolean isStopFlag() {
        return this.stopFlag ;
    }
    public void run() {
        for( ;; ) {
            try {
                try { Thread.sleep( WAIT ) ; } catch( Exception e ){}
                if( this.isEndFlag() == true ) {
                    break ;
                }
                if( this.isStopFlag() == true ) {
                    continue ;
                }
                String[] sessionId = this.man.getSessionIds() ;
                if( sessionId != null ) {
                    int len = sessionId.length ;
                    for( int i = 0 ; i < len ; i ++ ) {
                        if( this.isStopFlag() == true || this.isStopFlag() == true ) {
                            break ;
                        }
                        try {
                            HttpdSession cm = this.man.getSession( sessionId[ i ] ) ;
                            long updateTime = cm.getUpdateDate().getTime() ;
                            long now = System.currentTimeMillis() ;
                            if( cm.size() <= 0 ) {
                                if( updateTime + NO_DATA_SESSION_TIMEOUT <= now ) {
                                    this.man.removeSession( sessionId[ i ] ) ;
                                }
                            }
                            if( updateTime + this.man.getDeleteTime() <= now ) {
                                this.man.removeSession( sessionId[ i ] ) ;
                            }
                        } catch( Exception e ) {
                        }
                    }
                }
            } catch( Exception e ) {
            } catch( OutOfMemoryError er ) {
            }
        }
    }
}
