ExecutorServices.java

/*******************************************************************************
 * Copyright 2013 André Rouél
 * 
 * Licensed 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 net.sf.uadetector.internal.util;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;

import net.sf.qualitycheck.Check;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This utility is intended to provide predefined {@link ExecutorService}s which runs in background and can be easily
 * shut-downed within {@link #shutdownAll()} if necessary.
 * 
 * @author André Rouél
 */
public final class ExecutorServices {

	/**
	 * This synchronized {@link Set} is a registry of all distributed background executors by this utility.
	 * <p>
	 * The containing {@link ExecutorService}s will be used to run a concrete update of <i>UAS data</i> instantly in
	 * background.
	 */
	private static final Set<ExecutorService> BACKGROUND_EXECUTORS = Collections.synchronizedSet(new HashSet<ExecutorService>(3));

	/**
	 * Default name of a thread which will be used to run a concrete update within a background executor
	 */
	private static final String DEFAULT_BACKGROUND_EXECUTOR_NAME = "update-operation";

	/**
	 * Default name of a thread which will be created within a scheduler
	 */
	private static final String DEFAULT_SCHEDULER_NAME = "update-scheduler";

	/**
	 * Corresponding logger for this class
	 */
	private static final Logger LOG = LoggerFactory.getLogger(ExecutorServices.class);

	/**
	 * This synchronized {@link Set} is a registry of all distributed schedulers by this utility.
	 * <p>
	 * The containing {@link ScheduledExecutorService}s will be used to schedule commands which updates the <i>UAS
	 * data</i> in defined intervals.
	 */
	private static final Set<ScheduledExecutorService> SCHEDULERS = Collections.synchronizedSet(new HashSet<ScheduledExecutorService>(3));

	/**
	 * Timeout (in seconds) to shutdown all available executors at the latest
	 */
	public static final long SHUTDOWN_DURATION = 5;

	/**
	 * Creates a single-threaded executor that is registered by this class in order to shut it down later (when it
	 * becomes necessary).
	 * 
	 * @return a new background executor
	 */
	public static ExecutorService createBackgroundExecutor() {
		final ExecutorService executor = Executors.newSingleThreadExecutor(new DaemonThreadFactory(DEFAULT_BACKGROUND_EXECUTOR_NAME));
		BACKGROUND_EXECUTORS.add(executor);
		return executor;
	}

	/**
	 * Creates a single-threaded scheduler that is registered by this class in order to shut it down later (when it
	 * becomes necessary).
	 * 
	 * @return a new scheduler
	 */
	public static ScheduledExecutorService createScheduler() {
		final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, new DaemonThreadFactory(DEFAULT_SCHEDULER_NAME));
		SCHEDULERS.add(scheduler);
		return scheduler;
	}

	/**
	 * Shutdowns the given {@code ExecutorService} as soon as possible, but not later than the specified default time
	 * (which is {@value #SHUTDOWN_DURATION} seconds).
	 * 
	 * @param executorService
	 *            executor to stop
	 */
	public static void shutdown(@Nonnull final ExecutorService executorService) {
		Check.notNull(executorService, "executorService");
		shutdown(executorService, SHUTDOWN_DURATION, TimeUnit.SECONDS);
	}

	/**
	 * Shutdowns the given {@code ExecutorService} as soon as possible, but not later than the specified time.
	 * 
	 * @param executorService
	 *            executor to stop
	 * @param duration
	 *            duration as a numerical value
	 * @param unit
	 *            duration unit
	 */
	public static void shutdown(@Nonnull final ExecutorService executorService, @Nonnegative final long duration,
			@Nonnull final TimeUnit unit) {
		Check.notNull(executorService, "executorService");
		Check.notNull(duration, "duration");
		Check.notNull(unit, "unit");

		executorService.shutdown();
		try {
			if (!executorService.awaitTermination(duration, unit)) {
				LOG.info(String.format("Executor did not terminate in %s %s.", duration, unit.name().toLowerCase()));
				final List<Runnable> droppedTasks = executorService.shutdownNow();
				LOG.info("Executor was abruptly shut down. " + droppedTasks.size() + " tasks will not be executed.");
			}
			unregisterIfPossible(executorService);
		} catch (final InterruptedException e) {
			LOG.warn("Executor termination failed: " + e.getLocalizedMessage(), e);
		}
	}

	/**
	 * Shuts down all registered scheduler and background workers as soon as possible, but at the latest in specified
	 * {@link #SHUTDOWN_DURATION} seconds.
	 */
	public static void shutdownAll() {
		for (final ExecutorService executor : new ArrayList<ExecutorService>(BACKGROUND_EXECUTORS)) {
			shutdown(executor);
			BACKGROUND_EXECUTORS.remove(executor);
		}
		for (final ScheduledExecutorService scheduler : new ArrayList<ScheduledExecutorService>(SCHEDULERS)) {
			shutdown(scheduler);
			SCHEDULERS.remove(scheduler);
		}
	}

	/**
	 * Unregisters the given {@code ExecutorService} if it is an instance of {@code ScheduledExecutorService} from the
	 * list of registered schedulers.
	 * 
	 * @param executorService
	 *            a possible scheduler
	 */
	private static void unregisterIfPossible(final ExecutorService executorService) {
		if (executorService instanceof ScheduledExecutorService) {
			SCHEDULERS.remove(executorService);
		} else {
			BACKGROUND_EXECUTORS.remove(executorService);
		}
	}

	/**
	 * <strong>Attention:</strong> This class is not intended to create objects from it.
	 */
	private ExecutorServices() {
		// This class is not intended to create objects from it.
	}

}