package jenkins.management; import hudson.AbortException; import hudson.console.AnnotatedLargeText; import hudson.model.AdministrativeMonitor; import hudson.model.TaskListener; import hudson.security.ACL; import hudson.util.StreamTaskListener; import jenkins.model.Jenkins; import jenkins.security.RekeySecretAdminMonitor; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nonnull; /** * Convenient partial implementation of {@link AdministrativeMonitor} that involves a background "fixing" action * once the user opts in for the execution of it. * * <p> * A subclass defines what that background fixing actually does in {@link #fix(TaskListener)}. The logging output * from it gets persisted, and this class provides a "/log" view that allows the administrator to monitor its progress. * * <p> * See {@link RekeySecretAdminMonitor} for an example of how to subtype this class. * * @author Kohsuke Kawaguchi */ public abstract class AsynchronousAdministrativeMonitor extends AdministrativeMonitor { /** * Set to non-null once the background activity starts running. */ private volatile FixThread fixThread; /** * Is there an active execution process going on? */ public boolean isFixingActive() { return fixThread !=null && fixThread.isAlive(); } /** * Used to URL-bind {@link AnnotatedLargeText}. */ public AnnotatedLargeText getLogText() { return new AnnotatedLargeText<AsynchronousAdministrativeMonitor>( getLogFile(), Charset.defaultCharset(), !isFixingActive(), this); } /** * Rewrite log file. */ protected File getLogFile() { File base = getBaseDir(); base.mkdirs(); return new File(base,"log"); } protected File getBaseDir() { return new File(Jenkins.getInstance().getRootDir(),getClass().getName()); } public abstract String getDisplayName(); /** * Starts the background fixing activity. * * @param forceRestart * If true, any ongoing fixing activity gets interrupted and the new one starts right away. */ protected synchronized Thread start(boolean forceRestart) { if (!forceRestart && isFixingActive()) { fixThread.interrupt(); } if (forceRestart || !isFixingActive()) { fixThread = new FixThread(); fixThread.start(); } return fixThread; } /** * Run on a separate thread in the background to fix up stuff. */ protected abstract void fix(TaskListener listener) throws Exception; protected class FixThread extends Thread { FixThread() { super(getDisplayName()); } @Override public void run() { ACL.impersonate(ACL.SYSTEM); StreamTaskListener listener = null; try { listener = new StreamTaskListener(getLogFile()); try { doRun(listener); } finally { listener.close(); } } catch (IOException ex) { if (listener == null) { LOGGER.log(Level.SEVERE, "Cannot create listener for " + getName(), ex); //TODO: throw IllegalStateException? } else { LOGGER.log(Level.WARNING, "Cannot close listener for " + getName(), ex); } } } /** * Runs the monitor and encapsulates all errors within. * @since 1.590 */ private void doRun(@Nonnull TaskListener listener) { try { fix(listener); } catch (AbortException e) { listener.error(e.getMessage()); } catch (Throwable e) { e.printStackTrace(listener.error(getName() + " failed")); LOGGER.log(Level.WARNING, getName() + " failed", e); } } } private static final Logger LOGGER = Logger.getLogger(AsynchronousAdministrativeMonitor.class.getName()); }