package org.maachang.dbm.service ;

import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.maachang.dbm.MDbm;
import org.maachang.util.thread.LoopThread;


/**
 * キーセッション情報を管理.
 * 
 * @version 2008/05/25
 * @author masahito suzuki
 * @since MaachangDBM 1.10
 */
class KeySessionManager {
    
    /**
     * 監視タイム.
     */
    private static final long TIME = 180000L ;
    
    /**
     * 管理テーブル.
     */
    private Map<Long,KeySessionChild> map = null ;
    
    /**
     * 監視モニタ.
     */
    private KeySessionMonThread thread = null ;
    
    /**
     * コンストラクタ.
     */
    public KeySessionManager() throws Exception {
        map = Collections.synchronizedMap( new HashMap<Long,KeySessionChild>() ) ;
        thread = new KeySessionMonThread( map,TIME ) ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        destroy() ;
    }
    
    /**
     * オブジェクト破棄.
     */
    public void destroy() {
        if( thread != null ) {
            thread.stopThread() ;
        }
        thread = null ;
        map = null ;
    }
    
    /**
     * 情報を削除.
     * @param id 対象のIDを設定します.
     * @exception Exception 例外.
     */
    public synchronized void remove( long id ) throws Exception {
        KeySessionChild ch = map.remove( new Long( id ) ) ;
        if( ch != null ) {
            MDbm mdbm = ch.getMDbm() ;
            if( mdbm != null ) {
                try {
                    mdbm.rollback() ;
                } catch( Exception e ) {
                }
                try {
                    mdbm.close() ;
                } catch( Exception e ) {
                }
            }
        }
    }
    
    /**
     * トランザクションMDBM情報を追加.
     * @param id セッションIDを設定します.
     * @param value 対象のトランザクションMDBMを設定します.
     * @exception Exception 例外.
     */
    public synchronized void putMDbm( long id,MDbm value ) throws Exception {
        if( id <= 0 || value == null || value.isUse() == false ) {
            throw new IllegalArgumentException( "セッション登録条件は不正です" ) ;
        }
        Long key = new Long( id ) ;
        KeySessionChild ch = map.get( key ) ;
        if( ch == null ) {
            ch = new KeySessionChild() ;
            map.put( key,ch ) ;
        }
        ch.setMDbm( value ) ;
    }
    
    /**
     * トランザクションMDBM情報を削除.
     * @param id 対象のIDを設定します.
     * @exception Exception 例外.
     */
    public synchronized void removeMDbm( long id ) throws Exception {
        KeySessionChild ch = map.get( new Long( id ) ) ;
        if( ch != null ) {
            MDbm mdbm = ch.getMDbm() ;
            if( mdbm != null ) {
                try {
                    mdbm.rollback() ;
                } catch( Exception e ) {
                }
                try {
                    mdbm.close() ;
                } catch( Exception e ) {
                }
            }
            ch.setMDbm( null ) ;
        }
    }
    
    /**
     * トランザクションMDBM情報を取得.
     * @param id セッションIDを設定します.
     * @return MDbm トランザクションMDBMが返されます.
     * @exception Exception 例外.
     */
    public synchronized MDbm getMDbm( long id ) throws Exception {
        KeySessionChild ch = map.get( new Long( id ) ) ;
        if( ch == null ) {
            return null ;
        }
        return ch.getMDbm() ;
    }
    
    /**
     * キー情報を追加.
     * @param id セッションIDを設定します.
     * @param value 対象の要素を設定します.
     * @exception Exception 例外.
     */
    public synchronized void putKey( long id,Enumeration<byte[]> value ) throws Exception {
        if( id <= 0 || value == null ) {
            throw new IllegalArgumentException( "セッション登録条件は不正です" ) ;
        }
        Long key = new Long( id ) ;
        KeySessionChild ch = map.get( key ) ;
        if( ch == null ) {
            ch = new KeySessionChild() ;
            map.put( key,ch ) ;
        }
        ch.setKey( value ) ;
    }
    
    /**
     * キー情報を削除.
     * @param id 対象のIDを設定します.
     * @exception Exception 例外.
     */
    public synchronized void removeKey( long id ) throws Exception {
        KeySessionChild ch = map.get( new Long( id ) ) ;
        if( ch != null ) {
            ch.setKey( null ) ;
        }
    }
    
    /**
     * キー情報を取得.
     * @param id セッションIDを設定します.
     * @return Enumeration<byte[]> キー格納要素が返されます.
     * @exception Exception 例外.
     */
    public synchronized Enumeration<byte[]> getKey( long id ) throws Exception {
        KeySessionChild ch = map.get( new Long( id ) ) ;
        if( ch == null ) {
            return null ;
        }
        return ch.getKey() ;
    }
}

class KeySessionChild {
    private MDbm mdbm = null ;
    private Enumeration<byte[]> eum = null ;
    private long lastUpdate = -1L ;
    
    public KeySessionChild() {
        this.lastUpdate = System.currentTimeMillis() ;
    }
    
    public synchronized void setMDbm( MDbm mdbm ) {
        this.lastUpdate = System.currentTimeMillis() ;
        this.mdbm = mdbm ;
    }
    
    public synchronized MDbm getMDbm() {
        this.lastUpdate = System.currentTimeMillis() ;
        return this.mdbm ;
    }
    
    public synchronized void setKey( Enumeration<byte[]> eum ) {
        this.lastUpdate = System.currentTimeMillis() ;
        this.eum = eum ;
    }
    
    public synchronized Enumeration<byte[]> getKey() {
        this.lastUpdate = System.currentTimeMillis() ;
        return this.eum ;
    }
    
    public synchronized long getLastUpdate() {
        return this.lastUpdate ;
    }
}

/**
 * タイムアウト監視スレッド.
 */
class KeySessionMonThread extends LoopThread {
    private long timeout = -1L ;
    private Map<Long,KeySessionChild> map = null ;
    
    private KeySessionMonThread() {
        
    }
    
    public KeySessionMonThread( Map<Long,KeySessionChild> map,long timeout )
        throws Exception {
        this.map = map ;
        this.timeout = timeout ;
        startThread() ;
    }
    
    protected void clear() {
        this.map = null ;
    }
    
    protected boolean execution() throws Exception {
        if( map.size() <= 0 ) {
            Thread.sleep( 500 ) ;
        }
        else {
            for( Iterator<Long> keys = map.keySet().iterator() ; keys.hasNext() ; ) {
                Thread.sleep( 50 ) ;
                Long key = keys.next() ;
                KeySessionChild ch = map.get( key ) ;
                if( ch.getLastUpdate() + timeout <= System.currentTimeMillis() ) {
                    MDbm mdbm = ch.getMDbm() ;
                    if( mdbm != null ) {
                        try {
                            mdbm.rollback() ;
                        } catch( Exception e ) {
                        }
                        try {
                            mdbm.close() ;
                        } catch( Exception e ) {
                        }
                    }
                    keys.remove() ;
                }
            }
        }
        return false ;
    }
    
}
