package org.frameworkset.schedule;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.log4j.Logger;
import org.frameworkset.spi.BeanNameAware;
import org.frameworkset.spi.DisposableBean;
import org.frameworkset.spi.InitializingBean;

public abstract class ExecutorConfigurationSupport extends CustomizableThreadFactory
		implements BeanNameAware, InitializingBean, DisposableBean {

	protected final Logger logger = Logger.getLogger(getClass());

	private ThreadFactory threadFactory = this;

	private boolean threadNamePrefixSet = false;

	private RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.AbortPolicy();

	private boolean waitForTasksToCompleteOnShutdown = false;

	private int awaitTerminationSeconds = 0;

	private String beanName;

	private ExecutorService executor;

	/**
	 * Set the ThreadFactory to use for the ExecutorService's thread pool.
	 * Default is the underlying ExecutorService's default thread factory.
	 * <p>
	 * In a Java EE 7 or other managed environment with JSR-236 support,
	 * consider specifying a JNDI-located ManagedThreadFactory: by default, to
	 * be found at "java:comp/DefaultManagedThreadFactory". Use the
	 * "jee:jndi-lookup" namespace element in XML or the programmatic
	 * {@link org.frameworkset.jndi.JndiLocatorDelegate} for convenient
	 * lookup. Alternatively, consider using Spring's
	 * {@link DefaultManagedAwareThreadFactory} with its fallback to local
	 * threads in case of no managed thread factory found.
	 * 
	 * @see java.util.concurrent.Executors#defaultThreadFactory()
	 * @see javax.enterprise.concurrent.ManagedThreadFactory
	 * @see DefaultManagedAwareThreadFactory
	 */
	public void setThreadFactory(ThreadFactory threadFactory) {
		this.threadFactory = (threadFactory != null ? threadFactory : this);
	}

	@Override
	public void setThreadNamePrefix(String threadNamePrefix) {
		super.setThreadNamePrefix(threadNamePrefix);
		this.threadNamePrefixSet = true;
	}

	/**
	 * Set the RejectedExecutionHandler to use for the ExecutorService. Default
	 * is the ExecutorService's default abort policy.
	 * 
	 * @see java.util.concurrent.ThreadPoolExecutor.AbortPolicy
	 */
	public void setRejectedExecutionHandler(RejectedExecutionHandler rejectedExecutionHandler) {
		this.rejectedExecutionHandler = (rejectedExecutionHandler != null ? rejectedExecutionHandler
				: new ThreadPoolExecutor.AbortPolicy());
	}

	/**
	 * Set whether to wait for scheduled tasks to complete on shutdown, not
	 * interrupting running tasks and executing all tasks in the queue.
	 * <p>
	 * Default is "false", shutting down immediately through interrupting
	 * ongoing tasks and clearing the queue. Switch this flag to "true" if you
	 * prefer fully completed tasks at the expense of a longer shutdown phase.
	 * <p>
	 * Note that Spring's container shutdown continues while ongoing tasks are
	 * being completed. If you want this executor to block and wait for the
	 * termination of tasks before the rest of the container continues to shut
	 * down - e.g. in order to keep up other resources that your tasks may need
	 * -, set the {@link #setAwaitTerminationSeconds "awaitTerminationSeconds"}
	 * property instead of or in addition to this property.
	 * 
	 * @see java.util.concurrent.ExecutorService#shutdown()
	 * @see java.util.concurrent.ExecutorService#shutdownNow()
	 */
	public void setWaitForTasksToCompleteOnShutdown(boolean waitForJobsToCompleteOnShutdown) {
		this.waitForTasksToCompleteOnShutdown = waitForJobsToCompleteOnShutdown;
	}

	/**
	 * Set the maximum number of seconds that this executor is supposed to block
	 * on shutdown in order to wait for remaining tasks to complete their
	 * execution before the rest of the container continues to shut down. This
	 * is particularly useful if your remaining tasks are likely to need access
	 * to other resources that are also managed by the container.
	 * <p>
	 * By default, this executor won't wait for the termination of tasks at all.
	 * It will either shut down immediately, interrupting ongoing tasks and
	 * clearing the remaining task queue - or, if the
	 * {@link #setWaitForTasksToCompleteOnShutdown
	 * "waitForTasksToCompleteOnShutdown"} flag has been set to {@code true}, it
	 * will continue to fully execute all ongoing tasks as well as all remaining
	 * tasks in the queue, in parallel to the rest of the container shutting
	 * down.
	 * <p>
	 * In either case, if you specify an await-termination period using this
	 * property, this executor will wait for the given time (max) for the
	 * termination of tasks. As a rule of thumb, specify a significantly higher
	 * timeout here if you set "waitForTasksToCompleteOnShutdown" to
	 * {@code true} at the same time, since all remaining tasks in the queue
	 * will still get executed - in contrast to the default shutdown behavior
	 * where it's just about waiting for currently executing tasks that aren't
	 * reacting to thread interruption.
	 * 
	 * @see java.util.concurrent.ExecutorService#shutdown()
	 * @see java.util.concurrent.ExecutorService#awaitTermination
	 */
	public void setAwaitTerminationSeconds(int awaitTerminationSeconds) {
		this.awaitTerminationSeconds = awaitTerminationSeconds;
	}

	@Override
	public void setBeanName(String name) {
		this.beanName = name;
	}

	/**
	 * Calls {@code initialize()} after the container applied all property
	 * values.
	 * 
	 * @see #initialize()
	 */
	@Override
	public void afterPropertiesSet() {
		initialize();
	}

	/**
	 * Set up the ExecutorService.
	 */
	public void initialize() {
		if (logger.isInfoEnabled()) {
			logger.info("Initializing ExecutorService " + (this.beanName != null ? " '" + this.beanName + "'" : ""));
		}
		if (!this.threadNamePrefixSet && this.beanName != null) {
			setThreadNamePrefix(this.beanName + "-");
		}
		this.executor = initializeExecutor(this.threadFactory, this.rejectedExecutionHandler);
	}

	/**
	 * Create the target {@link java.util.concurrent.ExecutorService} instance.
	 * Called by {@code afterPropertiesSet}.
	 * 
	 * @param threadFactory
	 *            the ThreadFactory to use
	 * @param rejectedExecutionHandler
	 *            the RejectedExecutionHandler to use
	 * @return a new ExecutorService instance
	 * @see #afterPropertiesSet()
	 */
	protected abstract ExecutorService initializeExecutor(ThreadFactory threadFactory,
			RejectedExecutionHandler rejectedExecutionHandler);

	/**
	 * Calls {@code shutdown} when the BeanFactory destroys the task executor
	 * instance.
	 * 
	 * @see #shutdown()
	 */
	@Override
	public void destroy() {
		shutdown();
	}

	/**
	 * Perform a shutdown on the underlying ExecutorService.
	 * 
	 * @see java.util.concurrent.ExecutorService#shutdown()
	 * @see java.util.concurrent.ExecutorService#shutdownNow()
	 * @see #awaitTerminationIfNecessary()
	 */
	public void shutdown() {
		if (logger.isInfoEnabled()) {
			logger.info("Shutting down ExecutorService" + (this.beanName != null ? " '" + this.beanName + "'" : ""));
		}
		if (this.waitForTasksToCompleteOnShutdown) {
			this.executor.shutdown();
		} else {
			this.executor.shutdownNow();
		}
		awaitTerminationIfNecessary();
	}

	/**
	 * Wait for the executor to terminate, according to the value of the
	 * {@link #setAwaitTerminationSeconds "awaitTerminationSeconds"} property.
	 */
	private void awaitTerminationIfNecessary() {
		if (this.awaitTerminationSeconds > 0) {
			try {
				if (!this.executor.awaitTermination(this.awaitTerminationSeconds, TimeUnit.SECONDS)) {

					logger.warn("Timed out while waiting for executor"
							+ (this.beanName != null ? " '" + this.beanName + "'" : "") + " to terminate");

				}
			} catch (InterruptedException ex) {

				logger.warn("Interrupted while waiting for executor"
						+ (this.beanName != null ? " '" + this.beanName + "'" : "") + " to terminate");

				Thread.currentThread().interrupt();
			}
		}
	}

}
