package de.invesdwin.util.shutdown; import java.util.HashMap; import java.util.Map; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; import de.invesdwin.util.assertions.Assertions; /** * Registers an internal Thread as a ShutdownHook in the JVM that runs the given callback on shutdown. * * @author subes */ @ThreadSafe public final class ShutdownHookManager { public static final ShutdownHookManager INSTANCE = new ShutdownHookManager(); @GuardedBy("INSTANCE") private static final Map<IShutdownHook, ShutdownHookThread> REGISTERED_HOOKS = new HashMap<IShutdownHook, ShutdownHookThread>(); private static volatile boolean shuttingDown; static { /** * At least one ShutdownHook is needed for the flag to be working. */ register(new IShutdownHook() { @Override public void shutdown() throws Exception {} }); } private ShutdownHookManager() {} /** * Instead of using the linked hack, we use the ShutdownHookManager. The first started ShutdownHook sets this flag. * There is always at least one ShutdownHook registered in the JVM. * * @see <a href="http://www.seropian.eu/2009/10/how-to-know-when-java-virtual-machine.html#answer">Hacky * alternative</a> */ public static boolean isShuttingDown() { return shuttingDown; } public static void register(final IShutdownHook hook) { synchronized (INSTANCE) { if (isShuttingDown()) { //too late return; } final ShutdownHookThread thread = new ShutdownHookThread(hook); Assertions.assertThat(REGISTERED_HOOKS.put(hook, thread)) .as("Hook [%s] has already been registered!", hook) .isNull(); Runtime.getRuntime().addShutdownHook(thread); } } public static void unregister(final IShutdownHook hook) { synchronized (INSTANCE) { if (isShuttingDown()) { //too late return; } final ShutdownHookThread removedThread = REGISTERED_HOOKS.remove(hook); Assertions.assertThat(shuttingDown || removedThread != null) .as("Hook [%s] was never registered!", hook) .isTrue(); Assertions.assertThat(Runtime.getRuntime().removeShutdownHook(removedThread)).isTrue(); } } /** * Is threadsafe via encapsulation. * * @author subes */ @ThreadSafe private static class ShutdownHookThread extends Thread { private final IShutdownHook shutdownable; ShutdownHookThread(final IShutdownHook shutdownable) { this.shutdownable = shutdownable; } @Override public void run() { shuttingDown = true; try { shutdownable.shutdown(); } catch (final Exception e) { getUncaughtExceptionHandler().uncaughtException(this, e); } } } }