/** This file is part of Waarp Project. Copyright 2009, Frederic Bregier, and individual contributors by the @author tags. See the COPYRIGHT.txt in the distribution for a full listing of individual contributors. All Waarp Project is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Waarp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Waarp . If not, see <http://www.gnu.org/licenses/>. */ package org.waarp.common.utility; import java.io.File; import java.io.IOException; import java.lang.management.ManagementFactory; import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import org.waarp.common.future.WaarpFuture; import org.waarp.common.logging.WaarpLogger; import org.waarp.common.logging.WaarpLoggerFactory; /** * @author "Frederic Bregier" * */ public abstract class WaarpShutdownHook extends Thread { /** * Internal Logger */ private static final WaarpLogger logger = WaarpLoggerFactory .getLogger(WaarpShutdownHook.class); /** * Class for argument of creation of WaarpShutdownHook * * @author "Frederic Bregier" * */ public static class ShutdownConfiguration { public long timeout = 30000; // 30s per default public WaarpFuture serviceFuture; // no service per default } /** * Set if the program is in shutdown */ private static volatile boolean shutdown = false; /** * Set if the program will start shutdown process */ private static volatile boolean shutdownStarted = false; /** * Set if the program is in shutdown */ private static volatile boolean immediate = false; /** * Set if the Handler is initialized */ private static boolean initialized = false; /** * Is the shutdown finished */ private static boolean isShutdownOver = false; /** * Thread for ShutdownHook */ public static WaarpShutdownHook shutdownHook = null; private ShutdownConfiguration shutdownConfiguration = null; public WaarpShutdownHook(ShutdownConfiguration configuration) { if (initialized) { shutdownHook.shutdownConfiguration = configuration; this.setName("WaarpShutdownHook"); this.setDaemon(true); shutdownHook = this; this.shutdownConfiguration = configuration; return; } this.shutdownConfiguration = configuration; this.setName("WaarpShutdownHook"); this.setDaemon(true); shutdownHook = this; initialized = true; } /** * For Server part */ public static void addShutdownHook() { if (shutdownHook != null) { Runtime.getRuntime().addShutdownHook(shutdownHook); } } /** * For Server part */ public static void removeShutdownHook() { if (shutdownHook != null) { Runtime.getRuntime().removeShutdownHook(shutdownHook); } } /** * Says if the Process is currently in shutdown * * @return True if already in shutdown */ public static boolean isInShutdown() { return shutdown; } /** * * @return True if the Shutdown process will start soon */ public static boolean isShutdownStarting() { return shutdownStarted; } /** * To specify that shutdown will soon start */ public static void shutdownWillStart() { shutdownStarted = true; } /** * This function is the top function to be called when the process is to be shutdown. * * @param immediateSet */ public static void terminate(boolean immediateSet) { if (immediateSet) { immediate = immediateSet; } if (shutdownHook != null) { removeShutdownHook(); terminate(); } else { logger.error("No ShutdownHook setup"); System.exit(1); } } @Override public void run() { if (isShutdownOver) { if (shutdownHook != null && shutdownHook.serviceStopped()) { try { Thread.sleep(100); } catch (InterruptedException e) { } } // Already stopped System.err.println("Halt System now - services already stopped -"); Runtime.getRuntime().halt(0); return; } try { terminate(); } catch (Throwable t) { if (shutdownHook != null && shutdownHook.serviceStopped()) { try { Thread.sleep(100); } catch (InterruptedException e) { } } } System.err.println("Halt System now"); Runtime.getRuntime().halt(0); } /** * Print stack trace * * @param thread * @param stacks */ static private void printStackTrace(Thread thread, StackTraceElement[] stacks) { System.err.print(thread.toString() + " : "); for (int i = 0; i < stacks.length - 1; i++) { System.err.print(stacks[i].toString() + " "); } if (stacks.length >= 1) System.err.println(stacks[stacks.length - 1].toString()); else System.err.println(); } /** * Finalize resources attached to handlers * * @author Frederic Bregier */ private static class ShutdownTimerTask extends TimerTask { /** * Internal Logger */ private static final WaarpLogger logger = WaarpLoggerFactory .getLogger(ShutdownTimerTask.class); /** * Constructor from type * * @param type */ private ShutdownTimerTask() { } @Override public void run() { System.err.println("Halt System now - time waiting is over"); logger.error("System will force EXIT"); if (shutdownHook != null && shutdownHook.serviceStopped()) { try { Thread.sleep(100); } catch (InterruptedException e) { } } if (logger.isDebugEnabled()) { Map<Thread, StackTraceElement[]> map = Thread .getAllStackTraces(); for (Thread thread : map.keySet()) { printStackTrace(thread, map.get(thread)); } } Runtime.getRuntime().halt(0); } } /** * Extra call to ensure exit after long delay */ public void launchFinalExit() { Timer timer = new Timer("R66FinalExit", true); ShutdownTimerTask timerTask = new ShutdownTimerTask(); timer.schedule(timerTask, shutdownConfiguration.timeout * 4); } /** * Real exit function */ protected abstract void exit(); private boolean serviceStopped() { if (shutdownConfiguration.serviceFuture != null) { logger.info("Service will be stopped"); shutdownConfiguration.serviceFuture.setSuccess(); return true; } return false; } private static String applArgs = null; private static volatile boolean shouldRestart = false; /** * Sun property pointing the main class and its arguments. * Might not be defined on non Hotspot VM implementations. */ private static final String SUN_JAVA_COMMAND = "sun.java.command"; /** * Try to return the application arguments (for Oracle VM) * * @return null if it cannot */ private static String getArgs() { String test = System.getProperty(SUN_JAVA_COMMAND); if (test != null && !test.isEmpty()) { // compute args directly // program main and program arguments String[] mainCommand = test.split(" "); // program main is a jar StringBuilder args = new StringBuilder(); if (mainCommand[0].endsWith(".jar")) { // if it's a jar, add -jar mainJar args.append("-jar ").append(new File(mainCommand[0]).getPath()); } else { // else it's a .class, add the classpath and mainClass args.append("-cp \"").append(System.getProperty("java.class.path")).append("\" ") .append(mainCommand[0]); } // finally add program arguments for (int i = 1; i < mainCommand.length; i++) { args.append(" ").append(mainCommand[i]); } return args.toString(); } return null; } /** * Called to setup main class and args to enable restart * * @param main * @param args */ public static void registerMain(Class<?> main, String[] args) { if (main == null) { applArgs = getArgs(); return; } applArgs = ManagementFactory.getRuntimeMXBean().getClassPath(); if (applArgs != null && !applArgs.isEmpty()) { applArgs = "-cp " + applArgs; } applArgs += " " + main.getName(); for (int i = 0; i < args.length; i++) { applArgs += " " + args[i]; } } /** * Restart the application using the preset applArgs and computing the jvmArgs. * execute the command in a shutdown hook, to be sure that all the * resources have been disposed before restarting the application * * @throws IOException */ private static void restartApplication() throws IOException { if (shouldRestart) { try { // java binary String java = System.getProperty("java.home") + "/bin/java"; // vm arguments List<String> vmArguments = ManagementFactory.getRuntimeMXBean().getInputArguments(); StringBuilder vmArgsOneLine = new StringBuilder(); for (String arg : vmArguments) { // if it's the agent argument : we ignore it otherwise the // address of the old application and the new one will be in conflict if (!arg.contains("-agentlib")) { vmArgsOneLine.append(arg).append(" "); } } // init the command to execute, add the vm args final StringBuilder cmd; if (DetectionUtils.isWindows()) { cmd = new StringBuilder("\"" + java + "\" " + vmArgsOneLine); } else { cmd = new StringBuilder(java + " " + vmArgsOneLine); } if (applArgs == null) { applArgs = getArgs(); } if (applArgs == null) { // big issue then System.err.println("Cannot restart!"); } cmd.append(applArgs); logger.debug("Should restart with:\n" + cmd.toString()); logger.warn("Should restart"); Runtime.getRuntime().exec(cmd.toString()); } catch (Throwable e) { // something went wrong throw new IOException("Error while trying to restart the application", e); } } } /** * Set the way software should shutdown: with (true) or without restart (false) * * @param toRestart */ public static void setRestart(boolean toRestart) { shouldRestart = toRestart; } /** * * @return True if the shutdown should be followed by a restart */ public static boolean isRestart() { return shouldRestart; } /** * Intermediary exit function */ private static void terminate() { shutdownStarted = true; shutdown = true; if (immediate) { shutdownHook.exit(); // Force exit! try { Thread.sleep(shutdownHook.shutdownConfiguration.timeout / 2); } catch (InterruptedException e) { } if (logger.isDebugEnabled()) { Map<Thread, StackTraceElement[]> map = Thread .getAllStackTraces(); for (Thread thread : map.keySet()) { printStackTrace(thread, map.get(thread)); } } isShutdownOver = true; logger.info("Should restart? " + isRestart()); try { restartApplication(); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } shutdownHook.serviceStopped(); System.err.println("Halt System"); try { Thread.sleep(1000); } catch (InterruptedException e) { } Runtime.getRuntime().halt(0); } else { shutdownHook.launchFinalExit(); immediate = true; shutdownHook.exit(); isShutdownOver = true; shutdownHook.serviceStopped(); logger.info("Should restart? " + isRestart()); try { restartApplication(); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } logger.info("Exit System"); System.err.println("Exit System"); //Runtime.getRuntime().halt(0); } } }