/*
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999 Bull S.A.
 * Contact: jonas-team@objectweb.org
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * Initial developer(s): Philippe Durieux
 * Contributor(s):
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.objectweb.jonas_ejb.container;

import java.util.LinkedList;
import java.util.NoSuchElementException;

/**
 * This class is responsible for swapping bean instances to keep
 * memory usage low enough, according to entity max-cache-size value
 * and to passivate instances on timeout when used outside transactions.
 * There is 1 swapper per Container.
 */
class Swapper extends Thread {

    private JContainer container;
    private boolean valid;	// true if still valid
    private boolean syncallbeans;

    // list of BeanFactory used to reduce cache size periodically
    private LinkedList bfList = new LinkedList();

    // list of JEntitySwitch used to pasivate instances outside transactions
    private LinkedList entityList = new LinkedList();

    // default timeout when no action required
    private final int DEFAULT_TIMEOUT = 15 * 60 * 1000; // 15 mn
    private int swapTimeout = DEFAULT_TIMEOUT;

    /**
     * constructor
     */
    public Swapper(JContainer cont) {
	super("JonasSwapper");
	valid = true;
	syncallbeans = false;
	container = cont;
    }

    public void run() {
	JEntitySwitch es  = null;
	BeanFactory bf = null;

	while (valid) {
	     // Don't keep lock while accessing beans.
	    synchronized(this) {
		if (entityList.size() == 0 && bfList.size() == 0) {
		    try {
			wait(swapTimeout);
		    } catch (InterruptedException e) {
			TraceEjb.error(getName()+": swapper interrupted");
		    } catch (Exception e) {
			TraceEjb.error(getName()+": swapper exception", e);
		    }
		}
		try {
		    es = (JEntitySwitch) entityList.removeFirst();
		} catch (NoSuchElementException e) {
		    es = null;
		}
		try {
		    bf = (BeanFactory) bfList.removeFirst();
		} catch (NoSuchElementException e) {
		    bf = null;
		}
	    }
	    if (!valid) {
		TraceEjb.verbose("Swapper thread ending");
		break;
	    }
	    if (es != null) {
		// Synchronizes this entity bean instance.
		TraceEjb.debugSwapper("Swapper found an entity bean instance to passivate");
		if (es.passivateIH(false) == false) {
		    TraceEjb.debugSwapper(getName()+" cannot swap instance (busy)");
		}
	    }
	    if (bf != null) {
		TraceEjb.debugSwapper("Swapper will try to reduce the cache");
		bf.reduceCache();
	    }
	    if (es == null && bf == null && syncallbeans) {
		// synchonize all beans on timeout if someone asked for this.
		TraceEjb.debugSwapper("Swapper will synchronize all bean instances");
		container.syncAll();
	    }
	}
    }
	
    /**
     * stop the swapper.
     */
    public synchronized void stopIt() {
	valid = false;
	notify();
    }

    /**
     * add an entity bean to the list of beans to synchronize
     */
    public synchronized void addEntity(JEntitySwitch es) {
	TraceEjb.debugSwapper("swapper addEntity");
	entityList.addLast(es);
	notify();
    }

    /**
     * add a BeanFactory to the list
     */
    public synchronized void addBeanFactory(BeanFactory bf) {
	TraceEjb.debugSwapper("swapper addBeanFactory");
	bfList.addLast(bf);
	notify();
    }

    /**
     * set timeout value
     * @args timeout in milliseconds
     */
    public synchronized void setTimeout(int t) {
	TraceEjb.debugSwapper("swapper setTimeout "+t);
	syncallbeans = true;
	if (t < swapTimeout) {
	    swapTimeout = t;
	}
	notify();
    }

    /**
     * return the timeout for this container.
     * 0 means "no timeout".
     */
    public int getTimeout() {
	if (syncallbeans) {
	    return swapTimeout;
	} else {
	    return 0;
	}
    }
}
