/* JFox, the OpenSource J2EE Application Server
 *
 * Distributable under GNU LGPL license by gun.org
 * more details please visit http://www.huihoo.org/jfox
 */

package org.jfox.ejb;

import java.rmi.NoSuchObjectException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.ejb.SessionBean;

import org.jfox.persistent.FilePersistenter;
import org.jfox.persistent.Persistenter;
import org.jfox.pool.AbstractObjectPool;
import org.jfox.pool.IndexableObjectPool;
import org.jfox.pool.ObjectFactory;
import org.jfox.pool.PoolableObject;
import org.jfox.pool.SimpleObjectPool;
import org.jfox.system.AbstractComponent;

/**
 * @author <a href="mailto:young_yy@hotmail.com">Young Yang</a>
 */

public class StatefulSessionObjectPool extends AbstractComponent implements IndexableObjectPool {
    private AbstractObjectPool pool = null;
    private ObjectFactory factory = null;
    // 在使用的对象(ObjectID <=> EJB)
    private Map caches = null;
    // 记录每个 Bean 最后返回 pool 的时间
    private Map timeouts = null;

    private StatefulSessionBucket bucket = null;

    // bean 允许的最大休眠时间
    final static long MAX_LIFE_TIME = 60 * 60 * 1000L; // one hour
    private final static long MAX_PASSIVATE_TIME = MAX_LIFE_TIME * 24 * 7; // one week

    private static StatefulSessionLifecycle lifecycleManger = new StatefulSessionLifecycle();
    private static Persistenter persistent;

    static {
        String serDir = "../temp";
        String jfoxHome = System.getProperty("jfox.home");
        if(jfoxHome != null && !jfoxHome.equals("")) {
            serDir = jfoxHome + "/temp";
        }
        persistent = new FilePersistenter(serDir);
    }

    public StatefulSessionObjectPool(StatefulSessionBucket bucket, ObjectFactory factory) {
        if(factory == null) throw new NullPointerException("factory is null.");
        this.bucket = bucket;
        this.factory = factory;
        lifecycleManger.manageIt(this);
    }

    public PoolableObject createObject() throws Exception {
        return pool.retrieveObject();
    }

    public synchronized PoolableObject retrieveObject(Object key) throws Exception {
        // 该 bean 被调用了，所以从 timeout 中删除，计时消除，在 restore 的时候，重新开始计时
        timeouts.remove(key);
        // 不在二级 cache 中，有可能已经 passivate 了
        if(!caches.containsKey(key)) {
            try {
                EJBPoolableObject pobj = activateObject(key);
                //不要立即放到caches 中,在调用完成后, restoreObject的时候再返回
//        caches.put(key,pobj);
                return pobj;
            }
            catch(Exception e) {
            }
            // 在 passivate 库中找不到，抛出异常
            throw new NoSuchObjectException(key.toString());
        }
        else {
            return (PoolableObject) caches.remove(key);
        }
    }

    /**
     * 如果池中已经有该 key 存在，返回 false，没有则返回 true
     *
     * @param key
     * @param obj
     * @return
     */
    public boolean restoreObject(Object key, PoolableObject obj) {
        // 记录最后返回的时间，时间超过 60m，将把其序列化
        timeouts.put(key, new Long(System.currentTimeMillis()));

        if(caches.containsKey(key)) {
            logger.warn("key " + key + " is existed,overwrite!");
            caches.put(key, obj);
            return false;
        }
        else {
            caches.put(key, obj);
            return true;
        }
    }

    public boolean removeObject(Object key) {
        logger.debug("removeObject with key - " + key);
        timeouts.remove(key);
        try {
            SessionBean sb = loadObject(key);
            bucket.ejbRemove(sb);
            persistent.remove(key);
            return true;
        }
        catch(Exception e) {
            logger.warn(e);
            return false;
        }
    }

    public boolean passivateObject(Object key) {
        if(!caches.containsKey(key)) return false;
        logger.debug("passivateObject with key - " + key);
        // 不要从 timeouts 移除,因为还要为 removeObject 计时
//    timeouts.remove(key);
        EJBPoolableObject pobj = (EJBPoolableObject) caches.remove(key);
        try {
            bucket.ejbPassivate((SessionBean) pobj.getBeanInstance());
            persistent.store(key, pobj.getBeanInstance());
            pobj = null;
            return true;
        }
        catch(Exception e) {
            logger.warn(e);
            return false;
        }
    }

    public EJBPoolableObject activateObject(Object key) throws Exception {
        logger.debug("activateObject with key - " + key);
        SessionBean sb = loadObject(key);
        bucket.ejbActivate(sb);
        EJBPoolableObject pobj = new EJBPoolableObject();
        pobj.setBeanInstance(sb);
        return pobj;
    }

    private SessionBean loadObject(Object key) throws Exception {
//    logger.debug("loadObject with key - " + key );
        SessionBean sb = (SessionBean) persistent.load(key);
        return sb;
    }

    public void clear() {
        synchronized(caches) {
            for(Iterator it = caches.values().iterator(); it.hasNext();) {
                EJBPoolableObject pobj = (EJBPoolableObject) it.next();
                try {
                    bucket.ejbPassivate((SessionBean) (((EJBPoolableObject) pobj).getBeanInstance()));
                }
                catch(Exception e) {
                    logger.warn("ejbRemove failed, exception is: " + e.toString());
                }
            }
            caches.clear();
        }
        timeouts.clear();
        pool.clear();
    }

    public ObjectFactory getObjectFactory() {
        return factory;
    }

    public int getWorking() {
        return caches.size();
    }

    public int getRest() {
        return pool.getRest();
    }

    protected void doInit() throws Exception {
        timeouts = new HashMap();
        caches = new HashMap();
        pool = new SimpleObjectPool(factory);
        pool.init();
    }

    protected void doDestroy() throws Exception {
        clear();
        lifecycleManger.unmanageIt(this);
        caches = null;
        pool = null;
    }

    /**
     * 对缓存的对象进行生命周期检查
     */
    public void lifecycleCheck() {
        logger.debug("Lifecycle checking... " + timeouts.size() + ", " + bucket.getEJBDescriptor().getRemoteClassName());
        long now = System.currentTimeMillis();
//    logger.info("sleep time " + now + ", " + lastActive);
        if(timeouts.size() == 0) return;
        for(Iterator it = new HashMap(timeouts).entrySet().iterator(); it.hasNext();) {
            Map.Entry entry = (Map.Entry) it.next();
            long lastActive = ((Long) entry.getValue()).longValue();

            if((now - lastActive) > MAX_PASSIVATE_TIME) {
                removeObject(entry.getKey());
                continue;
            }
            if((now - lastActive) > MAX_LIFE_TIME) {
                passivateObject(entry.getKey());
            }

        }
    }

    public static void main(String[] args) {

    }
}