/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

/**
 * <BR> <I>$Source: /cvs/glassfish/appserv-core/src/java/com/sun/ejb/containers/util/pool/NonBlockingPool.java,v $</I>
 * @author     $Author: tcfujii $
 * @version    $Revision: 1.5 $ $Date: 2007/05/05 05:33:00 $
 */
 

package com.sun.ejb.containers.util.pool;

import java.util.*;

import com.sun.ejb.containers.ContainerFactoryImpl;
import java.util.logging.*;
import java.util.TimerTask;
import com.sun.logging.*;

import com.sun.enterprise.util.Utility;
import com.sun.ejb.containers.EJBContextImpl;

import com.sun.enterprise.util.threadpool.Servicable;
import com.sun.ejb.containers.util.ContainerWorkPool;

/**
 * <p>NonBlockingPool pool provides the basic implementation of an object 
 * pool. The implementation uses a linked list to maintain a list of 
 * (available) objects. If the pool is empty it simply creates one using the
 * ObjectFactory instance. Subclasses can change this behaviour by overriding
 * getObject(...) and returnObject(....) methods. This class provides basic 
 * support for synchronization, event notification, pool shutdown
 * and pool object recycling. It also does some very basic bookkeeping like the
 * number of objects created, number of threads waiting for object.
 * <p> Subclasses can make use of these book-keeping data to provide complex 
 * pooling mechanism like LRU / MRU / Random. Also, note that AbstractPool 
 * does not have a notion of  pool limit. It is upto to the derived classes 
 * to implement these features.
 *	
 */


public class NonBlockingPool
    extends AbstractPool
{

    private String	  poolName;
    private TimerTask	  poolTimerTask;
    protected boolean	  addedResizeTask = false;
    protected boolean	  addedIdleBeanWork = false;
    protected boolean	  inResizing = false;
    private boolean	  maintainSteadySize = false;
    
    private int		  resizeTaskCount;
    private int		  timerTaskCount;

    protected NonBlockingPool() {
    }

    public NonBlockingPool(String poolName, ObjectFactory factory, 
        int steadyPoolSize, int resizeQuantity,
        int maxPoolSize, int idleTimeoutInSeconds, 
        ClassLoader loader)
    {
        this.poolName = poolName;
    	initializePool(factory, steadyPoolSize, resizeQuantity, maxPoolSize,
                       idleTimeoutInSeconds, loader);
    }

    protected void initializePool(ObjectFactory factory, int steadyPoolSize, 
        int resizeQuantity, int maxPoolSize, int idleTimeoutInSeconds,
        ClassLoader loader)
    {
        list = new ArrayList();
        
        this.factory = factory;
        this.steadyPoolSize = (steadyPoolSize <= 0) ? 0 : steadyPoolSize;
        this.resizeQuantity = (resizeQuantity <= 0) ? 0 : resizeQuantity;
        this.maxPoolSize = (maxPoolSize <= 0)
            ? Integer.MAX_VALUE : maxPoolSize;
        this.steadyPoolSize = (this.steadyPoolSize > this.maxPoolSize)
            ? this.maxPoolSize : this.steadyPoolSize;
        this.idleTimeoutInSeconds = 
            (idleTimeoutInSeconds <= 0) ? 0 : idleTimeoutInSeconds;
        
        this.containerClassLoader = loader;
        
        this.maintainSteadySize = (this.steadyPoolSize > 0);
        if ((this.idleTimeoutInSeconds > 0) && (this.resizeQuantity > 0)) {
            try {
                this.poolTimerTask =  new PoolResizeTimerTask();
                ContainerFactoryImpl.getTimer().scheduleAtFixedRate
                    (poolTimerTask, idleTimeoutInSeconds*1000, 
                     idleTimeoutInSeconds*1000);
                if(_logger.isLoggable(Level.FINE)) {
                    _logger.log(Level.FINE,
                      "[Pool-" + poolName + "]: Added PoolResizeTimerTask...");
                }
            } catch (Throwable th) {
                _logger.log(Level.WARNING,"[Pool-" + 
                            poolName + "]: Could not add"
                            + " PoolTimerTask. Continuing anyway...", th);
            }
        }
    }
    

    public void setContainerClassLoader(ClassLoader loader) {
        this.containerClassLoader = loader;
    }
    
    /**
     * Get an object. Application can use pool.getObject() to get an object
     *	instead of using new XXX().
     * @param canWait Must be true if the calling thread is willing to 
     *  wait for infinite time to get an object, false if the calling 
     *  thread does not want to wait at all.
     *	
     */
    public Object getObject(boolean canWait, Object param)
        throws PoolException
    {
        return getObject(param);
    }

    public Object getObject(long maxWaitTime, Object param)
        throws PoolException
    {
        return getObject(param);
    }

    public Object getObject(Object param)
    {
        boolean toAddResizeTask = false;
        Object obj = null;
        synchronized (list) {
            int size = list.size();
            if (size > steadyPoolSize) {
                poolSuccess++;
                return list.remove(size-1);
            } else if (size > 0) {
                poolSuccess++;
                if ((maintainSteadySize) && (addedResizeTask == false)) {
                    toAddResizeTask = addedResizeTask = true;
                    obj = list.remove(size-1);
                } else {
                    return list.remove(size-1);
                }
            } else {
                if ((maintainSteadySize) && (addedResizeTask == false)) {
                    toAddResizeTask = addedResizeTask = true;
                }
                createdCount++;	//hope that everything will be OK.
            }
        }
        
        if (toAddResizeTask) {
            addResizeTaskForImmediateExecution();
        }
        
        if (obj != null) {
            return obj;
        }
        
        try {
            return factory.create(param);
        } catch (RuntimeException th) {
            synchronized (list) {
                createdCount--;
            }
            throw th;
        }
    }
    
    private void addResizeTaskForImmediateExecution() {
        try {
            ReSizeWork work = new ReSizeWork();
            ContainerWorkPool.addLast(work);
            if(_logger.isLoggable(Level.FINE)) {
                _logger.log(Level.FINE,
                     "[Pool-" + poolName + "]: Added PoolResizeTimerTask...");
            }
            resizeTaskCount++;
        } catch (Exception ex) {
            synchronized (list) {
                addedResizeTask = false;
            }
            if(_logger.isLoggable(Level.WARNING)) {
            	_logger.log(Level.WARNING, 
                            "[Pool-"+poolName+"]: Cannot perform "
                            + " pool resize task", ex);
            }
        }
    }

    /**
     * Return an object back to the pool. An object that is obtained through
     *	getObject() must always be returned back to the pool using either 
     *	returnObject(obj) or through destroyObject(obj).
     */
    public void returnObject(Object object) {
    	synchronized (list) {
            if (list.size() < maxPoolSize) {
                list.add(object);
                return;
            } else {
                destroyedCount++;
            }
        }
        
        try {
            factory.destroy(object);
        } catch (Exception ex) {
            _logger.log(Level.FINE, "exception in returnObj", ex);             
        }
    }

    /**
     * Destroys an Object. Note that applications should not ignore 
     * the reference to the object that they got from getObject(). An object 
     * that is obtained through getObject() must always be returned back to 
     * the pool using either returnObject(obj) or through destroyObject(obj). 
     * This method tells that the object should be destroyed and cannot 
     * be reused.
     */
    public void destroyObject(Object object) {
    	synchronized (list) {
            destroyedCount++;
    	}
        
        try {
            factory.destroy(object);
        } catch (Exception ex) {
            _logger.log(Level.FINE, "exception in destroyObject", ex);
        }
    }
    
    /**
    * Preload the pool with objects.
    * @param count the number of objects to be added.
    */
    protected void preload(int count) {
    	
        ArrayList instances = new ArrayList(count);
        try {
            for (int i=0; i<count; i++) {
                instances.add(factory.create(null));
            }
    	} catch (Exception ex) {
            //Need not throw this exception up since we are pre-populating
    	}

        int sz = instances.size();
        if (sz == 0) {
            return;
        }
    	synchronized (list) {
            for (int i=0; i<sz; i++) {
                list.add(instances.get(i));
            }
            createdCount += sz;
    	}
    }

    /**
    * Prepopulate the pool with objects.
    * @param count the number of objects to be added.
    */
    public void prepopulate(int count) {
        this.steadyPoolSize = (count <= 0) ? 0 : count;
        this.steadyPoolSize = (this.steadyPoolSize > this.maxPoolSize)
            ? this.maxPoolSize : this.steadyPoolSize;
	
        if (this.steadyPoolSize > 0) {
            preload(this.steadyPoolSize);
        }
            
    }
 
    /**
    * Close the pool
    */
    public void close() {
        synchronized (list) {
            if (poolTimerTask != null) {
                try {
                    poolTimerTask.cancel();	
                    if(_logger.isLoggable(Level.FINE)) {
                        _logger.log(Level.FINE,
                            "[Pool-"+poolName+"]: Cancelled pool timer task "
                                    + " at: " + (new java.util.Date()));
                    }
                } catch (Throwable th) {
                    //Can safely ignore this!!
                }
            }
	
            if(_logger.isLoggable(Level.FINE)) {
                _logger.log(Level.FINE,"[Pool-"+poolName+"]: Destroying "
                            + list.size() + " beans from the pool...");
            }
	
            // since we're calling into ejb code, we need to set context
            // class loader
            ClassLoader origLoader = 
                Utility.setContextClassLoader(containerClassLoader);

            Object[] array = list.toArray();
            for (int i=0; i<array.length; i++) {
                try {
                    destroyedCount++;
                    try {
                        factory.destroy(array[i]);
                    } catch (Throwable th) {
                        _logger.log(Level.FINE, "exception in close", th);
                    }
                } catch (Throwable th) {
                    _logger.log(Level.WARNING,
                        "[Pool-"+poolName+"]: Error while destroying", th);
                }
            }
            if(_logger.isLoggable(Level.FINE)) {
                _logger.log(Level.FINE,"Pool-"+poolName+"]: Pool closed....");
            }
            list.clear(); 

            Utility.setContextClassLoader(origLoader);
        }

        // helps garbage collection
        this.list                  = null;
        this.factory               = null;
        this.poolTimerTask         = null;
        this.containerClassLoader  = null;
        
    }

    protected void remove(int count) {
        ArrayList removeList = new ArrayList();
        synchronized (list) {
            int size = list.size();
            for (int i=0; (i<count) && (size > 0); i++) {
                removeList.add(list.remove(--size));
                destroyedCount++;
            }
        }
        
        int sz = removeList.size();
        for (int i=0; i<sz; i++) {
            try {
                factory.destroy(removeList.get(i));
            } catch (Throwable th) {
                _logger.log(Level.FINE, "exception in remove", th);
            }
        }
    }

    protected void removeIdleObjects() {
    }
    
    protected void doResize() {
        
        //We need to set the context class loader for this (deamon) thread!!
        final Thread currentThread = Thread.currentThread();
        final ClassLoader previousClassLoader = 
            currentThread.getContextClassLoader();
        final ClassLoader ctxClassLoader = containerClassLoader;
	
        long startTime = 0;
        boolean enteredResizeBlock = false;
        try {
            if(System.getSecurityManager() == null) {
                currentThread.setContextClassLoader(ctxClassLoader);
            } else {
                java.security.AccessController.doPrivileged(
                        new java.security.PrivilegedAction() {
                    public java.lang.Object run() {
                        currentThread.setContextClassLoader(ctxClassLoader);
                        return null;
                    }
                });
            }

            if(_logger.isLoggable(Level.FINE)) {
                _logger.log(Level.FINE, 
                    "[Pool-"+poolName+"]: Resize started at: "
                  + (new java.util.Date())+" steadyPoolSize ::"+steadyPoolSize
                  + " resizeQuantity ::"+resizeQuantity+" maxPoolSize ::" +
                  maxPoolSize );
            }
            startTime = System.currentTimeMillis();

            ArrayList removeList = new ArrayList();
            int populateCount = 0;
            synchronized (list) {
                if (inResizing == true) {
                    return;
                }

                enteredResizeBlock = true;
                inResizing = true;
                
                int curSize = list.size();

                if (curSize > steadyPoolSize) {

                    //possible to reduce pool size....
                    if ((idleTimeoutInSeconds <= 0)  || 
                        (resizeQuantity <= 0)) {
                        return;
                    }
                    int victimCount = 
                        (curSize > (steadyPoolSize + resizeQuantity) )
                        ? resizeQuantity : (curSize - steadyPoolSize);
                    long allowedIdleTime = System.currentTimeMillis() -
                        idleTimeoutInSeconds*1000;
                    if(_logger.isLoggable(Level.FINE)) {
                        _logger.log(Level.FINE, 
                                    "[Pool-"+poolName+"]: Resize:: reducing "
                                    + " pool size by: " + victimCount);
                    }
                    for (int i=0; i<victimCount; i++) {
                        //removeList.add(list.remove(--curSize));
               		//destroyedCount++;
                        EJBContextImpl ctx = (EJBContextImpl) list.get(0);
                        if (ctx.getLastTimeUsed() <= allowedIdleTime) {
                            removeList.add(list.remove(0));
                            destroyedCount++;
                        } else {
                            break;
                        }
                    }
                } else if (curSize < steadyPoolSize) {

                    //Need to populate....
                    if (maintainSteadySize  == false) {
                        return;
                    }

                    if (resizeQuantity <= 0) {
                        populateCount = steadyPoolSize - curSize; 
                    } else {
                        while ((curSize + populateCount) < steadyPoolSize) {
                            populateCount += resizeQuantity;
                        }
                        if ((curSize + populateCount) > maxPoolSize) {
                            populateCount -= (curSize + populateCount) - maxPoolSize;
                        }
                    }
                }
            }
            
            if (removeList.size() > 0) {
                int sz = removeList.size();
                for (int i=0; i<sz; i++) {
                    try {
                        factory.destroy(removeList.get(i));
                    } catch (Throwable th) {
                        _logger.log(Level.FINE, "exception in doResize", th);
                    }
                }
            }

            if (populateCount > 0) {
                //preload adds items inside a sync block....

                if(_logger.isLoggable(Level.FINE)) {
                    _logger.log(Level.FINE, 
                            "[Pool-"+poolName+"]: Attempting to preload "
                            + populateCount + " beans. CurSize/MaxPoolSize: "
                            + list.size() + "/" + maxPoolSize);
                }

                preload(populateCount);

                if(_logger.isLoggable(Level.FINE)) {
                    _logger.log(Level.FINE, 
                            "[Pool-"+poolName+"]: After preload "
                            + "CurSize/MaxPoolSize: "
                            + list.size() + "/" + maxPoolSize);
                }
            }
            
            
        } catch (Throwable th) {
            _logger.log(Level.WARNING,
                        "[Pool-"+poolName+"]: Exception during reSize", th);

        } finally {
            
            if (enteredResizeBlock) {
                synchronized (list) {
                    inResizing = false;
                }
            }
            if(System.getSecurityManager() == null) {
                currentThread.setContextClassLoader(previousClassLoader);
            } else {
                java.security.AccessController.doPrivileged(
                        new java.security.PrivilegedAction() {
                    public java.lang.Object run() {
                        currentThread.setContextClassLoader(previousClassLoader);
                        return null;
                    }
                });
            }
        }

        long endTime = System.currentTimeMillis();
        if(_logger.isLoggable(Level.FINE)) {
            _logger.log(Level.FINE, 
                "[Pool-"+poolName+"]: Resize completed at: "
                + (new java.util.Date()) + "; after reSize: " + 
                getAllAttrValues());
            _logger.log(Level.FINE, "[Pool-"+poolName+"]: Resize took: "
                        + ((endTime-startTime)/1000.0) + " seconds.");
        }
    }

    public String getAllAttrValues() {
        StringBuffer sbuf = new StringBuffer("[Pool-"+poolName+"] ");
        sbuf.append("CC=").append(createdCount).append("; ")
            .append("DC=").append(destroyedCount).append("; ")
            .append("CS=").append(list.size()).append("; ")
            .append("SS=").append(steadyPoolSize).append("; ")
            .append("MS=").append(maxPoolSize).append(";");
        return sbuf.toString();
    }

    private class ReSizeWork
        implements Servicable
    {
        public void prolog() {
        }
        
    	public void service() {
            run();
        }
        
    	public void epilog() {
        }

        public void run() {
            try {
                doResize();
            } catch (Exception ex) {
                _logger.log(Level.WARNING,
                    "[Pool-"+poolName+"]: Exception during reSize", ex);
            } finally {
                synchronized (list) {
                    addedResizeTask = false;
                }
            }
        }
    }

    private class IdleBeanWork
        implements Servicable
    {
        public void prolog() {
        }
        
    	public void service() {
            run();
        }
        
    	public void epilog() {
        }
        
        public void run() {
            try {
                doResize();
            } catch (Exception ex) {
            } finally {
                addedIdleBeanWork = false;
            }
        }
    }

    private class PoolResizeTimerTask
        extends java.util.TimerTask
    {
        Object		lock;
        
        PoolResizeTimerTask() {}
        
        PoolResizeTimerTask(Object lock) {
            this.lock = lock;
        }
        
        public void run() {
            
            try {
                if (addedIdleBeanWork == true) {
                    return;
                }
                IdleBeanWork work = new IdleBeanWork();
                ContainerWorkPool.addLast(work);
                addedIdleBeanWork = true;
            } catch (Exception ex) {
                _logger.log(Level.WARNING, 
                            "[Pool-"+poolName+"]: Cannot perform "
                            + " pool idle bean cleanup", ex);
            }
	
        }
    } // End of class PoolResizeTimerTask

}
