/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999 Bull S.A.
 * Contact: jonas-team@ow2.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
 *
 * --------------------------------------------------------------------------
 * $Id: Swapper.java 15428 2008-10-07 11:20:29Z sauthieg $
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.lib.ejb21;

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

import org.objectweb.util.monolog.api.BasicLevel;

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

    /**
     * My container
     */
    private JContainer container;

    /**
     * true if still valid. Will be set to false when the container is unloaded.
     */
    private boolean valid;

    /**
     * list of BeanFactory used to sync dirty instances
     * This is "CS policy" specific
     */
    private LinkedList bfsList = new LinkedList();

    /**
     * Value of the timeout to sync bean instances. (in sec.)
     * default timeout value when no action required = 15 mn.
     * never go under 1 mn.
     */
    private static final long SYNC_TIMEOUT = 30 * 1000;
    private static final long MIN_SWAP_TIMEOUT = 60 * 1000;
    private static final long MAX_SWAP_TIMEOUT = 15 * 60 * 1000;
    private long swapTimeout = MAX_SWAP_TIMEOUT;
    private long nextSync;
    private boolean waiting;


    /**
     * constructor
     * @param cont The Container
     */
    public Swapper(JContainer cont) {
        super("JonasSwapper-" + cont.getName());
        valid = true;
        container = cont;
    }

    /**
     * run method for Thread implementation.
     */
    public void run() {
        while (valid) {
            BeanFactory bfs = null;
            synchronized (this) {
                if (bfsList.size() == 0) {
                    try {
                        waiting = true;
                        wait(swapTimeout);
                        waiting = false;
                    } catch (InterruptedException e) {
                        TraceEjb.logger.log(BasicLevel.ERROR, getName() + ": swapper interrupted", e);
                    } catch (Exception e) {
                        TraceEjb.logger.log(BasicLevel.ERROR, getName() + ": swapper exception", e);
                    }
                }
                try {
                    bfs = (BeanFactory) bfsList.removeFirst();
                } catch (NoSuchElementException e) {
                    bfs = null;
                }
            }
            // The following code can be done with lock off: avoids deadlocks.

            if (bfs != null) {
                // Synchronize all dirty instances for this entity factory (CS only)
                // This happens usually at the beginning of a transaction
                bfs.syncDirty(true);
            }
            if (System.currentTimeMillis() >= nextSync) {
                // For each EntityFactory, look for passivationTimeout and inactivityTimeout
                container.syncAll(false, true);
                nextSync = System.currentTimeMillis() + swapTimeout;
            }
        }
    }

    /**
     * stop the swapper.
     */
    public synchronized void stopIt() {
        valid = false;
        notify();
    }

    /**
     * synchronizes all instances accessed outside transaction.
     * This is "CS policy" specific
     * @param bf the Bean Factory
     */
    public synchronized void addBeanFactorySync(BeanFactory bf) {
        if (!bfsList.contains(bf)) {
            if (TraceEjb.isDebugSwapper()) {
                TraceEjb.swapper.log(BasicLevel.DEBUG, "" + bfsList.size());
            }
            bfsList.addLast(bf);
            notify();
        }
    }

    /**
     * add a BeanFactory to the list
     * @param bf the Bean Factory
     */
    public synchronized void addBeanFactory(BeanFactory bf) {
        TraceEjb.swapper.log(BasicLevel.DEBUG, "");
        long curtime = System.currentTimeMillis();
        if (curtime >= nextSync) {
            // It's time to check memory
            if (waiting) {
                notify();
            }
        } else  if (nextSync - curtime > SYNC_TIMEOUT) {
            // advance next check
            nextSync = SYNC_TIMEOUT;
        }
    }

    /**
     * set timeout value
     * @param t in seconds
     */
    public synchronized void setSwapperTimeout(int t) {
        long ms = t * 1000L;
        if (ms < swapTimeout && ms >= MIN_SWAP_TIMEOUT) {
            swapTimeout = ms;
            TraceEjb.swapper.log(BasicLevel.DEBUG, "" + t);
        }
        notify();
    }

}
