/******************************************************************************* * * Copyright (c) 2004-2009 Oracle Corporation. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * * Kohsuke Kawaguchi * * *******************************************************************************/ package hudson.lifecycle; import hudson.ExtensionPoint; import hudson.Functions; import hudson.Util; import hudson.model.Hudson; import java.io.File; import java.io.IOException; import org.apache.commons.io.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Provides the capability for starting/stopping/restarting/uninstalling Hudson. * * <p> The steps to perform these operations depend on how Hudson is launched, * so the concrete instance of this method (which is VM-wide singleton) is * discovered by looking up a FQCN from the system property "hudson.lifecycle". * * @author Kohsuke Kawaguchi * @since 1.254 */ public abstract class Lifecycle implements ExtensionPoint { private transient Logger logger = LoggerFactory.getLogger(Lifecycle.class); private static Lifecycle INSTANCE = null; /** * Gets the singleton instance. * * @return never null */ public synchronized static Lifecycle get() { if (INSTANCE == null) { Lifecycle instance; String p = System.getProperty("hudson.lifecycle"); // Do this first for better error reporting if (RestartCommandLifecycle.isConfigured()) instance = new RestartCommandLifecycle(); else if (p != null) { try { ClassLoader cl = Hudson.getInstance().getPluginManager().uberClassLoader; instance = (Lifecycle) cl.loadClass(p).newInstance(); } catch (InstantiationException e) { InstantiationError x = new InstantiationError(e.getMessage()); x.initCause(e); throw x; } catch (IllegalAccessException e) { IllegalAccessError x = new IllegalAccessError(e.getMessage()); x.initCause(e); throw x; } catch (ClassNotFoundException e) { NoClassDefFoundError x = new NoClassDefFoundError(e.getMessage()); x.initCause(e); throw x; } } else { if (Functions.isWindows()) { instance = new Lifecycle() { @Override public void verifyRestartable() throws RestartNotSupportedException { throw new RestartNotSupportedException( "Default Windows lifecycle does not support restart."); } }; } else if (System.getenv("SMF_FMRI") != null && System.getenv("SMF_RESTARTER") != null) { // when we are run by Solaris SMF, these environment variables are set. instance = new SolarisSMFLifecycle(); } else { instance = new UnixLifecycle(); } } assert instance != null; INSTANCE = instance; } return INSTANCE; } /** * If the location of <tt>hudson.war</tt> is known in this life cycle, * return it location. Otherwise return null to indicate that it is unknown. * * <p> When a non-null value is returned, Hudson will offer an upgrade UI to * a newer version. */ public File getHudsonWar() { String war = System.getProperty("executable-war"); if (war != null && new File(war).exists()) { return new File(war); } return null; } /** * Replaces hudson.war by the given file. * * <p> On some system, most notably Windows, a file being in use cannot be * changed, so rewriting <tt>hudson.war</tt> requires some special trick. * Override this method to do so. */ public void rewriteHudsonWar(File by) throws IOException { File dest = getHudsonWar(); // this should be impossible given the canRewriteHudsonWar method, // but let's be defensive if (dest == null) { throw new IOException("hudson.war location is not known."); } // backing up the old hudson.war before it gets lost due to upgrading // (newly downloaded hudson.war and 'backup' (hudson.war.tmp) are the same files // unless we are trying to rewrite hudson.war by a backup itself File bak = new File(dest.getPath() + ".bak"); if (!by.equals(bak)) { FileUtils.copyFile(dest, bak); } FileUtils.copyFile(by, dest); // we don't want to keep backup if we are downgrading if (by.equals(bak) && bak.exists()) { bak.delete(); } } /** * Can {@link #rewriteHudsonWar(File)} work? */ public boolean canRewriteHudsonWar() { // if we don't know where hudson.war is, it's impossible to replace. File f = getHudsonWar(); return f != null && f.canWrite(); } /** * If this life cycle supports a restart of Hudson, do so. Otherwise, throw * {@link UnsupportedOperationException}, which is what the default * implementation does. * * <p> The restart operation may happen synchronously (in which case this * method will never return), or asynchronously (in which case this method * will successfully return.) * * <p> Throw an exception if the operation fails unexpectedly. */ public void restart() throws IOException, InterruptedException { throw new UnsupportedOperationException(); } /** * Can the {@link #restart()} method restart Hudson? * * @throws RestartNotSupportedException If the restart is not supported, * throw this exception and explain the cause. */ public void verifyRestartable() throws RestartNotSupportedException { // the rewriteHudsonWar method isn't overridden. if (!Util.isOverridden(Lifecycle.class, getClass(), "restart")) { throw new RestartNotSupportedException("Restart is not supported in this running mode (" + getClass().getName() + ")."); } } /** * The same as {@link #verifyRestartable()} except the status is indicated * by the return value, not by an exception. */ public boolean canRestart() { try { verifyRestartable(); return true; } catch (Throwable th) { // This could happen if the native libraries are not loaded properly. // Gracefully degrade rather than throwing exception logger.info(th.getLocalizedMessage()); return false; } } /** * Return true if <code>restart</code> can be called in a safe restart. * Any lifecycle that is able to restart at all must be safe restartable, * but safe restartable lifecycles are not necessarily unsafe restartable * (without the shutdown sequence). * <p> * <code>isSafeRestartable</code> is appropriate for testing whether a * restart button or link should be shown when a configuration * option requires restart to take effect. The button must cause a * safe restart. * * @since 3.0.1 */ public boolean isSafeRestartable() { return canRestart(); } }