/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */


package org.apache.axis2.util.threadpool;

import org.apache.axis2.AxisFault;
import org.apache.axis2.i18n.Messages;
import org.apache.axis2.java.security.AccessController;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * This the thread pool for axis2. This class will be used a singleton
 * across axis2 engine. <code>ThreadPool</code> is accepts <code>AxisWorkers</code> which has
 * run method on them and execute this method, using one of the threads
 * in the thread pool.
 */
public class ThreadPool implements ThreadFactory {
    private static final Log log = LogFactory.getLog(ThreadPool.class);
    protected static long SLEEP_INTERVAL = 1000;
    private boolean shutDown;
    protected ThreadPoolExecutor executor;

    //integers that define the pool size, with the default values set.
    private int corePoolSize = 5;
    //max pool size is set to 100 to control the maximum number of threads created
    private int maxPoolSize = 100;

    public ThreadPool() {
        setExecutor(createDefaultExecutor("Axis2 Task", Thread.NORM_PRIORITY, true));
    }

    public ThreadPool(int corePoolSize, int maxPoolSize) {
        this.corePoolSize = corePoolSize;
        this.maxPoolSize = maxPoolSize;
        setExecutor(createDefaultExecutor("Axis2 Task", Thread.NORM_PRIORITY, true));
    }

    public Executor getExecutor() {
        return executor;
    }

    public void setExecutor(ThreadPoolExecutor executor) {
        this.executor = executor;
    }

    public void execute(Runnable worker) {
        if (shutDown) {
            throw new RuntimeException(Messages.getMessage("threadpoolshutdown"));
        }
        executor.execute(worker);
    }

    /**
     * A forceful shutdown mechanism for thread pool.
     */
    public void forceShutDown() {
        if (log.isDebugEnabled()) {
            log.debug("forceShutDown called. Thread workers will be stopped");
        }
        executor.shutdownNow();
    }

    /**
     * This is the recommended shutdown method for the thread pool
     * This will wait till all the workers that are already handed over to the
     * thread pool get executed.
     *
     * @throws org.apache.axis2.AxisFault
     */
    public void safeShutDown() throws AxisFault {
        synchronized (this) {
            shutDown = true;
        }

        executor.shutdown();
    }

    protected ThreadPoolExecutor createDefaultExecutor(final String name,
                                                       final int priority,
                                                       final boolean daemon) {
        ThreadPoolExecutor rc;
        if (maxPoolSize == Integer.MAX_VALUE) {
            rc = new ThreadPoolExecutor(corePoolSize, maxPoolSize, 10,
                    TimeUnit.SECONDS, new SynchronousQueue(),
                    new DefaultThreadFactory(name, daemon, priority));
        } else {
            rc = new ThreadPoolExecutor(corePoolSize, maxPoolSize, 10,
                    TimeUnit.SECONDS, new LinkedBlockingQueue(),
                    new DefaultThreadFactory(name, daemon, priority));
        }
        rc.allowCoreThreadTimeOut(true);
        return rc;
    }

    private static class DefaultThreadFactory implements java.util.concurrent.ThreadFactory {
        private final String name;
        private final boolean daemon;
        private final int priority;

        public DefaultThreadFactory(String name, boolean daemon, int priority) {
            this.name = name;
            this.daemon = daemon;
            this.priority = priority;
        }

        public Thread newThread(
                final Runnable runnable) {
            // do the following section as privileged
            // so that it will work even when java2 security
            // has been enabled
            Thread returnThread = null;
            try {
                returnThread = (Thread)
                        AccessController.doPrivileged(new PrivilegedExceptionAction<Thread>() {
                            public Thread run() {
                                Thread newThread =
                                        new Thread(runnable, name);
                                newThread.setDaemon(daemon);
                                newThread.setPriority(priority);
                                return newThread;
                            }
                        }
                        );
            }
            catch (PrivilegedActionException e) {
                // note: inner class can't have its own static log variable
                if (log.isDebugEnabled()) {
                    log.debug("ThreadPoolExecutor.newThread():   Exception from AccessController [" + e.getClass()
                            .getName() + "]  for [" + e.getMessage() + "]", e);
                }
            }
            return returnThread;

        }
    }
}
