/*****************************************************************************
 * Copyright (C) The Apache Software Foundation. All rights reserved.        *
 * ------------------------------------------------------------------------- *
 * This software is published under the terms of the Apache Software License *
 * version 1.1, a copy of which has been included  with this distribution in *
 * the LICENSE file.                                                         *
 *****************************************************************************/
package org.apache.cocoon.components.store;

import org.apache.avalon.framework.component.Component;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.logger.AbstractLoggable;
import org.apache.avalon.framework.thread.ThreadSafe;

import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.ListIterator;

/**
 * This class monitors memory usage of thr JVM.
 * It calls the <code>free()</code> method in <code>Store</code>s
 * when memory gets low to free up some.
 *
 * @author <a href="mailto:g-froehlich@gmx.de">Gerhard Froehlich</a>
 * @author <a href="mailto:dims@yahoo.com">Davanum Srinivas</a>
 * @author <a href="mailto:Christian.Schmitt@dresdner-bank.com">Christian Schmitt</a>
 *
*/
public class StoreJanitor extends AbstractLoggable
    implements Component, Configurable, Runnable, ThreadSafe {

    /**
     * The Avalon role name
     */
    final static String ROLE = "org.apache.cocoon.components.store.StoreJanitor";

    /**
     * Indicates how much memory should be left free in the JVM for
     * normal operation.
     */
    private int freememory;

    /**
     * Indicates how big the heap size can grow to before the cleanup thread kicks in.
     * The default value is based on the default maximum heap size of 64Mb.
     */
    private int heapsize;

    /**
     * Indicates the time in seconds to sleep between memory checks.
     */
    private long cleanupthreadinterval;

    /**
     * Indicates the daemon thread priority.
     */
    private int priority;

    /**
     * Store of stores
     */
    private List stores = Collections.synchronizedList(new ArrayList());

    /**
     * The Runtime
     */
    private Runtime jvm;
    
    public void configure(Configuration config) throws ConfigurationException {
        getLogger().debug("About to be configured!");
        Parameters params = Parameters.fromConfiguration(config);
        this.freememory            = params.getParameterAsInteger("freememory", 1000000);
        this.heapsize              = params.getParameterAsInteger("heapsize", 60000000);
        this.cleanupthreadinterval = params.getParameterAsInteger("cleanupthreadinterval", 10);
        this.priority              = params.getParameterAsInteger("threadpriority",Thread.currentThread().getPriority());

        if ((this.priority < 1) || (this.priority > 10)) {
          throw new ConfigurationException("StoreJanitor cleanup thread priority must be between 1 and 10!");
        }
        if ((this.cleanupthreadinterval < 1)) {
          throw new ConfigurationException("StoreJanitor cleanup thread interval must be at least 1 second!");
        }
        this.jvm = Runtime.getRuntime();
        Thread janitor = new Thread(this);
        janitor.setDaemon(true);
        janitor.setPriority(this.priority);
        janitor.setName("janitor");
        janitor.start();
    }
    
    /**
     * The background thread that monitors memory.
     * If memory is getting low, appropriate actions have to be taken.
     */
    public void run() {
        while(true) {
            if (memoryLow()) {
                ListIterator li = stores.listIterator();
                this.freeMemory(); //free memory for the loop
                while (memoryLow()) { //has to loop until memory is normal
                                      //maybe some algotithm (round robin,...)
                    Store s = (Store)li.next();
                    getLogger().debug("Calling " + s.toString() + " to free some memory");
                    s.free();
                    this.freeMemory();//free memory after every loop to determine the
                                      //memory consumption
                }
            }
            try {
              Thread.currentThread().sleep(this.cleanupthreadinterval * 1000);
            } catch (InterruptedException ignore) {}
        }
    }
    
    /**
     * Add a Store to be watched.
     */  
    public void addToSurveillance(Store store) {
        stores.add(store);
        getLogger().debug(store.toString() + " added to surveillance");
    }

    /**
     * Remove a Store from being watched.
     */
    public void removeFromSurveillance(Store store) {
        stores.remove(store);
        getLogger().debug(store.toString() + " removed from surveillance");
    }

    /**
     * Method to check if memory is running low in the jvm.
     */
    private boolean memoryLow() {
      return jvm.totalMemory() > heapsize && jvm.freeMemory() < freememory;
    }

    /**
     * Method to run finalization and the gc() in the jvm
     */
    private void freeMemory() {
       this.jvm.runFinalization();
       this.jvm.gc();
    }
}


