/*
 * The MIT License
 *
 * Copyright (c) 2004-2011, Sun Microsystems, Inc., Kohsuke Kawaguchi,
 * Erik Ramfelt, Koichi Fujikawa, Red Hat, Inc., Seiji Sogabe,
 * Stephen Connolly, Tom Huybrechts, Yahoo! Inc., Alan Harder, CloudBees, Inc.,
 * Yahoo!, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package jenkins.model;

import antlr.ANTLRException;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.thoughtworks.xstream.XStream;
import hudson.*;
import hudson.Launcher.LocalLauncher;
import jenkins.AgentProtocol;
import jenkins.diagnostics.URICheckEncodingMonitor;
import jenkins.util.SystemProperties;
import hudson.cli.declarative.CLIMethod;
import hudson.cli.declarative.CLIResolver;
import hudson.init.InitMilestone;
import hudson.init.InitStrategy;
import hudson.init.TermMilestone;
import hudson.init.TerminatorFinder;
import hudson.lifecycle.Lifecycle;
import hudson.lifecycle.RestartNotSupportedException;
import hudson.logging.LogRecorderManager;
import hudson.markup.EscapedMarkupFormatter;
import hudson.markup.MarkupFormatter;
import hudson.model.AbstractCIBase;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.AdministrativeMonitor;
import hudson.model.AllView;
import hudson.model.Api;
import hudson.model.Computer;
import hudson.model.ComputerSet;
import hudson.model.DependencyGraph;
import hudson.model.Describable;
import hudson.model.Descriptor;
import hudson.model.Descriptor.FormException;
import hudson.model.DescriptorByNameOwner;
import hudson.model.DirectoryBrowserSupport;
import hudson.model.Failure;
import hudson.model.Fingerprint;
import hudson.model.FingerprintCleanupThread;
import hudson.model.FingerprintMap;
import hudson.model.Hudson;
import hudson.model.Item;
import hudson.model.ItemGroup;
import hudson.model.ItemGroupMixIn;
import hudson.model.Items;
import hudson.model.JDK;
import hudson.model.Job;
import hudson.model.JobPropertyDescriptor;
import hudson.model.Label;
import hudson.model.ListView;
import hudson.model.LoadBalancer;
import hudson.model.LoadStatistics;
import hudson.model.ManagementLink;
import hudson.model.Messages;
import hudson.model.ModifiableViewGroup;
import hudson.model.NoFingerprintMatch;
import hudson.model.Node;
import hudson.model.OverallLoadStatistics;
import hudson.model.PaneStatusProperties;
import hudson.model.Project;
import hudson.model.Queue;
import hudson.model.Queue.FlyweightTask;
import hudson.model.RestartListener;
import hudson.model.RootAction;
import hudson.model.Slave;
import hudson.model.TaskListener;
import hudson.model.TopLevelItem;
import hudson.model.TopLevelItemDescriptor;
import hudson.model.UnprotectedRootAction;
import hudson.model.UpdateCenter;
import hudson.model.User;
import hudson.model.View;
import hudson.model.ViewGroupMixIn;
import hudson.model.WorkspaceCleanupThread;
import hudson.model.labels.LabelAtom;
import hudson.model.listeners.ItemListener;
import hudson.model.listeners.SCMListener;
import hudson.model.listeners.SaveableListener;
import hudson.remoting.Callable;
import hudson.remoting.LocalChannel;
import hudson.remoting.VirtualChannel;
import hudson.scm.RepositoryBrowser;
import hudson.scm.SCM;
import hudson.search.CollectionSearchIndex;
import hudson.search.SearchIndexBuilder;
import hudson.search.SearchItem;
import hudson.security.ACL;
import hudson.security.AccessControlled;
import hudson.security.AuthorizationStrategy;
import hudson.security.BasicAuthenticationFilter;
import hudson.security.FederatedLoginService;
import hudson.security.HudsonFilter;
import hudson.security.LegacyAuthorizationStrategy;
import hudson.security.LegacySecurityRealm;
import hudson.security.Permission;
import hudson.security.PermissionGroup;
import hudson.security.PermissionScope;
import hudson.security.SecurityMode;
import hudson.security.SecurityRealm;
import hudson.security.csrf.CrumbIssuer;
import hudson.slaves.Cloud;
import hudson.slaves.ComputerListener;
import hudson.slaves.DumbSlave;
import hudson.slaves.NodeDescriptor;
import hudson.slaves.NodeList;
import hudson.slaves.NodeProperty;
import hudson.slaves.NodePropertyDescriptor;
import hudson.slaves.NodeProvisioner;
import hudson.slaves.OfflineCause;
import hudson.slaves.RetentionStrategy;
import hudson.tasks.BuildWrapper;
import hudson.tasks.Builder;
import hudson.tasks.Publisher;
import hudson.triggers.SafeTimerTask;
import hudson.triggers.Trigger;
import hudson.triggers.TriggerDescriptor;
import hudson.util.AdministrativeError;
import hudson.util.CaseInsensitiveComparator;
import hudson.util.ClockDifference;
import hudson.util.CopyOnWriteList;
import hudson.util.CopyOnWriteMap;
import hudson.util.DaemonThreadFactory;
import hudson.util.DescribableList;
import hudson.util.FormApply;
import hudson.util.FormValidation;
import hudson.util.Futures;
import hudson.util.HudsonIsLoading;
import hudson.util.HudsonIsRestarting;
import hudson.util.Iterators;
import hudson.util.JenkinsReloadFailed;
import hudson.util.Memoizer;
import hudson.util.MultipartFormDataParser;
import hudson.util.NamingThreadFactory;
import hudson.util.PluginServletFilter;
import hudson.util.RemotingDiagnostics;
import hudson.util.RemotingDiagnostics.HeapDump;
import hudson.util.TextFile;
import hudson.util.TimeUnit2;
import hudson.util.VersionNumber;
import hudson.util.XStream2;
import hudson.views.DefaultMyViewsTabBar;
import hudson.views.DefaultViewsTabBar;
import hudson.views.MyViewsTabBar;
import hudson.views.ViewsTabBar;
import hudson.widgets.Widget;

import java.util.Objects;
import java.util.TimerTask;
import java.util.concurrent.CountDownLatch;
import jenkins.ExtensionComponentSet;
import jenkins.ExtensionRefreshException;
import jenkins.InitReactorRunner;
import jenkins.install.InstallState;
import jenkins.install.InstallUtil;
import jenkins.install.SetupWizard;
import jenkins.model.ProjectNamingStrategy.DefaultProjectNamingStrategy;
import jenkins.security.ConfidentialKey;
import jenkins.security.ConfidentialStore;
import jenkins.security.SecurityListener;
import jenkins.security.MasterToSlaveCallable;
import jenkins.slaves.WorkspaceLocator;
import jenkins.util.JenkinsJVM;
import jenkins.util.Timer;
import jenkins.util.io.FileBoolean;
import jenkins.util.io.OnMaster;
import jenkins.util.xml.XMLUtils;
import net.jcip.annotations.GuardedBy;
import net.sf.json.JSONObject;
import org.acegisecurity.AccessDeniedException;
import org.acegisecurity.AcegiSecurityException;
import org.acegisecurity.Authentication;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.GrantedAuthorityImpl;
import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
import org.acegisecurity.ui.AbstractProcessingFilter;
import org.apache.commons.jelly.JellyException;
import org.apache.commons.jelly.Script;
import org.apache.commons.logging.LogFactory;
import org.jvnet.hudson.reactor.Executable;
import org.jvnet.hudson.reactor.Milestone;
import org.jvnet.hudson.reactor.Reactor;
import org.jvnet.hudson.reactor.ReactorException;
import org.jvnet.hudson.reactor.ReactorListener;
import org.jvnet.hudson.reactor.Task;
import org.jvnet.hudson.reactor.TaskBuilder;
import org.jvnet.hudson.reactor.TaskGraphBuilder;
import org.jvnet.hudson.reactor.TaskGraphBuilder.Handle;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.args4j.Argument;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.MetaClass;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerFallback;
import org.kohsuke.stapler.StaplerProxy;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.WebApp;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.framework.adjunct.AdjunctManager;
import org.kohsuke.stapler.interceptor.RequirePOST;
import org.kohsuke.stapler.jelly.JellyClassLoaderTearOff;
import org.kohsuke.stapler.jelly.JellyRequestDispatcher;
import org.xml.sax.InputSource;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.crypto.SecretKey;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.BindException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

import static hudson.Util.*;
import static hudson.init.InitMilestone.*;
import hudson.init.Initializer;
import hudson.util.LogTaskListener;
import static java.util.logging.Level.*;
import static javax.servlet.http.HttpServletResponse.*;
import org.kohsuke.stapler.WebMethod;

/**
 * Root object of the system.
 *
 * @author Kohsuke Kawaguchi
 */
@ExportedBean
public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLevelItemGroup, StaplerProxy, StaplerFallback,
        ModifiableViewGroup, AccessControlled, DescriptorByNameOwner,
        ModelObjectWithContextMenu, ModelObjectWithChildren, OnMaster {
    private transient final Queue queue;

    /**
     * Stores various objects scoped to {@link Jenkins}.
     */
    public transient final Lookup lookup = new Lookup();

    /**
     * We update this field to the current version of Jenkins whenever we save {@code config.xml}.
     * This can be used to detect when an upgrade happens from one version to next.
     *
     * <p>
     * Since this field is introduced starting 1.301, "1.0" is used to represent every version
     * up to 1.300. This value may also include non-standard versions like "1.301-SNAPSHOT" or
     * "?", etc., so parsing needs to be done with a care.
     *
     * @since 1.301
     */
    // this field needs to be at the very top so that other components can look at this value even during unmarshalling
    private String version = "1.0";

    /**
     * The Jenkins instance startup type i.e. NEW, UPGRADE etc
     */
    private transient InstallState installState = InstallState.UNKNOWN;
    
    /**
     * If we're in the process of an initial setup, 
     * this will be set
     */
    private transient SetupWizard setupWizard;

    /**
     * Number of executors of the master node.
     */
    private int numExecutors = 2;

    /**
     * Job allocation strategy.
     */
    private Mode mode = Mode.NORMAL;

    /**
     * False to enable anyone to do anything.
     * Left as a field so that we can still read old data that uses this flag.
     *
     * @see #authorizationStrategy
     * @see #securityRealm
     */
    private Boolean useSecurity;

    /**
     * Controls how the
     * <a href="http://en.wikipedia.org/wiki/Authorization">authorization</a>
     * is handled in Jenkins.
     * <p>
     * This ultimately controls who has access to what.
     *
     * Never null.
     */
    private volatile AuthorizationStrategy authorizationStrategy = AuthorizationStrategy.UNSECURED;

    /**
     * Controls a part of the
     * <a href="http://en.wikipedia.org/wiki/Authentication">authentication</a>
     * handling in Jenkins.
     * <p>
     * Intuitively, this corresponds to the user database.
     *
     * See {@link HudsonFilter} for the concrete authentication protocol.
     *
     * Never null. Always use {@link #setSecurityRealm(SecurityRealm)} to
     * update this field.
     *
     * @see #getSecurity()
     * @see #setSecurityRealm(SecurityRealm)
     */
    private volatile SecurityRealm securityRealm = SecurityRealm.NO_AUTHENTICATION;

    /**
     * Disables the remember me on this computer option in the standard login screen.
     *
     * @since 1.534
     */
    private volatile boolean disableRememberMe;

    /**
     * The project naming strategy defines/restricts the names which can be given to a project/job. e.g. does the name have to follow a naming convention?
     */
    private ProjectNamingStrategy projectNamingStrategy = DefaultProjectNamingStrategy.DEFAULT_NAMING_STRATEGY;

    /**
     * Root directory for the workspaces.
     * This value will be variable-expanded as per {@link #expandVariablesForDirectory}.
     * @see #getWorkspaceFor(TopLevelItem)
     */
    private String workspaceDir = "${ITEM_ROOTDIR}/"+WORKSPACE_DIRNAME;

    /**
     * Root directory for the builds.
     * This value will be variable-expanded as per {@link #expandVariablesForDirectory}.
     * @see #getBuildDirFor(Job)
     */
    private String buildsDir = "${ITEM_ROOTDIR}/builds";

    /**
     * Message displayed in the top page.
     */
    private String systemMessage;

    private MarkupFormatter markupFormatter;

    /**
     * Root directory of the system.
     */
    public transient final File root;

    /**
     * Where are we in the initialization?
     */
    private transient volatile InitMilestone initLevel = InitMilestone.STARTED;

    /**
     * All {@link Item}s keyed by their {@link Item#getName() name}s.
     */
    /*package*/ transient final Map<String,TopLevelItem> items = new CopyOnWriteMap.Tree<String,TopLevelItem>(CaseInsensitiveComparator.INSTANCE);

    /**
     * The sole instance.
     */
    private static Jenkins theInstance;

    private transient volatile boolean isQuietingDown;
    private transient volatile boolean terminating;
    @GuardedBy("Jenkins.class")
    private transient boolean cleanUpStarted;

    private volatile List<JDK> jdks = new ArrayList<JDK>();

    private transient volatile DependencyGraph dependencyGraph;
    private final transient AtomicBoolean dependencyGraphDirty = new AtomicBoolean();

    /**
     * Currently active Views tab bar.
     */
    private volatile ViewsTabBar viewsTabBar = new DefaultViewsTabBar();

    /**
     * Currently active My Views tab bar.
     */
    private volatile MyViewsTabBar myViewsTabBar = new DefaultMyViewsTabBar();

    /**
     * All {@link ExtensionList} keyed by their {@link ExtensionList#extensionType}.
     */
    private transient final Memoizer<Class,ExtensionList> extensionLists = new Memoizer<Class,ExtensionList>() {
        public ExtensionList compute(Class key) {
            return ExtensionList.create(Jenkins.this,key);
        }
    };

    /**
     * All {@link DescriptorExtensionList} keyed by their {@link DescriptorExtensionList#describableType}.
     */
    private transient final Memoizer<Class,DescriptorExtensionList> descriptorLists = new Memoizer<Class,DescriptorExtensionList>() {
        public DescriptorExtensionList compute(Class key) {
            return DescriptorExtensionList.createDescriptorList(Jenkins.this,key);
        }
    };

    /**
     * {@link Computer}s in this Jenkins system. Read-only.
     */
    protected transient final Map<Node,Computer> computers = new CopyOnWriteMap.Hash<Node,Computer>();

    /**
     * Active {@link Cloud}s.
     */
    public final Hudson.CloudList clouds = new Hudson.CloudList(this);

    public static class CloudList extends DescribableList<Cloud,Descriptor<Cloud>> {
        public CloudList(Jenkins h) {
            super(h);
        }

        public CloudList() {// needed for XStream deserialization
        }

        public Cloud getByName(String name) {
            for (Cloud c : this)
                if (c.name.equals(name))
                    return c;
            return null;
        }

        @Override
        protected void onModified() throws IOException {
            super.onModified();
            Jenkins.getInstance().trimLabels();
        }
    }

    /**
     * Legacy store of the set of installed cluster nodes.
     * @deprecated in favour of {@link Nodes}
     */
    @Deprecated
    protected transient volatile NodeList slaves;

    /**
     * The holder of the set of installed cluster nodes.
     *
     * @since 1.607
     */
    private transient final Nodes nodes = new Nodes(this);

    /**
     * Quiet period.
     *
     * This is {@link Integer} so that we can initialize it to '5' for upgrading users.
     */
    /*package*/ Integer quietPeriod;

    /**
     * Global default for {@link AbstractProject#getScmCheckoutRetryCount()}
     */
    /*package*/ int scmCheckoutRetryCount;

    /**
     * {@link View}s.
     */
    private final CopyOnWriteArrayList<View> views = new CopyOnWriteArrayList<View>();

    /**
     * Name of the primary view.
     * <p>
     * Start with null, so that we can upgrade pre-1.269 data well.
     * @since 1.269
     */
    private volatile String primaryView;

    private transient final ViewGroupMixIn viewGroupMixIn = new ViewGroupMixIn(this) {
        protected List<View> views() { return views; }
        protected String primaryView() { return primaryView; }
        protected void primaryView(String name) { primaryView=name; }
    };


    private transient final FingerprintMap fingerprintMap = new FingerprintMap();

    /**
     * Loaded plugins.
     */
    public transient final PluginManager pluginManager;

    public transient volatile TcpSlaveAgentListener tcpSlaveAgentListener;

    private transient final Object tcpSlaveAgentListenerLock = new Object();

    private transient UDPBroadcastThread udpBroadcastThread;

    private transient DNSMultiCast dnsMultiCast;

    /**
     * List of registered {@link SCMListener}s.
     */
    private transient final CopyOnWriteList<SCMListener> scmListeners = new CopyOnWriteList<SCMListener>();

    /**
     * TCP agent port.
     * 0 for random, -1 to disable.
     */
    private int slaveAgentPort = getSlaveAgentPortInitialValue(0);

    private static int getSlaveAgentPortInitialValue(int def) {
        return SystemProperties.getInteger(Jenkins.class.getName()+".slaveAgentPort", def);
    }

    /**
     * If -Djenkins.model.Jenkins.slaveAgentPort is defined, enforce it on every start instead of only the first one.
     */
    private static final boolean SLAVE_AGENT_PORT_ENFORCE = SystemProperties.getBoolean(Jenkins.class.getName()+".slaveAgentPortEnforce", false);

    /**
     * The TCP agent protocols that are explicitly disabled (we store the disabled ones so that newer protocols
     * are enabled by default). Will be {@code null} instead of empty to simplify XML format.
     *
     * @since 2.16
     */
    @CheckForNull
    private List<String> disabledAgentProtocols;
    /**
     * @deprecated Just a temporary buffer for XSTream migration code from JENKINS-39465, do not use
     */
    @Deprecated
    private transient String[] _disabledAgentProtocols;

    /**
     * The TCP agent protocols that are {@link AgentProtocol#isOptIn()} and explicitly enabled.
     * Will be {@code null} instead of empty to simplify XML format.
     *
     * @since 2.16
     */
    @CheckForNull
    private List<String> enabledAgentProtocols;
    /**
     * @deprecated Just a temporary buffer for XSTream migration code from JENKINS-39465, do not use
     */
    @Deprecated
    private transient String[] _enabledAgentProtocols;

    /**
     * The TCP agent protocols that are enabled. Built from {@link #disabledAgentProtocols} and
     * {@link #enabledAgentProtocols}.
     *
     * @since 2.16
     * @see #setAgentProtocols(Set)
     * @see #getAgentProtocols()
     */
    private transient Set<String> agentProtocols;

    /**
     * Whitespace-separated labels assigned to the master as a {@link Node}.
     */
    private String label="";

    /**
     * {@link hudson.security.csrf.CrumbIssuer}
     */
    private volatile CrumbIssuer crumbIssuer;

    /**
     * All labels known to Jenkins. This allows us to reuse the same label instances
     * as much as possible, even though that's not a strict requirement.
     */
    private transient final ConcurrentHashMap<String,Label> labels = new ConcurrentHashMap<String,Label>();

    /**
     * Load statistics of the entire system.
     *
     * This includes every executor and every job in the system.
     */
    @Exported
    public transient final OverallLoadStatistics overallLoad = new OverallLoadStatistics();

    /**
     * Load statistics of the free roaming jobs and agents.
     *
     * This includes all executors on {@link hudson.model.Node.Mode#NORMAL} nodes and jobs that do not have any assigned nodes.
     *
     * @since 1.467
     */
    @Exported
    public transient final LoadStatistics unlabeledLoad = new UnlabeledLoadStatistics();

    /**
     * {@link NodeProvisioner} that reacts to {@link #unlabeledLoad}.
     * @since 1.467
     */
    public transient final NodeProvisioner unlabeledNodeProvisioner = new NodeProvisioner(null,unlabeledLoad);

    /**
     * @deprecated as of 1.467
     *      Use {@link #unlabeledNodeProvisioner}.
     *      This was broken because it was tracking all the executors in the system, but it was only tracking
     *      free-roaming jobs in the queue. So {@link Cloud} fails to launch nodes when you have some exclusive
     *      agents and free-roaming jobs in the queue.
     */
    @Restricted(NoExternalUse.class)
    @Deprecated
    public transient final NodeProvisioner overallNodeProvisioner = unlabeledNodeProvisioner;


    public transient final ServletContext servletContext;

    /**
     * Transient action list. Useful for adding navigation items to the navigation bar
     * on the left.
     */
    private transient final List<Action> actions = new CopyOnWriteArrayList<Action>();

    /**
     * List of master node properties
     */
    private DescribableList<NodeProperty<?>,NodePropertyDescriptor> nodeProperties = new DescribableList<NodeProperty<?>,NodePropertyDescriptor>(this);

    /**
     * List of global properties
     */
    private DescribableList<NodeProperty<?>,NodePropertyDescriptor> globalNodeProperties = new DescribableList<NodeProperty<?>,NodePropertyDescriptor>(this);

    /**
     * {@link AdministrativeMonitor}s installed on this system.
     *
     * @see AdministrativeMonitor
     */
    public transient final List<AdministrativeMonitor> administrativeMonitors = getExtensionList(AdministrativeMonitor.class);

    /**
     * Widgets on Jenkins.
     */
    private transient final List<Widget> widgets = getExtensionList(Widget.class);

    /**
     * {@link AdjunctManager}
     */
    private transient final AdjunctManager adjuncts;

    /**
     * Code that handles {@link ItemGroup} work.
     */
    private transient final ItemGroupMixIn itemGroupMixIn = new ItemGroupMixIn(this,this) {
        @Override
        protected void add(TopLevelItem item) {
            items.put(item.getName(),item);
        }

        @Override
        protected File getRootDirFor(String name) {
            return Jenkins.this.getRootDirFor(name);
        }
    };


    /**
     * Hook for a test harness to intercept Jenkins.getInstance()
     *
     * Do not use in the production code as the signature may change.
     */
    public interface JenkinsHolder {
        @CheckForNull Jenkins getInstance();
    }

    static JenkinsHolder HOLDER = new JenkinsHolder() {
        public @CheckForNull Jenkins getInstance() {
            return theInstance;
        }
    };

    /**
     * Gets the {@link Jenkins} singleton.
     * {@link #getInstanceOrNull()} provides the unchecked versions of the method.
     * @return {@link Jenkins} instance
     * @throws IllegalStateException {@link Jenkins} has not been started, or was already shut down
     * @since 1.590
     * @deprecated use {@link #getInstance()}
     */
    @Deprecated
    @Nonnull
    public static Jenkins getActiveInstance() throws IllegalStateException {
        Jenkins instance = HOLDER.getInstance();
        if (instance == null) {
            throw new IllegalStateException("Jenkins has not been started, or was already shut down");
        }
        return instance;
    }

    /**
     * Gets the {@link Jenkins} singleton.
     * {@link #getActiveInstance()} provides the checked versions of the method.
     * @return The instance. Null if the {@link Jenkins} instance has not been started,
     * or was already shut down
     * @since 1.653
     */
    @CheckForNull
    public static Jenkins getInstanceOrNull() {
        return HOLDER.getInstance();
    }

    /**
     * Gets the {@link Jenkins} singleton. In certain rare cases you may have code that is intended to run before
     * Jenkins starts or while Jenkins is being shut-down. For those rare cases use {@link #getInstanceOrNull()}.
     * In other cases you may have code that might end up running on a remote JVM and not on the Jenkins master,
     * for those cases you really should rewrite your code so that when the {@link Callable} is sent over the remoting
     * channel it uses a {@code writeReplace} method or similar to ensure that the {@link Jenkins} class is not being
     * loaded into the remote class loader
     * @return The instance.
     * @throws IllegalStateException {@link Jenkins} has not been started, or was already shut down
     */
    @CLIResolver
    @Nonnull
    public static Jenkins getInstance() {
        Jenkins instance = HOLDER.getInstance();
        if (instance == null) {
            if(SystemProperties.getBoolean(Jenkins.class.getName()+".enableExceptionOnNullInstance")) {
                // TODO: remove that second block around 2.20 (that is: ~20 versions to battle test it)
                // See https://github.com/jenkinsci/jenkins/pull/2297#issuecomment-216710150
                throw new IllegalStateException("Jenkins has not been started, or was already shut down");
            }
        }
        return instance;
    }

    /**
     * Secret key generated once and used for a long time, beyond
     * container start/stop. Persisted outside <tt>config.xml</tt> to avoid
     * accidental exposure.
     */
    private transient final String secretKey;

    private transient final UpdateCenter updateCenter = UpdateCenter.createUpdateCenter(null);

    /**
     * True if the user opted out from the statistics tracking. We'll never send anything if this is true.
     */
    private Boolean noUsageStatistics;

    /**
     * HTTP proxy configuration.
     */
    public transient volatile ProxyConfiguration proxy;

    /**
     * Bound to "/log".
     */
    private transient final LogRecorderManager log = new LogRecorderManager();

    private transient final boolean oldJenkinsJVM;

    protected Jenkins(File root, ServletContext context) throws IOException, InterruptedException, ReactorException {
        this(root, context, null);
    }

    /**
     * @param pluginManager
     *      If non-null, use existing plugin manager.  create a new one.
     */
    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings({
        "SC_START_IN_CTOR", // bug in FindBugs. It flags UDPBroadcastThread.start() call but that's for another class
        "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD" // Trigger.timer
    })
    protected Jenkins(File root, ServletContext context, PluginManager pluginManager) throws IOException, InterruptedException, ReactorException {
        oldJenkinsJVM = JenkinsJVM.isJenkinsJVM(); // capture to restore in cleanUp()
        JenkinsJVMAccess._setJenkinsJVM(true); // set it for unit tests as they will not have gone through WebAppMain
        long start = System.currentTimeMillis();

        // As Jenkins is starting, grant this process full control
        ACL.impersonate(ACL.SYSTEM);
        try {
            this.root = root;
            this.servletContext = context;
            computeVersion(context);
            if(theInstance!=null)
                throw new IllegalStateException("second instance");
            theInstance = this;

            if (!new File(root,"jobs").exists()) {
                // if this is a fresh install, use more modern default layout that's consistent with agents
                workspaceDir = "${JENKINS_HOME}/workspace/${ITEM_FULLNAME}";
            }

            // doing this early allows InitStrategy to set environment upfront
            final InitStrategy is = InitStrategy.get(Thread.currentThread().getContextClassLoader());

            Trigger.timer = new java.util.Timer("Jenkins cron thread");
            queue = new Queue(LoadBalancer.CONSISTENT_HASH);

            try {
                dependencyGraph = DependencyGraph.EMPTY;
            } catch (InternalError e) {
                if(e.getMessage().contains("window server")) {
                    throw new Error("Looks like the server runs without X. Please specify -Djava.awt.headless=true as JVM option",e);
                }
                throw e;
            }

            // get or create the secret
            TextFile secretFile = new TextFile(new File(getRootDir(),"secret.key"));
            if(secretFile.exists()) {
                secretKey = secretFile.readTrim();
            } else {
                SecureRandom sr = new SecureRandom();
                byte[] random = new byte[32];
                sr.nextBytes(random);
                secretKey = Util.toHexString(random);
                secretFile.write(secretKey);

                // this marker indicates that the secret.key is generated by the version of Jenkins post SECURITY-49.
                // this indicates that there's no need to rewrite secrets on disk
                new FileBoolean(new File(root,"secret.key.not-so-secret")).on();
            }

            try {
                proxy = ProxyConfiguration.load();
            } catch (IOException e) {
                LOGGER.log(SEVERE, "Failed to load proxy configuration", e);
            }

            if (pluginManager==null)
                pluginManager = PluginManager.createDefault(this);
            this.pluginManager = pluginManager;
            // JSON binding needs to be able to see all the classes from all the plugins
            WebApp.get(servletContext).setClassLoader(pluginManager.uberClassLoader);

            adjuncts = new AdjunctManager(servletContext, pluginManager.uberClassLoader,"adjuncts/"+SESSION_HASH, TimeUnit2.DAYS.toMillis(365));

            // initialization consists of ...
            executeReactor( is,
                    pluginManager.initTasks(is),    // loading and preparing plugins
                    loadTasks(),                    // load jobs
                    InitMilestone.ordering()        // forced ordering among key milestones
            );

            // Ensure we reached the final initialization state. Log the error otherwise
            if (initLevel != InitMilestone.COMPLETED) {
                LOGGER.log(SEVERE, "Jenkins initialization has not reached the COMPLETED initialization milestone after the startup. " +
                                "Current state: {0}. " +
                                "It may cause undefined incorrect behavior in Jenkins plugin relying on this state. " +
                                "It is likely an issue with the Initialization task graph. " +
                                "Example: usage of @Initializer(after = InitMilestone.COMPLETED) in a plugin (JENKINS-37759). " +
                                "Please create a bug in Jenkins bugtracker. ",
                        initLevel);
            }


            if(KILL_AFTER_LOAD)
                System.exit(0);

            setupWizard = new SetupWizard();
            InstallUtil.proceedToNextStateFrom(InstallState.UNKNOWN);

            launchTcpSlaveAgentListener();

            if (UDPBroadcastThread.PORT != -1) {
                try {
                    udpBroadcastThread = new UDPBroadcastThread(this);
                    udpBroadcastThread.start();
                } catch (IOException e) {
                    LOGGER.log(Level.WARNING, "Failed to broadcast over UDP (use -Dhudson.udp=-1 to disable)", e);
                }
            }
            dnsMultiCast = new DNSMultiCast(this);

            Timer.get().scheduleAtFixedRate(new SafeTimerTask() {
                @Override
                protected void doRun() throws Exception {
                    trimLabels();
                }
            }, TimeUnit2.MINUTES.toMillis(5), TimeUnit2.MINUTES.toMillis(5), TimeUnit.MILLISECONDS);

            updateComputerList();

            {// master is online now, it's instance must always exist
                final Computer c = toComputer();
                if(c != null) {
                    for (ComputerListener cl : ComputerListener.all()) {
                        try {
                            cl.onOnline(c, new LogTaskListener(LOGGER, INFO));
                        } catch (Throwable t) {
                            if (t instanceof Error) {
                                // We propagate Runtime errors, because they are fatal.
                                throw t;
                            }

                            // Other exceptions should be logged instead of failing the Jenkins startup (See listener's Javadoc)
                            // We also throw it for InterruptedException since it's what is expected according to the javadoc
                            LOGGER.log(SEVERE, String.format("Invocation of the computer listener %s failed for the Jenkins master node",
                                    cl.getClass()), t);
                        }
                    }
                }
            }

            for (ItemListener l : ItemListener.all()) {
                long itemListenerStart = System.currentTimeMillis();
                try {
                    l.onLoaded();
                } catch (RuntimeException x) {
                    LOGGER.log(Level.WARNING, null, x);
                }
                if (LOG_STARTUP_PERFORMANCE)
                    LOGGER.info(String.format("Took %dms for item listener %s startup",
                            System.currentTimeMillis()-itemListenerStart,l.getClass().getName()));
            }

            if (LOG_STARTUP_PERFORMANCE)
                LOGGER.info(String.format("Took %dms for complete Jenkins startup",
                        System.currentTimeMillis()-start));
        } finally {
            SecurityContextHolder.clearContext();
        }
    }

    /**
     * Maintains backwards compatibility. Invoked by XStream when this object is de-serialized.
     */
    @SuppressWarnings({"unused"})
    private Object readResolve() {
        if (jdks == null) {
            jdks = new ArrayList<>();
        }
        if (SLAVE_AGENT_PORT_ENFORCE) {
            slaveAgentPort = getSlaveAgentPortInitialValue(slaveAgentPort);
        }
        if (disabledAgentProtocols == null && _disabledAgentProtocols != null) {
            disabledAgentProtocols = Arrays.asList(_disabledAgentProtocols);
            _disabledAgentProtocols = null;
        }
        if (enabledAgentProtocols == null && _enabledAgentProtocols != null) {
            enabledAgentProtocols = Arrays.asList(_enabledAgentProtocols);
            _enabledAgentProtocols = null;
        }
        // Invalidate the protocols cache after the reload
        agentProtocols = null;
        return this;
    }
    
    /**
     * Get the Jenkins {@link jenkins.install.InstallState install state}.
     * @return The Jenkins {@link jenkins.install.InstallState install state}.
     */
    @Nonnull
    @Restricted(NoExternalUse.class)
    public InstallState getInstallState() {
        if (installState == null || installState.name() == null) {
            return InstallState.UNKNOWN;
        }
        return installState;
    }

    /**
     * Update the current install state. This will invoke state.initializeState() 
     * when the state has been transitioned.
     */
    @Restricted(NoExternalUse.class)
    public void setInstallState(@Nonnull InstallState newState) {
        InstallState prior = installState;
        installState = newState;
        if (!prior.equals(newState)) {
            newState.initializeState();
        }
    }

    /**
     * Executes a reactor.
     *
     * @param is
     *      If non-null, this can be consulted for ignoring some tasks. Only used during the initialization of Jenkins.
     */
    private void executeReactor(final InitStrategy is, TaskBuilder... builders) throws IOException, InterruptedException, ReactorException {
        Reactor reactor = new Reactor(builders) {
            /**
             * Sets the thread name to the task for better diagnostics.
             */
            @Override
            protected void runTask(Task task) throws Exception {
                if (is!=null && is.skipInitTask(task))  return;

                ACL.impersonate(ACL.SYSTEM); // full access in the initialization thread
                String taskName = InitReactorRunner.getDisplayName(task);

                Thread t = Thread.currentThread();
                String name = t.getName();
                if (taskName !=null)
                    t.setName(taskName);
                try {
                    long start = System.currentTimeMillis();
                    super.runTask(task);
                    if(LOG_STARTUP_PERFORMANCE)
                        LOGGER.info(String.format("Took %dms for %s by %s",
                                System.currentTimeMillis()-start, taskName, name));
                } catch (Exception | Error x) {
                    if (containsLinkageError(x)) {
                        LOGGER.log(Level.WARNING, taskName + " failed perhaps due to plugin dependency issues", x);
                    } else {
                        throw x;
                    }
                } finally {
                    t.setName(name);
                    SecurityContextHolder.clearContext();
                }
            }
            private boolean containsLinkageError(Throwable x) {
                if (x instanceof LinkageError) {
                    return true;
                }
                Throwable x2 = x.getCause();
                return x2 != null && containsLinkageError(x2);
            }
        };

        new InitReactorRunner() {
            @Override
            protected void onInitMilestoneAttained(InitMilestone milestone) {
                initLevel = milestone;
                if (milestone==PLUGINS_PREPARED) {
                    // set up Guice to enable injection as early as possible
                    // before this milestone, ExtensionList.ensureLoaded() won't actually try to locate instances
                    ExtensionList.lookup(ExtensionFinder.class).getComponents();
                }
            }
        }.run(reactor);
    }


    public TcpSlaveAgentListener getTcpSlaveAgentListener() {
        return tcpSlaveAgentListener;
    }

    /**
     * Makes {@link AdjunctManager} URL-bound.
     * The dummy parameter allows us to use different URLs for the same adjunct,
     * for proper cache handling.
     */
    public AdjunctManager getAdjuncts(String dummy) {
        return adjuncts;
    }

    @Exported
    public int getSlaveAgentPort() {
        return slaveAgentPort;
    }

    /**
     * @since 2.24
     */
    public boolean isSlaveAgentPortEnforced() {
        return Jenkins.SLAVE_AGENT_PORT_ENFORCE;
    }

    /**
     * @param port
     *      0 to indicate random available TCP port. -1 to disable this service.
     */
    public void setSlaveAgentPort(int port) throws IOException {
        if (SLAVE_AGENT_PORT_ENFORCE) {
            LOGGER.log(Level.WARNING, "setSlaveAgentPort({0}) call ignored because system property {1} is true", new String[] { Integer.toString(port), Jenkins.class.getName()+".slaveAgentPortEnforce" });
        } else {
            forceSetSlaveAgentPort(port);
        }
    }

    private void forceSetSlaveAgentPort(int port) throws IOException {
        this.slaveAgentPort = port;
        launchTcpSlaveAgentListener();
    }

    /**
     * Returns the enabled agent protocols.
     *
     * @return the enabled agent protocols.
     * @since 2.16
     */
    public Set<String> getAgentProtocols() {
        if (agentProtocols == null) {
            // idempotent, so don't care if we do this concurrently, should all get same result
            Set<String> result = new TreeSet<>();
            Set<String> disabled = new TreeSet<>();
            for (String p : Util.fixNull(disabledAgentProtocols)) {
                disabled.add(p.trim());
            }
            Set<String> enabled = new TreeSet<>();
            for (String p : Util.fixNull(enabledAgentProtocols)) {
                enabled.add(p.trim());
            }
            for (AgentProtocol p : AgentProtocol.all()) {
                String name = p.getName();
                if (name != null && (p.isRequired()
                        || (!disabled.contains(name) && (!p.isOptIn() || enabled.contains(name))))) {
                    result.add(name);
                }
            }
            agentProtocols = result;
            return result;
        }
        return agentProtocols;
    }

    /**
     * Sets the enabled agent protocols.
     *
     * @param protocols the enabled agent protocols.
     * @since 2.16
     */
    public void setAgentProtocols(Set<String> protocols) {
        Set<String> disabled = new TreeSet<>();
        Set<String> enabled = new TreeSet<>();
        for (AgentProtocol p : AgentProtocol.all()) {
            String name = p.getName();
            if (name != null && !p.isRequired()) {
                // we want to record the protocols where the admin has made a conscious decision
                // thus, if a protocol is opt-in, we record the admin enabling it
                // if a protocol is opt-out, we record the admin disabling it
                // We should not transition rapidly from opt-in -> opt-out -> opt-in
                // the scenario we want to have work is:
                // 1. We introduce a new protocol, it starts off as opt-in. Some admins decide to test and opt-in
                // 2. We decide that the protocol is ready for general use. It gets marked as opt-out. Any admins
                //    that took part in early testing now have their config switched to not mention the new protocol
                //    at all when they save their config as the protocol is now opt-out. Any admins that want to
                //    disable it can do so and will have their preference recorded.
                // 3. We decide that the protocol needs to be retired. It gets switched back to opt-in. At this point
                //    the initial opt-in admins, assuming they visited an upgrade to a master with step 2, will
                //    have the protocol disabled for them. This is what we want. If they didn't upgrade to a master
                //    with step 2, well there is not much we can do to differentiate them from somebody who is upgrading
                //    from a previous step 3 master and had needed to keep the protocol turned on.
                //
                // What we should never do is flip-flop: opt-in -> opt-out -> opt-in -> opt-out as that will basically
                // clear any preference that an admin has set, but this should be ok as we only ever will be
                // adding new protocols and retiring old ones.
                if (p.isOptIn()) {
                    if (protocols.contains(name)) {
                        enabled.add(name);
                    }
                } else {
                    if (!protocols.contains(name)) {
                        disabled.add(name);
                    }
                }
            }
        }
        disabledAgentProtocols = disabled.isEmpty() ? null : new ArrayList<>(disabled);
        enabledAgentProtocols = enabled.isEmpty() ? null : new ArrayList<>(enabled);
        agentProtocols = null;
    }

    private void launchTcpSlaveAgentListener() throws IOException {
        synchronized(tcpSlaveAgentListenerLock) {
            // shutdown previous agent if the port has changed
            if (tcpSlaveAgentListener != null && tcpSlaveAgentListener.configuredPort != slaveAgentPort) {
                tcpSlaveAgentListener.shutdown();
                tcpSlaveAgentListener = null;
            }
            if (slaveAgentPort != -1 && tcpSlaveAgentListener == null) {
                final String administrativeMonitorId = getClass().getName() + ".tcpBind";
                try {
                    tcpSlaveAgentListener = new TcpSlaveAgentListener(slaveAgentPort);
                    // remove previous monitor in case of previous error
                    AdministrativeMonitor toBeRemoved = null;
                    ExtensionList<AdministrativeMonitor> all = AdministrativeMonitor.all();
                    for (AdministrativeMonitor am : all) {
                        if (administrativeMonitorId.equals(am.id)) {
                            toBeRemoved = am;
                            break;
                        }
                    }
                    all.remove(toBeRemoved);
                } catch (BindException e) {
                    LOGGER.log(Level.WARNING, String.format("Failed to listen to incoming agent connections through JNLP port %s. Change the JNLP port number", slaveAgentPort), e);
                    new AdministrativeError(administrativeMonitorId,
                            "Failed to listen to incoming agent connections through JNLP",
                            "Failed to listen to incoming agent connections through JNLP. <a href='configureSecurity'>Change the JNLP port number</a> to solve the problem.", e);
                }
            }
        }
    }

    @Extension
    @Restricted(NoExternalUse.class)
    public static class EnforceSlaveAgentPortAdministrativeMonitor extends AdministrativeMonitor {
        @Inject
        Jenkins j;

        @Override
        public String getDisplayName() {
            return jenkins.model.Messages.EnforceSlaveAgentPortAdministrativeMonitor_displayName();
        }

        public String getSystemPropertyName() {
            return Jenkins.class.getName() + ".slaveAgentPort";
        }

        public int getExpectedPort() {
            int slaveAgentPort = j.slaveAgentPort;
            return Jenkins.getSlaveAgentPortInitialValue(slaveAgentPort);
        }

        public void doAct(StaplerRequest req, StaplerResponse rsp) throws IOException {
            j.forceSetSlaveAgentPort(getExpectedPort());
            rsp.sendRedirect2(req.getContextPath() + "/manage");
        }

        @Override
        public boolean isActivated() {
            int slaveAgentPort = Jenkins.getInstance().slaveAgentPort;
            return SLAVE_AGENT_PORT_ENFORCE && slaveAgentPort != Jenkins.getSlaveAgentPortInitialValue(slaveAgentPort);
        }
    }

    public void setNodeName(String name) {
        throw new UnsupportedOperationException(); // not allowed
    }

    public String getNodeDescription() {
        return Messages.Hudson_NodeDescription();
    }

    @Exported
    public String getDescription() {
        return systemMessage;
    }

    public PluginManager getPluginManager() {
        return pluginManager;
    }

    public UpdateCenter getUpdateCenter() {
        return updateCenter;
    }

    public boolean isUsageStatisticsCollected() {
        return noUsageStatistics==null || !noUsageStatistics;
    }

    public void setNoUsageStatistics(Boolean noUsageStatistics) throws IOException {
        this.noUsageStatistics = noUsageStatistics;
        save();
    }

    public View.People getPeople() {
        return new View.People(this);
    }

    /**
     * @since 1.484
     */
    public View.AsynchPeople getAsynchPeople() {
        return new View.AsynchPeople(this);
    }

    /**
     * Does this {@link View} has any associated user information recorded?
     * @deprecated Potentially very expensive call; do not use from Jelly views.
     */
    @Deprecated
    public boolean hasPeople() {
        return View.People.isApplicable(items.values());
    }

    public Api getApi() {
        return new Api(this);
    }

    /**
     * Returns a secret key that survives across container start/stop.
     * <p>
     * This value is useful for implementing some of the security features.
     *
     * @deprecated
     *      Due to the past security advisory, this value should not be used any more to protect sensitive information.
     *      See {@link ConfidentialStore} and {@link ConfidentialKey} for how to store secrets.
     */
    @Deprecated
    public String getSecretKey() {
        return secretKey;
    }

    /**
     * Gets {@linkplain #getSecretKey() the secret key} as a key for AES-128.
     * @since 1.308
     * @deprecated
     *       See {@link #getSecretKey()}.
     */
    @Deprecated
    public SecretKey getSecretKeyAsAES128() {
        return Util.toAes128Key(secretKey);
    }

    /**
     * Returns the unique identifier of this Jenkins that has been historically used to identify
     * this Jenkins to the outside world.
     *
     * <p>
     * This form of identifier is weak in that it can be impersonated by others. See
     * https://wiki.jenkins-ci.org/display/JENKINS/Instance+Identity for more modern form of instance ID
     * that can be challenged and verified.
     *
     * @since 1.498
     */
    @SuppressWarnings("deprecation")
    public String getLegacyInstanceId() {
        return Util.getDigestOf(getSecretKey());
    }

    /**
     * Gets the SCM descriptor by name. Primarily used for making them web-visible.
     */
    public Descriptor<SCM> getScm(String shortClassName) {
        return findDescriptor(shortClassName, SCM.all());
    }

    /**
     * Gets the repository browser descriptor by name. Primarily used for making them web-visible.
     */
    public Descriptor<RepositoryBrowser<?>> getRepositoryBrowser(String shortClassName) {
        return findDescriptor(shortClassName,RepositoryBrowser.all());
    }

    /**
     * Gets the builder descriptor by name. Primarily used for making them web-visible.
     */
    public Descriptor<Builder> getBuilder(String shortClassName) {
        return findDescriptor(shortClassName, Builder.all());
    }

    /**
     * Gets the build wrapper descriptor by name. Primarily used for making them web-visible.
     */
    public Descriptor<BuildWrapper> getBuildWrapper(String shortClassName) {
        return findDescriptor(shortClassName, BuildWrapper.all());
    }

    /**
     * Gets the publisher descriptor by name. Primarily used for making them web-visible.
     */
    public Descriptor<Publisher> getPublisher(String shortClassName) {
        return findDescriptor(shortClassName, Publisher.all());
    }

    /**
     * Gets the trigger descriptor by name. Primarily used for making them web-visible.
     */
    public TriggerDescriptor getTrigger(String shortClassName) {
        return (TriggerDescriptor) findDescriptor(shortClassName, Trigger.all());
    }

    /**
     * Gets the retention strategy descriptor by name. Primarily used for making them web-visible.
     */
    public Descriptor<RetentionStrategy<?>> getRetentionStrategy(String shortClassName) {
        return findDescriptor(shortClassName, RetentionStrategy.all());
    }

    /**
     * Gets the {@link JobPropertyDescriptor} by name. Primarily used for making them web-visible.
     */
    public JobPropertyDescriptor getJobProperty(String shortClassName) {
        // combining these two lines triggers javac bug. See issue #610.
        Descriptor d = findDescriptor(shortClassName, JobPropertyDescriptor.all());
        return (JobPropertyDescriptor) d;
    }

    /**
     * @deprecated
     *      UI method. Not meant to be used programmatically.
     */
    @Deprecated
    public ComputerSet getComputer() {
        return new ComputerSet();
    }

    /**
     * Exposes {@link Descriptor} by its name to URL.
     *
     * After doing all the {@code getXXX(shortClassName)} methods, I finally realized that
     * this just doesn't scale.
     *
     * @param id
     *      Either {@link Descriptor#getId()} (recommended) or the short name of a {@link Describable} subtype (for compatibility)
     * @throws IllegalArgumentException if a short name was passed which matches multiple IDs (fail fast)
     */
    @SuppressWarnings({"unchecked", "rawtypes"}) // too late to fix
    public Descriptor getDescriptor(String id) {
        // legacy descriptors that are registered manually doesn't show up in getExtensionList, so check them explicitly.
        Iterable<Descriptor> descriptors = Iterators.sequence(getExtensionList(Descriptor.class), DescriptorExtensionList.listLegacyInstances());
        for (Descriptor d : descriptors) {
            if (d.getId().equals(id)) {
                return d;
            }
        }
        Descriptor candidate = null;
        for (Descriptor d : descriptors) {
            String name = d.getId();
            if (name.substring(name.lastIndexOf('.') + 1).equals(id)) {
                if (candidate == null) {
                    candidate = d;
                } else {
                    throw new IllegalArgumentException(id + " is ambiguous; matches both " + name + " and " + candidate.getId());
                }
            }
        }
        return candidate;
    }

    /**
     * Alias for {@link #getDescriptor(String)}.
     */
    public Descriptor getDescriptorByName(String id) {
        return getDescriptor(id);
    }

    /**
     * Gets the {@link Descriptor} that corresponds to the given {@link Describable} type.
     * <p>
     * If you have an instance of {@code type} and call {@link Describable#getDescriptor()},
     * you'll get the same instance that this method returns.
     */
    @CheckForNull
    public Descriptor getDescriptor(Class<? extends Describable> type) {
        for( Descriptor d : getExtensionList(Descriptor.class) )
            if(d.clazz==type)
                return d;
        return null;
    }

    /**
     * Works just like {@link #getDescriptor(Class)} but don't take no for an answer.
     *
     * @throws AssertionError
     *      If the descriptor is missing.
     * @since 1.326
     */
    public Descriptor getDescriptorOrDie(Class<? extends Describable> type) {
        Descriptor d = getDescriptor(type);
        if (d==null)
            throw new AssertionError(type+" is missing its descriptor");
        return d;
    }

    /**
     * Gets the {@link Descriptor} instance in the current Jenkins by its type.
     */
    public <T extends Descriptor> T getDescriptorByType(Class<T> type) {
        for( Descriptor d : getExtensionList(Descriptor.class) )
            if(d.getClass()==type)
                return type.cast(d);
        return null;
    }

    /**
     * Gets the {@link SecurityRealm} descriptors by name. Primarily used for making them web-visible.
     */
    public Descriptor<SecurityRealm> getSecurityRealms(String shortClassName) {
        return findDescriptor(shortClassName, SecurityRealm.all());
    }

    /**
     * Finds a descriptor that has the specified name.
     */
    private <T extends Describable<T>>
    Descriptor<T> findDescriptor(String shortClassName, Collection<? extends Descriptor<T>> descriptors) {
        String name = '.'+shortClassName;
        for (Descriptor<T> d : descriptors) {
            if(d.clazz.getName().endsWith(name))
                return d;
        }
        return null;
    }

    protected void updateComputerList() {
        updateComputerList(AUTOMATIC_SLAVE_LAUNCH);
    }

    /** @deprecated Use {@link SCMListener#all} instead. */
    @Deprecated
    public CopyOnWriteList<SCMListener> getSCMListeners() {
        return scmListeners;
    }

    /**
     * Gets the plugin object from its short name.
     * This allows URL <tt>hudson/plugin/ID</tt> to be served by the views
     * of the plugin class.
     * @param shortName Short name of the plugin
     * @return The plugin singleton or {@code null} if for some reason the plugin is not loaded.
     *         The fact the plugin is loaded does not mean it is enabled and fully initialized for the current Jenkins session.
     *         Use {@link Plugin#getWrapper()} and then {@link PluginWrapper#isActive()} to check it.
     */
    @CheckForNull
    public Plugin getPlugin(String shortName) {
        PluginWrapper p = pluginManager.getPlugin(shortName);
        if(p==null)     return null;
        return p.getPlugin();
    }

    /**
     * Gets the plugin object from its class.
     *
     * <p>
     * This allows easy storage of plugin information in the plugin singleton without
     * every plugin reimplementing the singleton pattern.
     *
     * @param <P> Class of the plugin
     * @param clazz The plugin class (beware class-loader fun, this will probably only work
     * from within the jpi that defines the plugin class, it may or may not work in other cases)
     * @return The plugin singleton or {@code null} if for some reason the plugin is not loaded.
     *         The fact the plugin is loaded does not mean it is enabled and fully initialized for the current Jenkins session.
     *         Use {@link Plugin#getWrapper()} and then {@link PluginWrapper#isActive()} to check it.
     */
    @SuppressWarnings("unchecked")
    @CheckForNull
    public <P extends Plugin> P getPlugin(Class<P> clazz) {
        PluginWrapper p = pluginManager.getPlugin(clazz);
        if(p==null)     return null;
        return (P) p.getPlugin();
    }

    /**
     * Gets the plugin objects from their super-class.
     *
     * @param clazz The plugin class (beware class-loader fun)
     *
     * @return The plugin instances.
     */
    public <P extends Plugin> List<P> getPlugins(Class<P> clazz) {
        List<P> result = new ArrayList<P>();
        for (PluginWrapper w: pluginManager.getPlugins(clazz)) {
            result.add((P)w.getPlugin());
        }
        return Collections.unmodifiableList(result);
    }

    /**
     * Synonym for {@link #getDescription}.
     */
    public String getSystemMessage() {
        return systemMessage;
    }

    /**
     * Gets the markup formatter used in the system.
     *
     * @return
     *      never null.
     * @since 1.391
     */
    public @Nonnull MarkupFormatter getMarkupFormatter() {
        MarkupFormatter f = markupFormatter;
        return f != null ? f : new EscapedMarkupFormatter();
    }

    /**
     * Sets the markup formatter used in the system globally.
     *
     * @since 1.391
     */
    public void setMarkupFormatter(MarkupFormatter f) {
        this.markupFormatter = f;
    }

    /**
     * Sets the system message.
     */
    public void setSystemMessage(String message) throws IOException {
        this.systemMessage = message;
        save();
    }

    public FederatedLoginService getFederatedLoginService(String name) {
        for (FederatedLoginService fls : FederatedLoginService.all()) {
            if (fls.getUrlName().equals(name))
                return fls;
        }
        return null;
    }

    public List<FederatedLoginService> getFederatedLoginServices() {
        return FederatedLoginService.all();
    }

    public Launcher createLauncher(TaskListener listener) {
        return new LocalLauncher(listener).decorateFor(this);
    }


    public String getFullName() {
        return "";
    }

    public String getFullDisplayName() {
        return "";
    }

    /**
     * Returns the transient {@link Action}s associated with the top page.
     *
     * <p>
     * Adding {@link Action} is primarily useful for plugins to contribute
     * an item to the navigation bar of the top page. See existing {@link Action}
     * implementation for it affects the GUI.
     *
     * <p>
     * To register an {@link Action}, implement {@link RootAction} extension point, or write code like
     * {@code Jenkins.getInstance().getActions().add(...)}.
     *
     * @return
     *      Live list where the changes can be made. Can be empty but never null.
     * @since 1.172
     */
    public List<Action> getActions() {
        return actions;
    }

    /**
     * Gets just the immediate children of {@link Jenkins}.
     *
     * @see #getAllItems(Class)
     */
    @Exported(name="jobs")
    public List<TopLevelItem> getItems() {
        List<TopLevelItem> viewableItems = new ArrayList<TopLevelItem>();
        for (TopLevelItem item : items.values()) {
            if (item.hasPermission(Item.READ))
                viewableItems.add(item);
        }

        return viewableItems;
    }

    /**
     * Returns the read-only view of all the {@link TopLevelItem}s keyed by their names.
     * <p>
     * This method is efficient, as it doesn't involve any copying.
     *
     * @since 1.296
     */
    public Map<String,TopLevelItem> getItemMap() {
        return Collections.unmodifiableMap(items);
    }

    /**
     * Gets just the immediate children of {@link Jenkins} but of the given type.
     */
    public <T> List<T> getItems(Class<T> type) {
        List<T> r = new ArrayList<T>();
        for (TopLevelItem i : getItems())
            if (type.isInstance(i))
                 r.add(type.cast(i));
        return r;
    }

    /**
     * Gets all the {@link Item}s recursively in the {@link ItemGroup} tree
     * and filter them by the given type.
     */
    public <T extends Item> List<T> getAllItems(Class<T> type) {
        return Items.getAllItems(this, type);
    }

    /**
     * Gets all the {@link Item}s unordered, lazily and recursively in the {@link ItemGroup} tree
     * and filter them by the given type.
     *
     * @since 2.37
     */
    public <T extends Item> Iterable<T> allItems(Class<T> type) {
        return Items.allItems(this, type);
    }

    /**
     * Gets all the items recursively.
     *
     * @since 1.402
     */
    public List<Item> getAllItems() {
        return getAllItems(Item.class);
    }

    /**
     * Gets all the items unordered, lazily and recursively.
     *
     * @since 2.37
     */
    public Iterable<Item> allItems() {
        return allItems(Item.class);
    }

    /**
     * Gets a list of simple top-level projects.
     * @deprecated This method will ignore Maven and matrix projects, as well as projects inside containers such as folders.
     * You may prefer to call {@link #getAllItems(Class)} on {@link AbstractProject},
     * perhaps also using {@link Util#createSubList} to consider only {@link TopLevelItem}s.
     * (That will also consider the caller's permissions.)
     * If you really want to get just {@link Project}s at top level, ignoring permissions,
     * you can filter the values from {@link #getItemMap} using {@link Util#createSubList}.
     */
    @Deprecated
    public List<Project> getProjects() {
        return Util.createSubList(items.values(), Project.class);
    }

    /**
     * Gets the names of all the {@link Job}s.
     */
    public Collection<String> getJobNames() {
        List<String> names = new ArrayList<String>();
        for (Job j : allItems(Job.class))
            names.add(j.getFullName());
        Collections.sort(names, String.CASE_INSENSITIVE_ORDER);
        return names;
    }

    public List<Action> getViewActions() {
        return getActions();
    }

    /**
     * Gets the names of all the {@link TopLevelItem}s.
     */
    public Collection<String> getTopLevelItemNames() {
        List<String> names = new ArrayList<String>();
        for (TopLevelItem j : items.values())
            names.add(j.getName());
        return names;
    }

    /**
     * Gets a view by the specified name.
     * The method iterates through {@link hudson.model.ViewGroup}s if required.
     * @param name Name of the view
     * @return View instance or {@code null} if it is missing
     */
    @CheckForNull
    public View getView(@CheckForNull String name) {
        return viewGroupMixIn.getView(name);
    }

    /**
     * Gets the read-only list of all {@link View}s.
     */
    @Exported
    public Collection<View> getViews() {
        return viewGroupMixIn.getViews();
    }

    @Override
    public void addView(View v) throws IOException {
        viewGroupMixIn.addView(v);
    }

    /**
     * Completely replaces views.
     *
     * <p>
     * This operation is NOT provided as an atomic operation, but rather
     * the sole purpose of this is to define a setter for this to help
     * introspecting code, such as system-config-dsl plugin
     */
    // even if we want to offer this atomic operation, CopyOnWriteArrayList
    // offers no such operation
    public void setViews(Collection<View> views) throws IOException {
        BulkChange bc = new BulkChange(this);
        try {
            this.views.clear();
            for (View v : views) {
                addView(v);
            }
        } finally {
            bc.commit();
        }
    }

    public boolean canDelete(View view) {
        return viewGroupMixIn.canDelete(view);
    }

    public synchronized void deleteView(View view) throws IOException {
        viewGroupMixIn.deleteView(view);
    }

    public void onViewRenamed(View view, String oldName, String newName) {
        viewGroupMixIn.onViewRenamed(view, oldName, newName);
    }

    /**
     * Returns the primary {@link View} that renders the top-page of Jenkins.
     */
    @Exported
    public View getPrimaryView() {
        return viewGroupMixIn.getPrimaryView();
     }

    public void setPrimaryView(@Nonnull View v) {
        this.primaryView = v.getViewName();
    }

    public ViewsTabBar getViewsTabBar() {
        return viewsTabBar;
    }

    public void setViewsTabBar(ViewsTabBar viewsTabBar) {
        this.viewsTabBar = viewsTabBar;
    }

    public Jenkins getItemGroup() {
        return this;
   }

    public MyViewsTabBar getMyViewsTabBar() {
        return myViewsTabBar;
    }

    public void setMyViewsTabBar(MyViewsTabBar myViewsTabBar) {
        this.myViewsTabBar = myViewsTabBar;
    }

    /**
     * Returns true if the current running Jenkins is upgraded from a version earlier than the specified version.
     *
     * <p>
     * This method continues to return true until the system configuration is saved, at which point
     * {@link #version} will be overwritten and Jenkins forgets the upgrade history.
     *
     * <p>
     * To handle SNAPSHOTS correctly, pass in "1.N.*" to test if it's upgrading from the version
     * equal or younger than N. So say if you implement a feature in 1.301 and you want to check
     * if the installation upgraded from pre-1.301, pass in "1.300.*"
     *
     * @since 1.301
     */
    public boolean isUpgradedFromBefore(VersionNumber v) {
        try {
            return new VersionNumber(version).isOlderThan(v);
        } catch (IllegalArgumentException e) {
            // fail to parse this version number
            return false;
        }
    }

    /**
     * Gets the read-only list of all {@link Computer}s.
     */
    public Computer[] getComputers() {
        Computer[] r = computers.values().toArray(new Computer[computers.size()]);
        Arrays.sort(r,new Comparator<Computer>() {
            @Override public int compare(Computer lhs, Computer rhs) {
                if(lhs.getNode()==Jenkins.this)  return -1;
                if(rhs.getNode()==Jenkins.this)  return 1;
                return lhs.getName().compareTo(rhs.getName());
            }
        });
        return r;
    }

    @CLIResolver
    public @CheckForNull Computer getComputer(@Argument(required=true,metaVar="NAME",usage="Node name") @Nonnull String name) {
        if(name.equals("(master)"))
            name = "";

        for (Computer c : computers.values()) {
            if(c.getName().equals(name))
                return c;
        }
        return null;
    }

    /**
     * Gets the label that exists on this system by the name.
     *
     * @return null if name is null.
     * @see Label#parseExpression(String) (String)
     */
    public Label getLabel(String expr) {
        if(expr==null)  return null;
        expr = hudson.util.QuotedStringTokenizer.unquote(expr);
        while(true) {
            Label l = labels.get(expr);
            if(l!=null)
                return l;

            // non-existent
            try {
                labels.putIfAbsent(expr,Label.parseExpression(expr));
            } catch (ANTLRException e) {
                // laxly accept it as a single label atom for backward compatibility
                return getLabelAtom(expr);
            }
        }
    }

    /**
     * Returns the label atom of the given name.
     * @return non-null iff name is non-null
     */
    public @Nullable LabelAtom getLabelAtom(@CheckForNull String name) {
        if (name==null)  return null;

        while(true) {
            Label l = labels.get(name);
            if(l!=null)
                return (LabelAtom)l;

            // non-existent
            LabelAtom la = new LabelAtom(name);
            if (labels.putIfAbsent(name, la)==null)
                la.load();
        }
    }

    /**
     * Gets all the active labels in the current system.
     */
    public Set<Label> getLabels() {
        Set<Label> r = new TreeSet<Label>();
        for (Label l : labels.values()) {
            if(!l.isEmpty())
                r.add(l);
        }
        return r;
    }

    public Set<LabelAtom> getLabelAtoms() {
        Set<LabelAtom> r = new TreeSet<LabelAtom>();
        for (Label l : labels.values()) {
            if(!l.isEmpty() && l instanceof LabelAtom)
                r.add((LabelAtom)l);
        }
        return r;
    }

    public Queue getQueue() {
        return queue;
    }

    @Override
    public String getDisplayName() {
        return Messages.Hudson_DisplayName();
    }

    public List<JDK> getJDKs() {
        return jdks;
    }

    /**
     * Replaces all JDK installations with those from the given collection.
     *
     * Use {@link hudson.model.JDK.DescriptorImpl#setInstallations(JDK...)} to
     * set JDK installations from external code.
     */
    @Restricted(NoExternalUse.class)
    public void setJDKs(Collection<? extends JDK> jdks) {
        this.jdks = new ArrayList<JDK>(jdks);
    }

    /**
     * Gets the JDK installation of the given name, or returns null.
     */
    public JDK getJDK(String name) {
        if(name==null) {
            // if only one JDK is configured, "default JDK" should mean that JDK.
            List<JDK> jdks = getJDKs();
            if(jdks.size()==1)  return jdks.get(0);
            return null;
        }
        for (JDK j : getJDKs()) {
            if(j.getName().equals(name))
                return j;
        }
        return null;
    }



    /**
     * Gets the agent node of the give name, hooked under this Jenkins.
     */
    public @CheckForNull Node getNode(String name) {
        return nodes.getNode(name);
    }

    /**
     * Gets a {@link Cloud} by {@link Cloud#name its name}, or null.
     */
    public Cloud getCloud(String name) {
        return clouds.getByName(name);
    }

    protected Map<Node,Computer> getComputerMap() {
        return computers;
    }

    /**
     * Returns all {@link Node}s in the system, excluding {@link Jenkins} instance itself which
     * represents the master.
     */
    public List<Node> getNodes() {
        return nodes.getNodes();
    }

    /**
     * Get the {@link Nodes} object that handles maintaining individual {@link Node}s.
     * @return The Nodes object.
     */
    @Restricted(NoExternalUse.class)
    public Nodes getNodesObject() {
        // TODO replace this with something better when we properly expose Nodes.
        return nodes;
    }

    /**
     * Adds one more {@link Node} to Jenkins.
     */
    public void addNode(Node n) throws IOException {
        nodes.addNode(n);
    }

    /**
     * Removes a {@link Node} from Jenkins.
     */
    public void removeNode(@Nonnull Node n) throws IOException {
        nodes.removeNode(n);
    }

    /**
     * Saves an existing {@link Node} on disk, called by {@link Node#save()}. This method is preferred in those cases
     * where you need to determine atomically that the node being saved is actually in the list of nodes.
     *
     * @param n the node to be updated.
     * @return {@code true}, if the node was updated. {@code false}, if the node was not in the list of nodes.
     * @throws IOException if the node could not be persisted.
     * @see Nodes#updateNode
     * @since 1.634
     */
    public boolean updateNode(Node n) throws IOException {
        return nodes.updateNode(n);
    }

    public void setNodes(final List<? extends Node> n) throws IOException {
        nodes.setNodes(n);
    }

    public DescribableList<NodeProperty<?>, NodePropertyDescriptor> getNodeProperties() {
        return nodeProperties;
    }

    public DescribableList<NodeProperty<?>, NodePropertyDescriptor> getGlobalNodeProperties() {
        return globalNodeProperties;
    }

    /**
     * Resets all labels and remove invalid ones.
     *
     * This should be called when the assumptions behind label cache computation changes,
     * but we also call this periodically to self-heal any data out-of-sync issue.
     */
    /*package*/ void trimLabels() {
        for (Iterator<Label> itr = labels.values().iterator(); itr.hasNext();) {
            Label l = itr.next();
            resetLabel(l);
            if(l.isEmpty())
                itr.remove();
        }
    }

    /**
     * Binds {@link AdministrativeMonitor}s to URL.
     */
    public AdministrativeMonitor getAdministrativeMonitor(String id) {
        for (AdministrativeMonitor m : administrativeMonitors)
            if(m.id.equals(id))
                return m;
        return null;
    }

    public NodeDescriptor getDescriptor() {
        return DescriptorImpl.INSTANCE;
    }

    public static final class DescriptorImpl extends NodeDescriptor {
        @Extension
        public static final DescriptorImpl INSTANCE = new DescriptorImpl();

        @Override
        public boolean isInstantiable() {
            return false;
        }

        public FormValidation doCheckNumExecutors(@QueryParameter String value) {
            return FormValidation.validateNonNegativeInteger(value);
        }

        public FormValidation doCheckRawBuildsDir(@QueryParameter String value) {
            // do essentially what expandVariablesForDirectory does, without an Item
            String replacedValue = expandVariablesForDirectory(value,
                    "doCheckRawBuildsDir-Marker:foo",
                    Jenkins.getInstance().getRootDir().getPath() + "/jobs/doCheckRawBuildsDir-Marker$foo");

            File replacedFile = new File(replacedValue);
            if (!replacedFile.isAbsolute()) {
                return FormValidation.error(value + " does not resolve to an absolute path");
            }

            if (!replacedValue.contains("doCheckRawBuildsDir-Marker")) {
                return FormValidation.error(value + " does not contain ${ITEM_FULL_NAME} or ${ITEM_ROOTDIR}, cannot distinguish between projects");
            }

            if (replacedValue.contains("doCheckRawBuildsDir-Marker:foo")) {
                // make sure platform can handle colon
                try {
                    File tmp = File.createTempFile("Jenkins-doCheckRawBuildsDir", "foo:bar");
                    tmp.delete();
                } catch (IOException e) {
                    return FormValidation.error(value + " contains ${ITEM_FULLNAME} but your system does not support it (JENKINS-12251). Use ${ITEM_FULL_NAME} instead");
                }
            }

            File d = new File(replacedValue);
            if (!d.isDirectory()) {
                // if dir does not exist (almost guaranteed) need to make sure nearest existing ancestor can be written to
                d = d.getParentFile();
                while (!d.exists()) {
                    d = d.getParentFile();
                }
                if (!d.canWrite()) {
                    return FormValidation.error(value + " does not exist and probably cannot be created");
                }
            }

            return FormValidation.ok();
        }

        // to route /descriptor/FQCN/xxx to getDescriptor(FQCN).xxx
        public Object getDynamic(String token) {
            return Jenkins.getInstance().getDescriptor(token);
        }
    }

    /**
     * Gets the system default quiet period.
     */
    public int getQuietPeriod() {
        return quietPeriod!=null ? quietPeriod : 5;
    }

    /**
     * Sets the global quiet period.
     *
     * @param quietPeriod
     *      null to the default value.
     */
    public void setQuietPeriod(Integer quietPeriod) throws IOException {
        this.quietPeriod = quietPeriod;
        save();
    }

    /**
     * Gets the global SCM check out retry count.
     */
    public int getScmCheckoutRetryCount() {
        return scmCheckoutRetryCount;
    }

    public void setScmCheckoutRetryCount(int scmCheckoutRetryCount) throws IOException {
        this.scmCheckoutRetryCount = scmCheckoutRetryCount;
        save();
    }

    @Override
    public String getSearchUrl() {
        return "";
    }

    @Override
    public SearchIndexBuilder makeSearchIndex() {
        SearchIndexBuilder builder = super.makeSearchIndex();
        if (hasPermission(ADMINISTER)) {
                builder.add("configure", "config", "configure")
                    .add("manage")
                    .add("log");
        }
        builder.add(new CollectionSearchIndex<TopLevelItem>() {
                    protected SearchItem get(String key) { return getItemByFullName(key, TopLevelItem.class); }
                    protected Collection<TopLevelItem> all() { return getAllItems(TopLevelItem.class); }
                    @Nonnull
                    @Override
                    protected Iterable<TopLevelItem> allAsIterable() {
                        return allItems(TopLevelItem.class);
                    }
                })
                .add(getPrimaryView().makeSearchIndex())
                .add(new CollectionSearchIndex() {// for computers
                    protected Computer get(String key) { return getComputer(key); }
                    protected Collection<Computer> all() { return computers.values(); }
                })
                .add(new CollectionSearchIndex() {// for users
                    protected User get(String key) { return User.get(key,false); }
                    protected Collection<User> all() { return User.getAll(); }
                })
                .add(new CollectionSearchIndex() {// for views
                    protected View get(String key) { return getView(key); }
                    protected Collection<View> all() { return viewGroupMixIn.getViews(); }
                });
        return builder;
    }

    public String getUrlChildPrefix() {
        return "job";
    }

    /**
     * Gets the absolute URL of Jenkins, such as {@code http://localhost/jenkins/}.
     *
     * <p>
     * This method first tries to use the manually configured value, then
     * fall back to {@link #getRootUrlFromRequest}.
     * It is done in this order so that it can work correctly even in the face
     * of a reverse proxy.
     *
     * @return null if this parameter is not configured by the user and the calling thread is not in an HTTP request; otherwise the returned URL will always have the trailing {@code /}
     * @since 1.66
     * @see <a href="https://wiki.jenkins-ci.org/display/JENKINS/Hyperlinks+in+HTML">Hyperlinks in HTML</a>
     */
    public @Nullable String getRootUrl() {
        String url = JenkinsLocationConfiguration.get().getUrl();
        if(url!=null) {
            return Util.ensureEndsWith(url,"/");
        }
        StaplerRequest req = Stapler.getCurrentRequest();
        if(req!=null)
            return getRootUrlFromRequest();
        return null;
    }

    /**
     * Is Jenkins running in HTTPS?
     *
     * Note that we can't really trust {@link StaplerRequest#isSecure()} because HTTPS might be terminated
     * in the reverse proxy.
     */
    public boolean isRootUrlSecure() {
        String url = getRootUrl();
        return url!=null && url.startsWith("https");
    }

    /**
     * Gets the absolute URL of Jenkins top page, such as {@code http://localhost/jenkins/}.
     *
     * <p>
     * Unlike {@link #getRootUrl()}, which uses the manually configured value,
     * this one uses the current request to reconstruct the URL. The benefit is
     * that this is immune to the configuration mistake (users often fail to set the root URL
     * correctly, especially when a migration is involved), but the downside
     * is that unless you are processing a request, this method doesn't work.
     *
     * <p>Please note that this will not work in all cases if Jenkins is running behind a
     * reverse proxy which has not been fully configured.
     * Specifically the {@code Host} and {@code X-Forwarded-Proto} headers must be set.
     * <a href="https://wiki.jenkins-ci.org/display/JENKINS/Running+Jenkins+behind+Apache">Running Jenkins behind Apache</a>
     * shows some examples of configuration.
     * @since 1.263
     */
    public @Nonnull String getRootUrlFromRequest() {
        StaplerRequest req = Stapler.getCurrentRequest();
        if (req == null) {
            throw new IllegalStateException("cannot call getRootUrlFromRequest from outside a request handling thread");
        }
        StringBuilder buf = new StringBuilder();
        String scheme = getXForwardedHeader(req, "X-Forwarded-Proto", req.getScheme());
        buf.append(scheme).append("://");
        String host = getXForwardedHeader(req, "X-Forwarded-Host", req.getServerName());
        int index = host.indexOf(':');
        int port = req.getServerPort();
        if (index == -1) {
            // Almost everyone else except Nginx put the host and port in separate headers
            buf.append(host);
        } else {
            // Nginx uses the same spec as for the Host header, i.e. hostname:port
            buf.append(host.substring(0, index));
            if (index + 1 < host.length()) {
                try {
                    port = Integer.parseInt(host.substring(index + 1));
                } catch (NumberFormatException e) {
                    // ignore
                }
            }
            // but if a user has configured Nginx with an X-Forwarded-Port, that will win out.
        }
        String forwardedPort = getXForwardedHeader(req, "X-Forwarded-Port", null);
        if (forwardedPort != null) {
            try {
                port = Integer.parseInt(forwardedPort);
            } catch (NumberFormatException e) {
                // ignore
            }
        }
        if (port != ("https".equals(scheme) ? 443 : 80)) {
            buf.append(':').append(port);
        }
        buf.append(req.getContextPath()).append('/');
        return buf.toString();
    }

    /**
     * Gets the originating "X-Forwarded-..." header from the request. If there are multiple headers the originating
     * header is the first header. If the originating header contains a comma separated list, the originating entry
     * is the first one.
     * @param req the request
     * @param header the header name
     * @param defaultValue the value to return if the header is absent.
     * @return the originating entry of the header or the default value if the header was not present.
     */
    private static String getXForwardedHeader(StaplerRequest req, String header, String defaultValue) {
        String value = req.getHeader(header);
        if (value != null) {
            int index = value.indexOf(',');
            return index == -1 ? value.trim() : value.substring(0,index).trim();
        }
        return defaultValue;
    }

    public File getRootDir() {
        return root;
    }

    public FilePath getWorkspaceFor(TopLevelItem item) {
        for (WorkspaceLocator l : WorkspaceLocator.all()) {
            FilePath workspace = l.locate(item, this);
            if (workspace != null) {
                return workspace;
            }
        }

        return new FilePath(expandVariablesForDirectory(workspaceDir, item));
    }

    public File getBuildDirFor(Job job) {
        return expandVariablesForDirectory(buildsDir, job);
    }

    private File expandVariablesForDirectory(String base, Item item) {
        return new File(expandVariablesForDirectory(base, item.getFullName(), item.getRootDir().getPath()));
    }

    @Restricted(NoExternalUse.class)
    static String expandVariablesForDirectory(String base, String itemFullName, String itemRootDir) {
        return Util.replaceMacro(base, ImmutableMap.of(
                "JENKINS_HOME", Jenkins.getInstance().getRootDir().getPath(),
                "ITEM_ROOTDIR", itemRootDir,
                "ITEM_FULLNAME", itemFullName,   // legacy, deprecated
                "ITEM_FULL_NAME", itemFullName.replace(':','$'))); // safe, see JENKINS-12251

    }

    public String getRawWorkspaceDir() {
        return workspaceDir;
    }

    public String getRawBuildsDir() {
        return buildsDir;
    }

    @Restricted(NoExternalUse.class)
    public void setRawBuildsDir(String buildsDir) {
        this.buildsDir = buildsDir;
    }

    @Override public @Nonnull FilePath getRootPath() {
        return new FilePath(getRootDir());
    }

    @Override
    public FilePath createPath(String absolutePath) {
        return new FilePath((VirtualChannel)null,absolutePath);
    }

    public ClockDifference getClockDifference() {
        return ClockDifference.ZERO;
    }

    @Override
    public Callable<ClockDifference, IOException> getClockDifferenceCallable() {
        return new MasterToSlaveCallable<ClockDifference, IOException>() {
            public ClockDifference call() throws IOException {
                return new ClockDifference(0);
            }
        };
    }

    /**
     * For binding {@link LogRecorderManager} to "/log".
     * Everything below here is admin-only, so do the check here.
     */
    public LogRecorderManager getLog() {
        checkPermission(ADMINISTER);
        return log;
    }

    /**
     * A convenience method to check if there's some security
     * restrictions in place.
     */
    @Exported
    public boolean isUseSecurity() {
        return securityRealm!=SecurityRealm.NO_AUTHENTICATION || authorizationStrategy!=AuthorizationStrategy.UNSECURED;
    }

    public boolean isUseProjectNamingStrategy(){
        return projectNamingStrategy != DefaultProjectNamingStrategy.DEFAULT_NAMING_STRATEGY;
    }

    /**
     * If true, all the POST requests to Jenkins would have to have crumb in it to protect
     * Jenkins from CSRF vulnerabilities.
     */
    @Exported
    public boolean isUseCrumbs() {
        return crumbIssuer!=null;
    }

    /**
     * Returns the constant that captures the three basic security modes in Jenkins.
     */
    public SecurityMode getSecurity() {
        // fix the variable so that this code works under concurrent modification to securityRealm.
        SecurityRealm realm = securityRealm;

        if(realm==SecurityRealm.NO_AUTHENTICATION)
            return SecurityMode.UNSECURED;
        if(realm instanceof LegacySecurityRealm)
            return SecurityMode.LEGACY;
        return SecurityMode.SECURED;
    }

    /**
     * @return
     *      never null.
     */
    public SecurityRealm getSecurityRealm() {
        return securityRealm;
    }

    public void setSecurityRealm(SecurityRealm securityRealm) {
        if(securityRealm==null)
            securityRealm= SecurityRealm.NO_AUTHENTICATION;
        this.useSecurity = true;
        IdStrategy oldUserIdStrategy = this.securityRealm == null
                ? securityRealm.getUserIdStrategy() // don't trigger rekey on Jenkins load
                : this.securityRealm.getUserIdStrategy();
        this.securityRealm = securityRealm;
        // reset the filters and proxies for the new SecurityRealm
        try {
            HudsonFilter filter = HudsonFilter.get(servletContext);
            if (filter == null) {
                // Fix for #3069: This filter is not necessarily initialized before the servlets.
                // when HudsonFilter does come back, it'll initialize itself.
                LOGGER.fine("HudsonFilter has not yet been initialized: Can't perform security setup for now");
            } else {
                LOGGER.fine("HudsonFilter has been previously initialized: Setting security up");
                filter.reset(securityRealm);
                LOGGER.fine("Security is now fully set up");
            }
            if (!oldUserIdStrategy.equals(this.securityRealm.getUserIdStrategy())) {
                User.rekey();
            }
        } catch (ServletException e) {
            // for binary compatibility, this method cannot throw a checked exception
            throw new AcegiSecurityException("Failed to configure filter",e) {};
        }
        saveQuietly();
    }

    public void setAuthorizationStrategy(AuthorizationStrategy a) {
        if (a == null)
            a = AuthorizationStrategy.UNSECURED;
        useSecurity = true;
        authorizationStrategy = a;
        saveQuietly();
    }

    public boolean isDisableRememberMe() {
        return disableRememberMe;
    }

    public void setDisableRememberMe(boolean disableRememberMe) {
        this.disableRememberMe = disableRememberMe;
    }

    public void disableSecurity() {
        useSecurity = null;
        setSecurityRealm(SecurityRealm.NO_AUTHENTICATION);
        authorizationStrategy = AuthorizationStrategy.UNSECURED;
    }

    public void setProjectNamingStrategy(ProjectNamingStrategy ns) {
        if(ns == null){
            ns = DefaultProjectNamingStrategy.DEFAULT_NAMING_STRATEGY;
        }
        projectNamingStrategy = ns;
    }

    public Lifecycle getLifecycle() {
        return Lifecycle.get();
    }

    /**
     * Gets the dependency injection container that hosts all the extension implementations and other
     * components in Jenkins.
     *
     * @since 1.433
     */
    public Injector getInjector() {
        return lookup(Injector.class);
    }

    /**
     * Returns {@link ExtensionList} that retains the discovered instances for the given extension type.
     *
     * @param extensionType
     *      The base type that represents the extension point. Normally {@link ExtensionPoint} subtype
     *      but that's not a hard requirement.
     * @return
     *      Can be an empty list but never null.
     * @see ExtensionList#lookup
     */
    @SuppressWarnings({"unchecked"})
    public <T> ExtensionList<T> getExtensionList(Class<T> extensionType) {
        return extensionLists.get(extensionType);
    }

    /**
     * Used to bind {@link ExtensionList}s to URLs.
     *
     * @since 1.349
     */
    public ExtensionList getExtensionList(String extensionType) throws ClassNotFoundException {
        return getExtensionList(pluginManager.uberClassLoader.loadClass(extensionType));
    }

    /**
     * Returns {@link ExtensionList} that retains the discovered {@link Descriptor} instances for the given
     * kind of {@link Describable}.
     *
     * @return
     *      Can be an empty list but never null.
     */
    @SuppressWarnings({"unchecked"})
    public <T extends Describable<T>,D extends Descriptor<T>> DescriptorExtensionList<T,D> getDescriptorList(Class<T> type) {
        return descriptorLists.get(type);
    }

    /**
     * Refresh {@link ExtensionList}s by adding all the newly discovered extensions.
     *
     * Exposed only for {@link PluginManager#dynamicLoad(File)}.
     */
    public void refreshExtensions() throws ExtensionRefreshException {
        ExtensionList<ExtensionFinder> finders = getExtensionList(ExtensionFinder.class);
        for (ExtensionFinder ef : finders) {
            if (!ef.isRefreshable())
                throw new ExtensionRefreshException(ef+" doesn't support refresh");
        }

        List<ExtensionComponentSet> fragments = Lists.newArrayList();
        for (ExtensionFinder ef : finders) {
            fragments.add(ef.refresh());
        }
        ExtensionComponentSet delta = ExtensionComponentSet.union(fragments).filtered();

        // if we find a new ExtensionFinder, we need it to list up all the extension points as well
        List<ExtensionComponent<ExtensionFinder>> newFinders = Lists.newArrayList(delta.find(ExtensionFinder.class));
        while (!newFinders.isEmpty()) {
            ExtensionFinder f = newFinders.remove(newFinders.size()-1).getInstance();

            ExtensionComponentSet ecs = ExtensionComponentSet.allOf(f).filtered();
            newFinders.addAll(ecs.find(ExtensionFinder.class));
            delta = ExtensionComponentSet.union(delta, ecs);
        }

        for (ExtensionList el : extensionLists.values()) {
            el.refresh(delta);
        }
        for (ExtensionList el : descriptorLists.values()) {
            el.refresh(delta);
        }

        // TODO: we need some generalization here so that extension points can be notified when a refresh happens?
        for (ExtensionComponent<RootAction> ea : delta.find(RootAction.class)) {
            Action a = ea.getInstance();
            if (!actions.contains(a)) actions.add(a);
        }
    }

    /**
     * Returns the root {@link ACL}.
     *
     * @see AuthorizationStrategy#getRootACL()
     */
    @Override
    public ACL getACL() {
        return authorizationStrategy.getRootACL();
    }

    /**
     * @return
     *      never null.
     */
    public AuthorizationStrategy getAuthorizationStrategy() {
        return authorizationStrategy;
    }

    /**
     * The strategy used to check the project names.
     * @return never <code>null</code>
     */
    public ProjectNamingStrategy getProjectNamingStrategy() {
        return projectNamingStrategy == null ? ProjectNamingStrategy.DEFAULT_NAMING_STRATEGY : projectNamingStrategy;
    }

    /**
     * Returns true if Jenkins is quieting down.
     * <p>
     * No further jobs will be executed unless it
     * can be finished while other current pending builds
     * are still in progress.
     */
    @Exported
    public boolean isQuietingDown() {
        return isQuietingDown;
    }

    /**
     * Returns true if the container initiated the termination of the web application.
     */
    public boolean isTerminating() {
        return terminating;
    }

    /**
     * Gets the initialization milestone that we've already reached.
     *
     * @return
     *      {@link InitMilestone#STARTED} even if the initialization hasn't been started, so that this method
     *      never returns null.
     */
    public InitMilestone getInitLevel() {
        return initLevel;
    }

    public void setNumExecutors(int n) throws IOException {
        if (this.numExecutors != n) {
            this.numExecutors = n;
            updateComputerList();
            save();
        }
    }



    /**
     * {@inheritDoc}.
     *
     * Note that the look up is case-insensitive.
     */
    @Override public TopLevelItem getItem(String name) throws AccessDeniedException {
        if (name==null)    return null;
        TopLevelItem item = items.get(name);
        if (item==null)
            return null;
        if (!item.hasPermission(Item.READ)) {
            if (item.hasPermission(Item.DISCOVER)) {
                throw new AccessDeniedException("Please login to access job " + name);
            }
            return null;
        }
        return item;
    }

    /**
     * Gets the item by its path name from the given context
     *
     * <h2>Path Names</h2>
     * <p>
     * If the name starts from '/', like "/foo/bar/zot", then it's interpreted as absolute.
     * Otherwise, the name should be something like "foo/bar" and it's interpreted like
     * relative path name in the file system is, against the given context.
     * <p>For compatibility, as a fallback when nothing else matches, a simple path
     * like {@code foo/bar} can also be treated with {@link #getItemByFullName}.
     * @param context
     *      null is interpreted as {@link Jenkins}. Base 'directory' of the interpretation.
     * @since 1.406
     */
    public Item getItem(String pathName, ItemGroup context) {
        if (context==null)  context = this;
        if (pathName==null) return null;

        if (pathName.startsWith("/"))   // absolute
            return getItemByFullName(pathName);

        Object/*Item|ItemGroup*/ ctx = context;

        StringTokenizer tokens = new StringTokenizer(pathName,"/");
        while (tokens.hasMoreTokens()) {
            String s = tokens.nextToken();
            if (s.equals("..")) {
                if (ctx instanceof Item) {
                    ctx = ((Item)ctx).getParent();
                    continue;
                }

                ctx=null;    // can't go up further
                break;
            }
            if (s.equals(".")) {
                continue;
            }

            if (ctx instanceof ItemGroup) {
                ItemGroup g = (ItemGroup) ctx;
                Item i = g.getItem(s);
                if (i==null || !i.hasPermission(Item.READ)) { // TODO consider DISCOVER
                    ctx=null;    // can't go up further
                    break;
                }
                ctx=i;
            } else {
                return null;
            }
        }

        if (ctx instanceof Item)
            return (Item)ctx;

        // fall back to the classic interpretation
        return getItemByFullName(pathName);
    }

    public final Item getItem(String pathName, Item context) {
        return getItem(pathName,context!=null?context.getParent():null);
    }

    public final <T extends Item> T getItem(String pathName, ItemGroup context, @Nonnull Class<T> type) {
        Item r = getItem(pathName, context);
        if (type.isInstance(r))
            return type.cast(r);
        return null;
    }

    public final <T extends Item> T getItem(String pathName, Item context, Class<T> type) {
        return getItem(pathName,context!=null?context.getParent():null,type);
    }

    public File getRootDirFor(TopLevelItem child) {
        return getRootDirFor(child.getName());
    }

    private File getRootDirFor(String name) {
        return new File(new File(getRootDir(),"jobs"), name);
    }

    /**
     * Gets the {@link Item} object by its full name.
     * Full names are like path names, where each name of {@link Item} is
     * combined by '/'.
     *
     * @return
     *      null if either such {@link Item} doesn't exist under the given full name,
     *      or it exists but it's no an instance of the given type.
     * @throws AccessDeniedException as per {@link ItemGroup#getItem}
     */
    public @CheckForNull <T extends Item> T getItemByFullName(String fullName, Class<T> type) throws AccessDeniedException {
        StringTokenizer tokens = new StringTokenizer(fullName,"/");
        ItemGroup parent = this;

        if(!tokens.hasMoreTokens()) return null;    // for example, empty full name.

        while(true) {
            Item item = parent.getItem(tokens.nextToken());
            if(!tokens.hasMoreTokens()) {
                if(type.isInstance(item))
                    return type.cast(item);
                else
                    return null;
            }

            if(!(item instanceof ItemGroup))
                return null;    // this item can't have any children

            if (!item.hasPermission(Item.READ))
                return null; // TODO consider DISCOVER

            parent = (ItemGroup) item;
        }
    }

    public @CheckForNull Item getItemByFullName(String fullName) {
        return getItemByFullName(fullName,Item.class);
    }

    /**
     * Gets the user of the given name.
     *
     * @return the user of the given name (which may or may not be an id), if that person exists; else null
     * @see User#get(String,boolean)
     * @see User#getById(String, boolean)
     */
    public @CheckForNull User getUser(String name) {
        return User.get(name, User.ALLOW_USER_CREATION_VIA_URL && hasPermission(ADMINISTER));
    }

    public synchronized TopLevelItem createProject( TopLevelItemDescriptor type, String name ) throws IOException {
        return createProject(type, name, true);
    }

    public synchronized TopLevelItem createProject( TopLevelItemDescriptor type, String name, boolean notify ) throws IOException {
        return itemGroupMixIn.createProject(type,name,notify);
    }

    /**
     * Overwrites the existing item by new one.
     *
     * <p>
     * This is a short cut for deleting an existing job and adding a new one.
     */
    public synchronized void putItem(TopLevelItem item) throws IOException, InterruptedException {
        String name = item.getName();
        TopLevelItem old = items.get(name);
        if (old ==item)  return; // noop

        checkPermission(Item.CREATE);
        if (old!=null)
            old.delete();
        items.put(name,item);
        ItemListener.fireOnCreated(item);
    }

    /**
     * Creates a new job.
     *
     * <p>
     * This version infers the descriptor from the type of the top-level item.
     *
     * @throws IllegalArgumentException
     *      if the project of the given name already exists.
     */
    public synchronized <T extends TopLevelItem> T createProject( Class<T> type, String name ) throws IOException {
        return type.cast(createProject((TopLevelItemDescriptor)getDescriptor(type),name));
    }

    /**
     * Called by {@link Job#renameTo(String)} to update relevant data structure.
     * assumed to be synchronized on Jenkins by the caller.
     */
    public void onRenamed(TopLevelItem job, String oldName, String newName) throws IOException {
        items.remove(oldName);
        items.put(newName,job);

        // For compatibility with old views:
        for (View v : views)
            v.onJobRenamed(job, oldName, newName);
    }

    /**
     * Called in response to {@link Job#doDoDelete(StaplerRequest, StaplerResponse)}
     */
    public void onDeleted(TopLevelItem item) throws IOException {
        ItemListener.fireOnDeleted(item);

        items.remove(item.getName());
        // For compatibility with old views:
        for (View v : views)
            v.onJobRenamed(item, item.getName(), null);
    }

    @Override public boolean canAdd(TopLevelItem item) {
        return true;
    }

    @Override synchronized public <I extends TopLevelItem> I add(I item, String name) throws IOException, IllegalArgumentException {
        if (items.containsKey(name)) {
            throw new IllegalArgumentException("already an item '" + name + "'");
        }
        items.put(name, item);
        return item;
    }

    @Override public void remove(TopLevelItem item) throws IOException, IllegalArgumentException {
        items.remove(item.getName());
    }

    public FingerprintMap getFingerprintMap() {
        return fingerprintMap;
    }

    // if no finger print matches, display "not found page".
    public Object getFingerprint( String md5sum ) throws IOException {
        Fingerprint r = fingerprintMap.get(md5sum);
        if(r==null)     return new NoFingerprintMatch(md5sum);
        else            return r;
    }

    /**
     * Gets a {@link Fingerprint} object if it exists.
     * Otherwise null.
     */
    public Fingerprint _getFingerprint( String md5sum ) throws IOException {
        return fingerprintMap.get(md5sum);
    }

    /**
     * The file we save our configuration.
     */
    private XmlFile getConfigFile() {
        return new XmlFile(XSTREAM, new File(root,"config.xml"));
    }

    public int getNumExecutors() {
        return numExecutors;
    }

    public Mode getMode() {
        return mode;
    }

    public void setMode(Mode m) throws IOException {
        this.mode = m;
        save();
    }

    public String getLabelString() {
        return fixNull(label).trim();
    }

    @Override
    public void setLabelString(String label) throws IOException {
        this.label = label;
        save();
    }

    @Override
    public LabelAtom getSelfLabel() {
        return getLabelAtom("master");
    }

    public Computer createComputer() {
        return new Hudson.MasterComputer();
    }

    private void loadConfig() throws IOException {
        XmlFile cfg = getConfigFile();
        if (cfg.exists()) {
            // reset some data that may not exist in the disk file
            // so that we can take a proper compensation action later.
            primaryView = null;
            views.clear();

            // load from disk
            cfg.unmarshal(Jenkins.this);
        }
    }

    private synchronized TaskBuilder loadTasks() throws IOException {
        File projectsDir = new File(root,"jobs");
        if(!projectsDir.getCanonicalFile().isDirectory() && !projectsDir.mkdirs()) {
            if(projectsDir.exists())
                throw new IOException(projectsDir+" is not a directory");
            throw new IOException("Unable to create "+projectsDir+"\nPermission issue? Please create this directory manually.");
        }
        File[] subdirs = projectsDir.listFiles();

        final Set<String> loadedNames = Collections.synchronizedSet(new HashSet<String>());

        TaskGraphBuilder g = new TaskGraphBuilder();
        Handle loadJenkins = g.requires(EXTENSIONS_AUGMENTED).attains(JOB_LOADED).add("Loading global config", new Executable() {
            public void run(Reactor session) throws Exception {
                loadConfig();
                // if we are loading old data that doesn't have this field
                if (slaves != null && !slaves.isEmpty() && nodes.isLegacy()) {
                    nodes.setNodes(slaves);
                    slaves = null;
                } else {
                    nodes.load();
                }

                clouds.setOwner(Jenkins.this);
            }
        });

        for (final File subdir : subdirs) {
            g.requires(loadJenkins).attains(JOB_LOADED).notFatal().add("Loading item " + subdir.getName(), new Executable() {
                public void run(Reactor session) throws Exception {
                    if(!Items.getConfigFile(subdir).exists()) {
                        //Does not have job config file, so it is not a jenkins job hence skip it
                        return;
                    }
                    TopLevelItem item = (TopLevelItem) Items.load(Jenkins.this, subdir);
                    items.put(item.getName(), item);
                    loadedNames.add(item.getName());
                }
            });
        }

        g.requires(JOB_LOADED).add("Cleaning up obsolete items deleted from the disk", new Executable() {
            public void run(Reactor reactor) throws Exception {
                // anything we didn't load from disk, throw them away.
                // doing this after loading from disk allows newly loaded items
                // to inspect what already existed in memory (in case of reloading)

                // retainAll doesn't work well because of CopyOnWriteMap implementation, so remove one by one
                // hopefully there shouldn't be too many of them.
                for (String name : items.keySet()) {
                    if (!loadedNames.contains(name))
                        items.remove(name);
                }
            }
        });

        g.requires(JOB_LOADED).add("Finalizing set up",new Executable() {
            public void run(Reactor session) throws Exception {
                rebuildDependencyGraph();

                {// recompute label objects - populates the labels mapping.
                    for (Node slave : nodes.getNodes())
                        // Note that not all labels are visible until the agents have connected.
                        slave.getAssignedLabels();
                    getAssignedLabels();
                }

                // initialize views by inserting the default view if necessary
                // this is both for clean Jenkins and for backward compatibility.
                if(views.size()==0 || primaryView==null) {
                    View v = new AllView(AllView.DEFAULT_VIEW_NAME);
                    setViewOwner(v);
                    views.add(0,v);
                    primaryView = v.getViewName();
                }
                primaryView = AllView.migrateLegacyPrimaryAllViewLocalizedName(views, primaryView);

                if (useSecurity!=null && !useSecurity) {
                    // forced reset to the unsecure mode.
                    // this works as an escape hatch for people who locked themselves out.
                    authorizationStrategy = AuthorizationStrategy.UNSECURED;
                    setSecurityRealm(SecurityRealm.NO_AUTHENTICATION);
                } else {
                    // read in old data that doesn't have the security field set
                    if(authorizationStrategy==null) {
                        if(useSecurity==null)
                            authorizationStrategy = AuthorizationStrategy.UNSECURED;
                        else
                            authorizationStrategy = new LegacyAuthorizationStrategy();
                    }
                    if(securityRealm==null) {
                        if(useSecurity==null)
                            setSecurityRealm(SecurityRealm.NO_AUTHENTICATION);
                        else
                            setSecurityRealm(new LegacySecurityRealm());
                    } else {
                        // force the set to proxy
                        setSecurityRealm(securityRealm);
                    }
                }


                // Initialize the filter with the crumb issuer
                setCrumbIssuer(crumbIssuer);

                // auto register root actions
                for (Action a : getExtensionList(RootAction.class))
                    if (!actions.contains(a)) actions.add(a);
            }
        });

        return g;
    }

    /**
     * Save the settings to a file.
     */
    public synchronized void save() throws IOException {
        if(BulkChange.contains(this))   return;
        getConfigFile().write(this);
        SaveableListener.fireOnChange(this, getConfigFile());
    }

    private void saveQuietly() {
        try {
            save();
        } catch (IOException x) {
            LOGGER.log(Level.WARNING, null, x);
        }
    }

    /**
     * Called to shut down the system.
     */
    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD")
    public void cleanUp() {
        if (theInstance != this && theInstance != null) {
            LOGGER.log(Level.WARNING, "This instance is no longer the singleton, ignoring cleanUp()");
            return;
        }
        synchronized (Jenkins.class) {
            if (cleanUpStarted) {
                LOGGER.log(Level.WARNING, "Jenkins.cleanUp() already started, ignoring repeated cleanUp()");
                return;
            }
            cleanUpStarted = true;
        }
        try {
            LOGGER.log(Level.INFO, "Stopping Jenkins");

            final List<Throwable> errors = new ArrayList<>();

            fireBeforeShutdown(errors);

            _cleanUpRunTerminators(errors);

            terminating = true;

            final Set<Future<?>> pending = _cleanUpDisconnectComputers(errors);

            _cleanUpShutdownUDPBroadcast(errors);

            _cleanUpCloseDNSMulticast(errors);

            _cleanUpInterruptReloadThread(errors);

            _cleanUpShutdownTriggers(errors);

            _cleanUpShutdownTimer(errors);

            _cleanUpShutdownTcpSlaveAgent(errors);

            _cleanUpShutdownPluginManager(errors);

            _cleanUpPersistQueue(errors);

            _cleanUpShutdownThreadPoolForLoad(errors);

            _cleanUpAwaitDisconnects(errors, pending);

            _cleanUpPluginServletFilters(errors);

            _cleanUpReleaseAllLoggers(errors);

            LOGGER.log(Level.INFO, "Jenkins stopped");

            if (!errors.isEmpty()) {
                StringBuilder message = new StringBuilder("Unexpected issues encountered during cleanUp: ");
                Iterator<Throwable> iterator = errors.iterator();
                message.append(iterator.next().getMessage());
                while (iterator.hasNext()) {
                    message.append("; ");
                    message.append(iterator.next().getMessage());
                }
                iterator = errors.iterator();
                RuntimeException exception = new RuntimeException(message.toString(), iterator.next());
                while (iterator.hasNext()) {
                    exception.addSuppressed(iterator.next());
                }
                throw exception;
            }
        } finally {
            theInstance = null;
            if (JenkinsJVM.isJenkinsJVM()) {
                JenkinsJVMAccess._setJenkinsJVM(oldJenkinsJVM);
            }
        }
    }

    private void fireBeforeShutdown(List<Throwable> errors) {
        LOGGER.log(Level.FINE, "Notifying termination");
        for (ItemListener l : ItemListener.all()) {
            try {
                l.onBeforeShutdown();
            } catch (OutOfMemoryError e) {
                // we should just propagate this, no point trying to log
                throw e;
            } catch (LinkageError e) {
                LOGGER.log(Level.WARNING, "ItemListener " + l + ": " + e.getMessage(), e);
                // safe to ignore and continue for this one
            } catch (Throwable e) {
                LOGGER.log(Level.WARNING, "ItemListener " + l + ": " + e.getMessage(), e);
                // save for later
                errors.add(e);
            }
        }
    }

    private void _cleanUpRunTerminators(List<Throwable> errors) {
        try {
            final TerminatorFinder tf = new TerminatorFinder(
                    pluginManager != null ? pluginManager.uberClassLoader : Thread.currentThread().getContextClassLoader());
            new Reactor(tf).execute(new Executor() {
                @Override
                public void execute(Runnable command) {
                    command.run();
                }
            }, new ReactorListener() {
                final Level level = Level.parse(Configuration.getStringConfigParameter("termLogLevel", "FINE"));

                public void onTaskStarted(Task t) {
                    LOGGER.log(level, "Started {0}", InitReactorRunner.getDisplayName(t));
                }

                public void onTaskCompleted(Task t) {
                    LOGGER.log(level, "Completed {0}", InitReactorRunner.getDisplayName(t));
                }

                public void onTaskFailed(Task t, Throwable err, boolean fatal) {
                    LOGGER.log(SEVERE, "Failed " + InitReactorRunner.getDisplayName(t), err);
                }

                public void onAttained(Milestone milestone) {
                    Level lv = level;
                    String s = "Attained " + milestone.toString();
                    if (milestone instanceof TermMilestone) {
                        lv = Level.INFO; // noteworthy milestones --- at least while we debug problems further
                        s = milestone.toString();
                    }
                    LOGGER.log(lv, s);
                }
            });
        } catch (InterruptedException | ReactorException | IOException e) {
            LOGGER.log(SEVERE, "Failed to execute termination",e);
            errors.add(e);
        } catch (OutOfMemoryError e) {
            // we should just propagate this, no point trying to log
            throw e;
        } catch (LinkageError e) {
            LOGGER.log(SEVERE, "Failed to execute termination", e);
            // safe to ignore and continue for this one
        } catch (Throwable e) {
            LOGGER.log(SEVERE, "Failed to execute termination", e);
            // save for later
            errors.add(e);
        }
    }

    private Set<Future<?>> _cleanUpDisconnectComputers(final List<Throwable> errors) {
        LOGGER.log(Level.INFO, "Starting node disconnection");
        final Set<Future<?>> pending = new HashSet<Future<?>>();
        // JENKINS-28840 we know we will be interrupting all the Computers so get the Queue lock once for all
        Queue.withLock(new Runnable() {
            @Override
            public void run() {
                for( Computer c : computers.values() ) {
                    try {
                        c.interrupt();
                        killComputer(c);
                        pending.add(c.disconnect(null));
                    } catch (OutOfMemoryError e) {
                        // we should just propagate this, no point trying to log
                        throw e;
                    } catch (LinkageError e) {
                        LOGGER.log(Level.WARNING, "Could not disconnect " + c + ": " + e.getMessage(), e);
                        // safe to ignore and continue for this one
                    } catch (Throwable e) {
                        LOGGER.log(Level.WARNING, "Could not disconnect " + c + ": " + e.getMessage(), e);
                        // save for later
                        errors.add(e);
                    }
                }
            }
        });
        return pending;
    }

    private void _cleanUpShutdownUDPBroadcast(List<Throwable> errors) {
        if(udpBroadcastThread!=null) {
            LOGGER.log(Level.FINE, "Shutting down {0}", udpBroadcastThread.getName());
            try {
                udpBroadcastThread.shutdown();
            } catch (OutOfMemoryError e) {
                // we should just propagate this, no point trying to log
                throw e;
            } catch (LinkageError e) {
                LOGGER.log(SEVERE, "Failed to shutdown UDP Broadcast Thread", e);
                // safe to ignore and continue for this one
            } catch (Throwable e) {
                LOGGER.log(SEVERE, "Failed to shutdown UDP Broadcast Thread", e);
                // save for later
                errors.add(e);
            }
        }
    }

    private void _cleanUpCloseDNSMulticast(List<Throwable> errors) {
        if(dnsMultiCast!=null) {
            LOGGER.log(Level.FINE, "Closing DNS Multicast service");
            try {
                dnsMultiCast.close();
            } catch (OutOfMemoryError e) {
                // we should just propagate this, no point trying to log
                throw e;
            } catch (LinkageError e) {
                LOGGER.log(SEVERE, "Failed to close DNS Multicast service", e);
                // safe to ignore and continue for this one
            } catch (Throwable e) {
                LOGGER.log(SEVERE, "Failed to close DNS Multicast service", e);
                // save for later
                errors.add(e);
            }
        }
    }

    private void _cleanUpInterruptReloadThread(List<Throwable> errors) {
        LOGGER.log(Level.FINE, "Interrupting reload thread");
        try {
            interruptReloadThread();
        } catch (SecurityException e) {
            LOGGER.log(WARNING, "Not permitted to interrupt reload thread", e);
            errors.add(e);
        } catch (OutOfMemoryError e) {
            // we should just propagate this, no point trying to log
            throw e;
        } catch (LinkageError e) {
            LOGGER.log(SEVERE, "Failed to interrupt reload thread", e);
            // safe to ignore and continue for this one
        } catch (Throwable e) {
            LOGGER.log(SEVERE, "Failed to interrupt reload thread", e);
            // save for later
            errors.add(e);
        }
    }

    private void _cleanUpShutdownTriggers(List<Throwable> errors) {
        LOGGER.log(Level.FINE, "Shutting down triggers");
        try {
            final java.util.Timer timer = Trigger.timer;
            if (timer != null) {
                final CountDownLatch latch = new CountDownLatch(1);
                timer.schedule(new TimerTask() {
                    @Override
                    public void run() {
                        timer.cancel();
                        latch.countDown();
                    }
                }, 0);
                if (latch.await(10, TimeUnit.SECONDS)) {
                    LOGGER.log(Level.FINE, "Triggers shut down successfully");
                } else {
                    timer.cancel();
                    LOGGER.log(Level.INFO, "Gave up waiting for triggers to finish running");
                }
            }
            Trigger.timer = null;
        } catch (OutOfMemoryError e) {
            // we should just propagate this, no point trying to log
            throw e;
        } catch (LinkageError e) {
            LOGGER.log(SEVERE, "Failed to shut down triggers", e);
            // safe to ignore and continue for this one
        } catch (Throwable e) {
            LOGGER.log(SEVERE, "Failed to shut down triggers", e);
            // save for later
            errors.add(e);
        }
    }

    private void _cleanUpShutdownTimer(List<Throwable> errors) {
        LOGGER.log(Level.FINE, "Shutting down timer");
        try {
            Timer.shutdown();
        } catch (SecurityException e) {
            LOGGER.log(WARNING, "Not permitted to shut down Timer", e);
            errors.add(e);
        } catch (OutOfMemoryError e) {
            // we should just propagate this, no point trying to log
            throw e;
        } catch (LinkageError e) {
            LOGGER.log(SEVERE, "Failed to shut down Timer", e);
            // safe to ignore and continue for this one
        } catch (Throwable e) {
            LOGGER.log(SEVERE, "Failed to shut down Timer", e);
            // save for later
            errors.add(e);
        }
    }

    private void _cleanUpShutdownTcpSlaveAgent(List<Throwable> errors) {
        if(tcpSlaveAgentListener!=null) {
            LOGGER.log(FINE, "Shutting down TCP/IP slave agent listener");
            try {
                tcpSlaveAgentListener.shutdown();
            } catch (OutOfMemoryError e) {
                // we should just propagate this, no point trying to log
                throw e;
            } catch (LinkageError e) {
                LOGGER.log(SEVERE, "Failed to shut down TCP/IP slave agent listener", e);
                // safe to ignore and continue for this one
            } catch (Throwable e) {
                LOGGER.log(SEVERE, "Failed to shut down TCP/IP slave agent listener", e);
                // save for later
                errors.add(e);
            }
        }
    }

    private void _cleanUpShutdownPluginManager(List<Throwable> errors) {
        if(pluginManager!=null) {// be defensive. there could be some ugly timing related issues
            LOGGER.log(Level.INFO, "Stopping plugin manager");
            try {
                pluginManager.stop();
            } catch (OutOfMemoryError e) {
                // we should just propagate this, no point trying to log
                throw e;
            } catch (LinkageError e) {
                LOGGER.log(SEVERE, "Failed to stop plugin manager", e);
                // safe to ignore and continue for this one
            } catch (Throwable e) {
                LOGGER.log(SEVERE, "Failed to stop plugin manager", e);
                // save for later
                errors.add(e);
            }
        }
    }

    private void _cleanUpPersistQueue(List<Throwable> errors) {
        if(getRootDir().exists()) {
            // if we are aborting because we failed to create JENKINS_HOME,
            // don't try to save. Issue #536
            LOGGER.log(Level.INFO, "Persisting build queue");
            try {
                getQueue().save();
            } catch (OutOfMemoryError e) {
                // we should just propagate this, no point trying to log
                throw e;
            } catch (LinkageError e) {
                LOGGER.log(SEVERE, "Failed to persist build queue", e);
                // safe to ignore and continue for this one
            } catch (Throwable e) {
                LOGGER.log(SEVERE, "Failed to persist build queue", e);
                // save for later
                errors.add(e);
            }
        }
    }

    private void _cleanUpShutdownThreadPoolForLoad(List<Throwable> errors) {
        LOGGER.log(FINE, "Shuting down Jenkins load thread pool");
        try {
            threadPoolForLoad.shutdown();
        } catch (SecurityException e) {
            LOGGER.log(WARNING, "Not permitted to shut down Jenkins load thread pool", e);
            errors.add(e);
        } catch (OutOfMemoryError e) {
            // we should just propagate this, no point trying to log
            throw e;
        } catch (LinkageError e) {
            LOGGER.log(SEVERE, "Failed to shut down Jenkins load thread pool", e);
            // safe to ignore and continue for this one
        } catch (Throwable e) {
            LOGGER.log(SEVERE, "Failed to shut down Jenkins load thread pool", e);
            // save for later
            errors.add(e);
        }
    }

    private void _cleanUpAwaitDisconnects(List<Throwable> errors, Set<Future<?>> pending) {
        if (!pending.isEmpty()) {
            LOGGER.log(Level.INFO, "Waiting for node disconnection completion");
        }
        for (Future<?> f : pending) {
            try {
                f.get(10, TimeUnit.SECONDS);    // if clean up operation didn't complete in time, we fail the test
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;  // someone wants us to die now. quick!
            } catch (ExecutionException e) {
                LOGGER.log(Level.WARNING, "Failed to shut down remote computer connection cleanly", e);
            } catch (TimeoutException e) {
                LOGGER.log(Level.WARNING, "Failed to shut down remote computer connection within 10 seconds", e);
            } catch (OutOfMemoryError e) {
                // we should just propagate this, no point trying to log
                throw e;
            } catch (LinkageError e) {
                LOGGER.log(Level.WARNING, "Failed to shut down remote computer connection", e);
            } catch (Throwable e) {
                LOGGER.log(Level.SEVERE, "Unexpected error while waiting for remote computer connection disconnect", e);
                errors.add(e);
            }
        }
    }

    private void _cleanUpPluginServletFilters(List<Throwable> errors) {
        LOGGER.log(Level.FINE, "Stopping filters");
        try {
            PluginServletFilter.cleanUp();
        } catch (OutOfMemoryError e) {
            // we should just propagate this, no point trying to log
            throw e;
        } catch (LinkageError e) {
            LOGGER.log(SEVERE, "Failed to stop filters", e);
            // safe to ignore and continue for this one
        } catch (Throwable e) {
            LOGGER.log(SEVERE, "Failed to stop filters", e);
            // save for later
            errors.add(e);
        }
    }

    private void _cleanUpReleaseAllLoggers(List<Throwable> errors) {
        LOGGER.log(Level.FINE, "Releasing all loggers");
        try {
            LogFactory.releaseAll();
        } catch (OutOfMemoryError e) {
            // we should just propagate this, no point trying to log
            throw e;
        } catch (LinkageError e) {
            LOGGER.log(SEVERE, "Failed to release all loggers", e);
            // safe to ignore and continue for this one
        } catch (Throwable e) {
            LOGGER.log(SEVERE, "Failed to release all loggers", e);
            // save for later
            errors.add(e);
        }
    }

    public Object getDynamic(String token) {
        for (Action a : getActions()) {
            String url = a.getUrlName();
            if (url==null)  continue;
            if (url.equals(token) || url.equals('/' + token))
                return a;
        }
        for (Action a : getManagementLinks())
            if (Objects.equals(a.getUrlName(), token))
                return a;
        return null;
    }


//
//
// actions
//
//
    /**
     * Accepts submission from the configuration page.
     */
    public synchronized void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
        BulkChange bc = new BulkChange(this);
        try {
            checkPermission(ADMINISTER);

            JSONObject json = req.getSubmittedForm();

            workspaceDir = json.getString("rawWorkspaceDir");
            buildsDir = json.getString("rawBuildsDir");

            systemMessage = Util.nullify(req.getParameter("system_message"));

            boolean result = true;
            for (Descriptor<?> d : Functions.getSortedDescriptorsForGlobalConfigUnclassified())
                result &= configureDescriptor(req,json,d);

            version = VERSION;

            save();
            updateComputerList();
            if(result)
                FormApply.success(req.getContextPath()+'/').generateResponse(req, rsp, null);
            else
                FormApply.success("configure").generateResponse(req, rsp, null);    // back to config
        } finally {
            bc.commit();
        }
    }

    /**
     * Gets the {@link CrumbIssuer} currently in use.
     *
     * @return null if none is in use.
     */
    public CrumbIssuer getCrumbIssuer() {
        return crumbIssuer;
    }

    public void setCrumbIssuer(CrumbIssuer issuer) {
        crumbIssuer = issuer;
    }

    public synchronized void doTestPost( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
        rsp.sendRedirect("foo");
    }

    private boolean configureDescriptor(StaplerRequest req, JSONObject json, Descriptor<?> d) throws FormException {
        // collapse the structure to remain backward compatible with the JSON structure before 1.
        String name = d.getJsonSafeClassName();
        JSONObject js = json.has(name) ? json.getJSONObject(name) : new JSONObject(); // if it doesn't have the property, the method returns invalid null object.
        json.putAll(js);
        return d.configure(req, js);
    }

    /**
     * Accepts submission from the node configuration page.
     */
    @RequirePOST
    public synchronized void doConfigExecutorsSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
        checkPermission(ADMINISTER);

        BulkChange bc = new BulkChange(this);
        try {
            JSONObject json = req.getSubmittedForm();

            MasterBuildConfiguration mbc = MasterBuildConfiguration.all().get(MasterBuildConfiguration.class);
            if (mbc!=null)
                mbc.configure(req,json);

            getNodeProperties().rebuild(req, json.optJSONObject("nodeProperties"), NodeProperty.all());
        } finally {
            bc.commit();
        }

        updateComputerList();

        rsp.sendRedirect(req.getContextPath()+'/'+toComputer().getUrl());  // back to the computer page
    }

    /**
     * Accepts the new description.
     */
    @RequirePOST
    public synchronized void doSubmitDescription( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
        getPrimaryView().doSubmitDescription(req, rsp);
    }

    @RequirePOST // TODO does not seem to work on _either_ overload!
    public synchronized HttpRedirect doQuietDown() throws IOException {
        try {
            return doQuietDown(false,0);
        } catch (InterruptedException e) {
            throw new AssertionError(); // impossible
        }
    }

    /**
     * Quiet down Jenkins - preparation for a restart
     *
     * @param block Block until the system really quiets down and no builds are running
     * @param timeout If non-zero, only block up to the specified number of milliseconds
     */
    @RequirePOST
    public HttpRedirect doQuietDown(@QueryParameter boolean block, @QueryParameter int timeout) throws InterruptedException, IOException {
        synchronized (this) {
            checkPermission(ADMINISTER);
            isQuietingDown = true;
        }
        if (block) {
            long waitUntil = timeout;
            if (timeout > 0) waitUntil += System.currentTimeMillis();
            while (isQuietingDown
                   && (timeout <= 0 || System.currentTimeMillis() < waitUntil)
                   && !RestartListener.isAllReady()) {
                Thread.sleep(1000);
            }
        }
        return new HttpRedirect(".");
    }

    /**
     * Cancel previous quiet down Jenkins - preparation for a restart
     */
    @RequirePOST // TODO the cancel link needs to be updated accordingly
    public synchronized HttpRedirect doCancelQuietDown() {
        checkPermission(ADMINISTER);
        isQuietingDown = false;
        getQueue().scheduleMaintenance();
        return new HttpRedirect(".");
    }

    public HttpResponse doToggleCollapse() throws ServletException, IOException {
        final StaplerRequest request = Stapler.getCurrentRequest();
        final String paneId = request.getParameter("paneId");

        PaneStatusProperties.forCurrentUser().toggleCollapsed(paneId);

        return HttpResponses.forwardToPreviousPage();
    }

    /**
     * Backward compatibility. Redirect to the thread dump.
     */
    public void doClassicThreadDump(StaplerResponse rsp) throws IOException, ServletException {
        rsp.sendRedirect2("threadDump");
    }

    /**
     * Obtains the thread dump of all agents (including the master.)
     *
     * <p>
     * Since this is for diagnostics, it has a built-in precautionary measure against hang agents.
     */
    public Map<String,Map<String,String>> getAllThreadDumps() throws IOException, InterruptedException {
        checkPermission(ADMINISTER);

        // issue the requests all at once
        Map<String,Future<Map<String,String>>> future = new HashMap<String, Future<Map<String, String>>>();

        for (Computer c : getComputers()) {
            try {
                future.put(c.getName(), RemotingDiagnostics.getThreadDumpAsync(c.getChannel()));
            } catch(Exception e) {
                LOGGER.info("Failed to get thread dump for node " + c.getName() + ": " + e.getMessage());
            }
        }
        if (toComputer() == null) {
            future.put("master", RemotingDiagnostics.getThreadDumpAsync(FilePath.localChannel));
        }

        // if the result isn't available in 5 sec, ignore that.
        // this is a precaution against hang nodes
        long endTime = System.currentTimeMillis() + 5000;

        Map<String,Map<String,String>> r = new HashMap<String, Map<String, String>>();
        for (Entry<String, Future<Map<String, String>>> e : future.entrySet()) {
            try {
                r.put(e.getKey(), e.getValue().get(endTime-System.currentTimeMillis(), TimeUnit.MILLISECONDS));
            } catch (Exception x) {
                r.put(e.getKey(), Collections.singletonMap("Failed to retrieve thread dump", Functions.printThrowable(x)));
            }
        }
        return Collections.unmodifiableSortedMap(new TreeMap<String, Map<String, String>>(r));
    }

    @RequirePOST
    public synchronized TopLevelItem doCreateItem( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
        return itemGroupMixIn.createTopLevelItem(req, rsp);
    }

    /**
     * @since 1.319
     */
    public TopLevelItem createProjectFromXML(String name, InputStream xml) throws IOException {
        return itemGroupMixIn.createProjectFromXML(name, xml);
    }


    @SuppressWarnings({"unchecked"})
    public <T extends TopLevelItem> T copy(T src, String name) throws IOException {
        return itemGroupMixIn.copy(src, name);
    }

    // a little more convenient overloading that assumes the caller gives us the right type
    // (or else it will fail with ClassCastException)
    public <T extends AbstractProject<?,?>> T copy(T src, String name) throws IOException {
        return (T)copy((TopLevelItem)src,name);
    }

    @RequirePOST
    public synchronized void doCreateView( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
        checkPermission(View.CREATE);
        addView(View.create(req,rsp, this));
    }

    /**
     * Check if the given name is suitable as a name
     * for job, view, etc.
     *
     * @throws Failure
     *      if the given name is not good
     */
    public static void checkGoodName(String name) throws Failure {
        if(name==null || name.length()==0)
            throw new Failure(Messages.Hudson_NoName());

        if(".".equals(name.trim()))
            throw new Failure(Messages.Jenkins_NotAllowedName("."));
        if("..".equals(name.trim()))
            throw new Failure(Messages.Jenkins_NotAllowedName(".."));
        for( int i=0; i<name.length(); i++ ) {
            char ch = name.charAt(i);
            if(Character.isISOControl(ch)) {
                throw new Failure(Messages.Hudson_ControlCodeNotAllowed(toPrintableName(name)));
            }
            if("?*/\\%!@#$^&|<>[]:;".indexOf(ch)!=-1)
                throw new Failure(Messages.Hudson_UnsafeChar(ch));
        }

        // looks good
    }

    private static String toPrintableName(String name) {
        StringBuilder printableName = new StringBuilder();
        for( int i=0; i<name.length(); i++ ) {
            char ch = name.charAt(i);
            if(Character.isISOControl(ch))
                printableName.append("\\u").append((int)ch).append(';');
            else
                printableName.append(ch);
        }
        return printableName.toString();
    }

    /**
     * Checks if the user was successfully authenticated.
     *
     * @see BasicAuthenticationFilter
     */
    public void doSecured( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
        // TODO fire something in SecurityListener? (seems to be used only for REST calls when LegacySecurityRealm is active)

        if(req.getUserPrincipal()==null) {
            // authentication must have failed
            rsp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return;
        }

        // the user is now authenticated, so send him back to the target
        String path = req.getContextPath()+req.getOriginalRestOfPath();
        String q = req.getQueryString();
        if(q!=null)
            path += '?'+q;

        rsp.sendRedirect2(path);
    }

    /**
     * Called once the user logs in. Just forward to the top page.
     * Used only by {@link LegacySecurityRealm}.
     */
    public void doLoginEntry( StaplerRequest req, StaplerResponse rsp ) throws IOException {
        if(req.getUserPrincipal()==null) {
            rsp.sendRedirect2("noPrincipal");
            return;
        }

        // TODO fire something in SecurityListener?

        String from = req.getParameter("from");
        if(from!=null && from.startsWith("/") && !from.equals("/loginError")) {
            rsp.sendRedirect2(from);    // I'm bit uncomfortable letting users redirected to other sites, make sure the URL falls into this domain
            return;
        }

        String url = AbstractProcessingFilter.obtainFullRequestUrl(req);
        if(url!=null) {
            // if the login redirect is initiated by Acegi
            // this should send the user back to where s/he was from.
            rsp.sendRedirect2(url);
            return;
        }

        rsp.sendRedirect2(".");
    }

    /**
     * Logs out the user.
     */
    public void doLogout( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
        String user = getAuthentication().getName();
        securityRealm.doLogout(req, rsp);
        SecurityListener.fireLoggedOut(user);
    }

    /**
     * Serves jar files for JNLP agents.
     */
    public Slave.JnlpJar getJnlpJars(String fileName) {
        return new Slave.JnlpJar(fileName);
    }

    public Slave.JnlpJar doJnlpJars(StaplerRequest req) {
        return new Slave.JnlpJar(req.getRestOfPath().substring(1));
    }

    /**
     * Reloads the configuration.
     */
    @RequirePOST
    public synchronized HttpResponse doReload() throws IOException {
        checkPermission(ADMINISTER);
        LOGGER.log(Level.WARNING, "Reloading Jenkins as requested by {0}", getAuthentication().getName());

        // engage "loading ..." UI and then run the actual task in a separate thread
        servletContext.setAttribute("app", new HudsonIsLoading());

        new Thread("Jenkins config reload thread") {
            @Override
            public void run() {
                try {
                    ACL.impersonate(ACL.SYSTEM);
                    reload();
                } catch (Exception e) {
                    LOGGER.log(SEVERE,"Failed to reload Jenkins config",e);
                    new JenkinsReloadFailed(e).publish(servletContext,root);
                }
            }
        }.start();

        return HttpResponses.redirectViaContextPath("/");
    }

    /**
     * Reloads the configuration synchronously.
     * Beware that this calls neither {@link ItemListener#onLoaded} nor {@link Initializer}s.
     */
    public void reload() throws IOException, InterruptedException, ReactorException {
        queue.save();
        executeReactor(null, loadTasks());

        // Ensure we reached the final initialization state. Log the error otherwise
        if (initLevel != InitMilestone.COMPLETED) {
            LOGGER.log(SEVERE, "Jenkins initialization has not reached the COMPLETED initialization milestone after the configuration reload. " +
                            "Current state: {0}. " +
                            "It may cause undefined incorrect behavior in Jenkins plugin relying on this state. " +
                            "It is likely an issue with the Initialization task graph. " +
                            "Example: usage of @Initializer(after = InitMilestone.COMPLETED) in a plugin (JENKINS-37759). " +
                            "Please create a bug in Jenkins bugtracker.",
                    initLevel);
        }

        User.reload();
        queue.load();
        servletContext.setAttribute("app", this);
    }

    /**
     * Do a finger-print check.
     */
    public void doDoFingerprintCheck( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
        // Parse the request
        try (MultipartFormDataParser p = new MultipartFormDataParser(req)) {
            if (isUseCrumbs() && !getCrumbIssuer().validateCrumb(req, p)) {
                rsp.sendError(HttpServletResponse.SC_FORBIDDEN, "No crumb found");
            }
            rsp.sendRedirect2(req.getContextPath()+"/fingerprint/"+
                Util.getDigestOf(p.getFileItem("name").getInputStream())+'/');
        }
    }

    /**
     * For debugging. Expose URL to perform GC.
     */
    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings("DM_GC")
    @RequirePOST
    public void doGc(StaplerResponse rsp) throws IOException {
        checkPermission(Jenkins.ADMINISTER);
        System.gc();
        rsp.setStatus(HttpServletResponse.SC_OK);
        rsp.setContentType("text/plain");
        rsp.getWriter().println("GCed");
    }

    /**
     * End point that intentionally throws an exception to test the error behaviour.
     * @since 1.467
     */
    public void doException() {
        throw new RuntimeException();
    }

    public ContextMenu doContextMenu(StaplerRequest request, StaplerResponse response) throws IOException, JellyException {
        ContextMenu menu = new ContextMenu().from(this, request, response);
        for (MenuItem i : menu.items) {
            if (i.url.equals(request.getContextPath() + "/manage")) {
                // add "Manage Jenkins" subitems
                i.subMenu = new ContextMenu().from(this, request, response, "manage");
            }
        }
        return menu;
    }

    public ContextMenu doChildrenContextMenu(StaplerRequest request, StaplerResponse response) throws Exception {
        ContextMenu menu = new ContextMenu();
        for (View view : getViews()) {
            menu.add(view.getViewUrl(),view.getDisplayName());
        }
        return menu;
    }

    /**
     * Obtains the heap dump.
     */
    public HeapDump getHeapDump() throws IOException {
        return new HeapDump(this,FilePath.localChannel);
    }

    /**
     * Simulates OutOfMemoryError.
     * Useful to make sure OutOfMemoryHeapDump setting.
     */
    @RequirePOST
    public void doSimulateOutOfMemory() throws IOException {
        checkPermission(ADMINISTER);

        System.out.println("Creating artificial OutOfMemoryError situation");
        List<Object> args = new ArrayList<Object>();
        while (true)
            args.add(new byte[1024*1024]);
    }

    /**
     * Binds /userContent/... to $JENKINS_HOME/userContent.
     */
    public DirectoryBrowserSupport doUserContent() {
        return new DirectoryBrowserSupport(this,getRootPath().child("userContent"),"User content","folder.png",true);
    }

    /**
     * Perform a restart of Jenkins, if we can.
     *
     * This first replaces "app" to {@link HudsonIsRestarting}
     */
    @CLIMethod(name="restart")
    public void doRestart(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, RestartNotSupportedException {
        checkPermission(ADMINISTER);
        if (req != null && req.getMethod().equals("GET")) {
            req.getView(this,"_restart.jelly").forward(req,rsp);
            return;
        }

        restart();

        if (rsp != null) // null for CLI
            rsp.sendRedirect2(".");
    }

    /**
     * Queues up a restart of Jenkins for when there are no builds running, if we can.
     *
     * This first replaces "app" to {@link HudsonIsRestarting}
     *
     * @since 1.332
     */
    @CLIMethod(name="safe-restart")
    public HttpResponse doSafeRestart(StaplerRequest req) throws IOException, ServletException, RestartNotSupportedException {
        checkPermission(ADMINISTER);
        if (req != null && req.getMethod().equals("GET"))
            return HttpResponses.forwardToView(this,"_safeRestart.jelly");

        safeRestart();

        return HttpResponses.redirectToDot();
    }

    /**
     * Performs a restart.
     */
    public void restart() throws RestartNotSupportedException {
        final Lifecycle lifecycle = Lifecycle.get();
        lifecycle.verifyRestartable(); // verify that Jenkins is restartable
        servletContext.setAttribute("app", new HudsonIsRestarting());

        new Thread("restart thread") {
            final String exitUser = getAuthentication().getName();
            @Override
            public void run() {
                try {
                    ACL.impersonate(ACL.SYSTEM);

                    // give some time for the browser to load the "reloading" page
                    Thread.sleep(5000);
                    LOGGER.severe(String.format("Restarting VM as requested by %s",exitUser));
                    for (RestartListener listener : RestartListener.all())
                        listener.onRestart();
                    lifecycle.restart();
                } catch (InterruptedException | IOException e) {
                    LOGGER.log(Level.WARNING, "Failed to restart Jenkins",e);
                }
            }
        }.start();
    }

    /**
     * Queues up a restart to be performed once there are no builds currently running.
     * @since 1.332
     */
    public void safeRestart() throws RestartNotSupportedException {
        final Lifecycle lifecycle = Lifecycle.get();
        lifecycle.verifyRestartable(); // verify that Jenkins is restartable
        // Quiet down so that we won't launch new builds.
        isQuietingDown = true;

        new Thread("safe-restart thread") {
            final String exitUser = getAuthentication().getName();
            @Override
            public void run() {
                try {
                    ACL.impersonate(ACL.SYSTEM);

                    // Wait 'til we have no active executors.
                    doQuietDown(true, 0);

                    // Make sure isQuietingDown is still true.
                    if (isQuietingDown) {
                        servletContext.setAttribute("app",new HudsonIsRestarting());
                        // give some time for the browser to load the "reloading" page
                        LOGGER.info("Restart in 10 seconds");
                        Thread.sleep(10000);
                        LOGGER.severe(String.format("Restarting VM as requested by %s",exitUser));
                        for (RestartListener listener : RestartListener.all())
                            listener.onRestart();
                        lifecycle.restart();
                    } else {
                        LOGGER.info("Safe-restart mode cancelled");
                    }
                } catch (Throwable e) {
                    LOGGER.log(Level.WARNING, "Failed to restart Jenkins",e);
                }
            }
        }.start();
    }

    @Extension @Restricted(NoExternalUse.class)
    public static class MasterRestartNotifyier extends RestartListener {

        @Override
        public void onRestart() {
            Computer computer = Jenkins.getInstance().toComputer();
            if (computer == null) return;
            RestartCause cause = new RestartCause();
            for (ComputerListener listener: ComputerListener.all()) {
                listener.onOffline(computer, cause);
            }
        }

        @Override
        public boolean isReadyToRestart() throws IOException, InterruptedException {
            return true;
        }

        private static class RestartCause extends OfflineCause.SimpleOfflineCause {
            protected RestartCause() {
                super(Messages._Jenkins_IsRestarting());
            }
        }
    }

    /**
     * Shutdown the system.
     * @since 1.161
     */
    @CLIMethod(name="shutdown")
    @RequirePOST
    public void doExit( StaplerRequest req, StaplerResponse rsp ) throws IOException {
        checkPermission(ADMINISTER);
        LOGGER.severe(String.format("Shutting down VM as requested by %s from %s",
                getAuthentication().getName(), req!=null?req.getRemoteAddr():"???"));
        if (rsp!=null) {
            rsp.setStatus(HttpServletResponse.SC_OK);
            rsp.setContentType("text/plain");
            try (PrintWriter w = rsp.getWriter()) {
                w.println("Shutting down");
            }
        }

        System.exit(0);
    }


    /**
     * Shutdown the system safely.
     * @since 1.332
     */
    @CLIMethod(name="safe-shutdown")
    @RequirePOST
    public HttpResponse doSafeExit(StaplerRequest req) throws IOException {
        checkPermission(ADMINISTER);
        isQuietingDown = true;
        final String exitUser = getAuthentication().getName();
        final String exitAddr = req!=null ? req.getRemoteAddr() : "unknown";
        new Thread("safe-exit thread") {
            @Override
            public void run() {
                try {
                    ACL.impersonate(ACL.SYSTEM);
                    LOGGER.severe(String.format("Shutting down VM as requested by %s from %s",
                                                exitUser, exitAddr));
                    // Wait 'til we have no active executors.
                    doQuietDown(true, 0);
                    // Make sure isQuietingDown is still true.
                    if (isQuietingDown) {
                        cleanUp();
                        System.exit(0);
                    }
                } catch (Exception e) {
                    LOGGER.log(Level.WARNING, "Failed to shut down Jenkins", e);
                }
            }
        }.start();

        return HttpResponses.plainText("Shutting down as soon as all jobs are complete");
    }

    /**
     * Gets the {@link Authentication} object that represents the user
     * associated with the current request.
     */
    public static @Nonnull Authentication getAuthentication() {
        Authentication a = SecurityContextHolder.getContext().getAuthentication();
        // on Tomcat while serving the login page, this is null despite the fact
        // that we have filters. Looking at the stack trace, Tomcat doesn't seem to
        // run the request through filters when this is the login request.
        // see http://www.nabble.com/Matrix-authorization-problem-tp14602081p14886312.html
        if(a==null)
            a = ANONYMOUS;
        return a;
    }

    /**
     * For system diagnostics.
     * Run arbitrary Groovy script.
     */
    public void doScript(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
        _doScript(req, rsp, req.getView(this, "_script.jelly"), FilePath.localChannel, getACL());
    }

    /**
     * Run arbitrary Groovy script and return result as plain text.
     */
    public void doScriptText(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
        _doScript(req, rsp, req.getView(this, "_scriptText.jelly"), FilePath.localChannel, getACL());
    }

    /**
     * @since 1.509.1
     */
    public static void _doScript(StaplerRequest req, StaplerResponse rsp, RequestDispatcher view, VirtualChannel channel, ACL acl) throws IOException, ServletException {
        // ability to run arbitrary script is dangerous
        acl.checkPermission(RUN_SCRIPTS);

        String text = req.getParameter("script");
        if (text != null) {
            if (!"POST".equals(req.getMethod())) {
                throw HttpResponses.error(HttpURLConnection.HTTP_BAD_METHOD, "requires POST");
            }

            if (channel == null) {
                throw HttpResponses.error(HttpURLConnection.HTTP_NOT_FOUND, "Node is offline");
            }

            try {
                req.setAttribute("output",
                        RemotingDiagnostics.executeGroovy(text, channel));
            } catch (InterruptedException e) {
                throw new ServletException(e);
            }
        }

        view.forward(req, rsp);
    }

    /**
     * Evaluates the Jelly script submitted by the client.
     *
     * This is useful for system administration as well as unit testing.
     */
    @RequirePOST
    public void doEval(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
        checkPermission(RUN_SCRIPTS);

        try {
            MetaClass mc = WebApp.getCurrent().getMetaClass(getClass());
            Script script = mc.classLoader.loadTearOff(JellyClassLoaderTearOff.class).createContext().compileScript(new InputSource(req.getReader()));
            new JellyRequestDispatcher(this,script).forward(req,rsp);
        } catch (JellyException e) {
            throw new ServletException(e);
        }
    }

    /**
     * Sign up for the user account.
     */
    public void doSignup( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
        if (getSecurityRealm().allowsSignup()) {
            req.getView(getSecurityRealm(), "signup.jelly").forward(req, rsp);
            return;
        }
        req.getView(SecurityRealm.class, "signup.jelly").forward(req, rsp);
    }

    /**
     * Changes the icon size by changing the cookie
     */
    public void doIconSize( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
        String qs = req.getQueryString();
        if(qs==null)
            throw new ServletException();
        Cookie cookie = new Cookie("iconSize", Functions.validateIconSize(qs));
        cookie.setMaxAge(/* ~4 mo. */9999999); // #762
        rsp.addCookie(cookie);
        String ref = req.getHeader("Referer");
        if(ref==null)   ref=".";
        rsp.sendRedirect2(ref);
    }

    @RequirePOST
    public void doFingerprintCleanup(StaplerResponse rsp) throws IOException {
        checkPermission(ADMINISTER);
        FingerprintCleanupThread.invoke();
        rsp.setStatus(HttpServletResponse.SC_OK);
        rsp.setContentType("text/plain");
        rsp.getWriter().println("Invoked");
    }

    @RequirePOST
    public void doWorkspaceCleanup(StaplerResponse rsp) throws IOException {
        checkPermission(ADMINISTER);
        WorkspaceCleanupThread.invoke();
        rsp.setStatus(HttpServletResponse.SC_OK);
        rsp.setContentType("text/plain");
        rsp.getWriter().println("Invoked");
    }

    /**
     * If the user chose the default JDK, make sure we got 'java' in PATH.
     */
    public FormValidation doDefaultJDKCheck(StaplerRequest request, @QueryParameter String value) {
        if(!JDK.isDefaultName(value))
            // assume the user configured named ones properly in system config ---
            // or else system config should have reported form field validation errors.
            return FormValidation.ok();

        // default JDK selected. Does such java really exist?
        if(JDK.isDefaultJDKValid(Jenkins.this))
            return FormValidation.ok();
        else
            return FormValidation.errorWithMarkup(Messages.Hudson_NoJavaInPath(request.getContextPath()));
    }

    /**
     * Checks if a top-level view with the given name exists and
     * make sure that the name is good as a view name.
     */
    public FormValidation doCheckViewName(@QueryParameter String value) {
        checkPermission(View.CREATE);

        String name = fixEmpty(value);
        if (name == null)
            return FormValidation.ok();

        // already exists?
        if (getView(name) != null)
            return FormValidation.error(Messages.Hudson_ViewAlreadyExists(name));

        // good view name?
        try {
            checkGoodName(name);
        } catch (Failure e) {
            return FormValidation.error(e.getMessage());
        }

        return FormValidation.ok();
    }

    /**
     * Checks if a top-level view with the given name exists.
     * @deprecated 1.512
     */
    @Deprecated
    public FormValidation doViewExistsCheck(@QueryParameter String value) {
        checkPermission(View.CREATE);

        String view = fixEmpty(value);
        if(view==null) return FormValidation.ok();

        if(getView(view)==null)
            return FormValidation.ok();
        else
            return FormValidation.error(Messages.Hudson_ViewAlreadyExists(view));
    }

    /**
     * Serves static resources placed along with Jelly view files.
     * <p>
     * This method can serve a lot of files, so care needs to be taken
     * to make this method secure. It's not clear to me what's the best
     * strategy here, though the current implementation is based on
     * file extensions.
     */
    public void doResources(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
        String path = req.getRestOfPath();
        // cut off the "..." portion of /resources/.../path/to/file
        // as this is only used to make path unique (which in turn
        // allows us to set a long expiration date
        path = path.substring(path.indexOf('/',1)+1);

        int idx = path.lastIndexOf('.');
        String extension = path.substring(idx+1);
        if(ALLOWED_RESOURCE_EXTENSIONS.contains(extension)) {
            URL url = pluginManager.uberClassLoader.getResource(path);
            if(url!=null) {
                long expires = MetaClass.NO_CACHE ? 0 : 365L * 24 * 60 * 60 * 1000; /*1 year*/
                rsp.serveFile(req,url,expires);
                return;
            }
        }
        rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
    }

    /**
     * Extension list that {@link #doResources(StaplerRequest, StaplerResponse)} can serve.
     * This set is mutable to allow plugins to add additional extensions.
     */
    public static final Set<String> ALLOWED_RESOURCE_EXTENSIONS = new HashSet<String>(Arrays.asList(
        "js|css|jpeg|jpg|png|gif|html|htm".split("\\|")
    ));

    /**
     * Checks if container uses UTF-8 to decode URLs. See
     * http://wiki.jenkins-ci.org/display/JENKINS/Tomcat#Tomcat-i18n
     */
    @Restricted(NoExternalUse.class)
    @RestrictedSince("2.37")
    @Deprecated
    public FormValidation doCheckURIEncoding(StaplerRequest request) throws IOException {
        return ExtensionList.lookup(URICheckEncodingMonitor.class).get(0).doCheckURIEncoding(request);
    }

    /**
     * Does not check when system default encoding is "ISO-8859-1".
     */
    @Restricted(NoExternalUse.class)
    @RestrictedSince("2.37")
    @Deprecated
    public static boolean isCheckURIEncodingEnabled() {
        return ExtensionList.lookup(URICheckEncodingMonitor.class).get(0).isCheckEnabled();
    }

    /**
     * Rebuilds the dependency map.
     */
    public void rebuildDependencyGraph() {
        DependencyGraph graph = new DependencyGraph();
        graph.build();
        // volatile acts a as a memory barrier here and therefore guarantees
        // that graph is fully build, before it's visible to other threads
        dependencyGraph = graph;
        dependencyGraphDirty.set(false);
    }

    /**
     * Rebuilds the dependency map asynchronously.
     *
     * <p>
     * This would keep the UI thread more responsive and helps avoid the deadlocks,
     * as dependency graph recomputation tends to touch a lot of other things.
     *
     * @since 1.522
     */
    public Future<DependencyGraph> rebuildDependencyGraphAsync() {
        dependencyGraphDirty.set(true);
        return Timer.get().schedule(new java.util.concurrent.Callable<DependencyGraph>() {
            @Override
            public DependencyGraph call() throws Exception {
                if (dependencyGraphDirty.get()) {
                    rebuildDependencyGraph();
                }
                return dependencyGraph;
            }
        }, 500, TimeUnit.MILLISECONDS);
    }

    public DependencyGraph getDependencyGraph() {
        return dependencyGraph;
    }

    // for Jelly
    public List<ManagementLink> getManagementLinks() {
        return ManagementLink.all();
    }
    
    /**
     * If set, a currently active setup wizard - e.g. installation
     *
     * @since 2.0
     */
    @Restricted(NoExternalUse.class)
    public SetupWizard getSetupWizard() {
        return setupWizard;
    }
    
    /**
     * Exposes the current user to <tt>/me</tt> URL.
     */
    public User getMe() {
        User u = User.current();
        if (u == null)
            throw new AccessDeniedException("/me is not available when not logged in");
        return u;
    }

    /**
     * Gets the {@link Widget}s registered on this object.
     *
     * <p>
     * Plugins who wish to contribute boxes on the side panel can add widgets
     * by {@code getWidgets().add(new MyWidget())} from {@link Plugin#start()}.
     */
    public List<Widget> getWidgets() {
        return widgets;
    }

    public Object getTarget() {
        try {
            checkPermission(READ);
        } catch (AccessDeniedException e) {
            if (!isSubjectToMandatoryReadPermissionCheck(Stapler.getCurrentRequest().getRestOfPath())) {
                return this;
            }

            throw e;
        }
        return this;
    }
    
    /**
     * Test a path to see if it is subject to mandatory read permission checks by container-managed security
     * @param restOfPath the URI, excluding the Jenkins root URI and query string
     * @return true if the path is subject to mandatory read permission checks
     * @since 2.37
     */
    public boolean isSubjectToMandatoryReadPermissionCheck(String restOfPath) {
        for (String name : ALWAYS_READABLE_PATHS) {
            if (restOfPath.startsWith(name)) {
                return false;
            }
        }

        for (String name : getUnprotectedRootActions()) {
            if (restOfPath.startsWith("/" + name + "/") || restOfPath.equals("/" + name)) {
                return false;
            }
        }

        // TODO SlaveComputer.doSlaveAgentJnlp; there should be an annotation to request unprotected access
        if (restOfPath.matches("/computer/[^/]+/slave-agent[.]jnlp")
            && "true".equals(Stapler.getCurrentRequest().getParameter("encrypt"))) {
            return false;
        }

        return true;
    }

    /**
     * Gets a list of unprotected root actions.
     * These URL prefixes should be exempted from access control checks by container-managed security.
     * Ideally would be synchronized with {@link #getTarget}.
     * @return a list of {@linkplain Action#getUrlName URL names}
     * @since 1.495
     */
    public Collection<String> getUnprotectedRootActions() {
        Set<String> names = new TreeSet<String>();
        names.add("jnlpJars"); // TODO cleaner to refactor doJnlpJars into a URA
        // TODO consider caching (expiring cache when actions changes)
        for (Action a : getActions()) {
            if (a instanceof UnprotectedRootAction) {
                String url = a.getUrlName();
                if (url == null) continue;
                names.add(url);
            }
        }
        return names;
    }

    /**
     * Fallback to the primary view.
     */
    public View getStaplerFallback() {
        return getPrimaryView();
    }

    /**
     * This method checks all existing jobs to see if displayName is
     * unique. It does not check the displayName against the displayName of the
     * job that the user is configuring though to prevent a validation warning
     * if the user sets the displayName to what it currently is.
     * @param displayName
     * @param currentJobName
     */
    boolean isDisplayNameUnique(String displayName, String currentJobName) {
        Collection<TopLevelItem> itemCollection = items.values();

        // if there are a lot of projects, we'll have to store their
        // display names in a HashSet or something for a quick check
        for(TopLevelItem item : itemCollection) {
            if(item.getName().equals(currentJobName)) {
                // we won't compare the candidate displayName against the current
                // item. This is to prevent an validation warning if the user
                // sets the displayName to what the existing display name is
                continue;
            }
            else if(displayName.equals(item.getDisplayName())) {
                return false;
            }
        }

        return true;
    }

    /**
     * True if there is no item in Jenkins that has this name
     * @param name The name to test
     * @param currentJobName The name of the job that the user is configuring
     */
    boolean isNameUnique(String name, String currentJobName) {
        Item item = getItem(name);

        if(null==item) {
            // the candidate name didn't return any items so the name is unique
            return true;
        }
        else if(item.getName().equals(currentJobName)) {
            // the candidate name returned an item, but the item is the item
            // that the user is configuring so this is ok
            return true;
        }
        else {
            // the candidate name returned an item, so it is not unique
            return false;
        }
    }

    /**
     * Checks to see if the candidate displayName collides with any
     * existing display names or project names
     * @param displayName The display name to test
     * @param jobName The name of the job the user is configuring
     */
    public FormValidation doCheckDisplayName(@QueryParameter String displayName,
            @QueryParameter String jobName) {
        displayName = displayName.trim();

        if(LOGGER.isLoggable(Level.FINE)) {
            LOGGER.log(Level.FINE, "Current job name is " + jobName);
        }

        if(!isNameUnique(displayName, jobName)) {
            return FormValidation.warning(Messages.Jenkins_CheckDisplayName_NameNotUniqueWarning(displayName));
        }
        else if(!isDisplayNameUnique(displayName, jobName)){
            return FormValidation.warning(Messages.Jenkins_CheckDisplayName_DisplayNameNotUniqueWarning(displayName));
        }
        else {
            return FormValidation.ok();
        }
    }

    public static class MasterComputer extends Computer {
        protected MasterComputer() {
            super(Jenkins.getInstance());
        }

        /**
         * Returns "" to match with {@link Jenkins#getNodeName()}.
         */
        @Override
        public String getName() {
            return "";
        }

        @Override
        public boolean isConnecting() {
            return false;
        }

        @Override
        public String getDisplayName() {
            return Messages.Hudson_Computer_DisplayName();
        }

        @Override
        public String getCaption() {
            return Messages.Hudson_Computer_Caption();
        }

        @Override
        public String getUrl() {
            return "computer/(master)/";
        }

        public RetentionStrategy getRetentionStrategy() {
            return RetentionStrategy.NOOP;
        }

        /**
         * Will always keep this guy alive so that it can function as a fallback to
         * execute {@link FlyweightTask}s. See JENKINS-7291.
         */
        @Override
        protected boolean isAlive() {
            return true;
        }

        @Override
        public Boolean isUnix() {
            return !Functions.isWindows();
        }

        /**
         * Report an error.
         */
        @Override
        public HttpResponse doDoDelete() throws IOException {
            throw HttpResponses.status(SC_BAD_REQUEST);
        }

        @Override
        public void doConfigSubmit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException {
            Jenkins.getInstance().doConfigExecutorsSubmit(req, rsp);
        }

        @WebMethod(name="config.xml")
        @Override
        public void doConfigDotXml(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
            throw HttpResponses.status(SC_BAD_REQUEST);
        }

        @Override
        public boolean hasPermission(Permission permission) {
            // no one should be allowed to delete the master.
            // this hides the "delete" link from the /computer/(master) page.
            if(permission==Computer.DELETE)
                return false;
            // Configuration of master node requires ADMINISTER permission
            return super.hasPermission(permission==Computer.CONFIGURE ? Jenkins.ADMINISTER : permission);
        }

        @Override
        public VirtualChannel getChannel() {
            return FilePath.localChannel;
        }

        @Override
        public Charset getDefaultCharset() {
            return Charset.defaultCharset();
        }

        public List<LogRecord> getLogRecords() throws IOException, InterruptedException {
            return logRecords;
        }

        @RequirePOST
        public void doLaunchSlaveAgent(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
            // this computer never returns null from channel, so
            // this method shall never be invoked.
            rsp.sendError(SC_NOT_FOUND);
        }

        protected Future<?> _connect(boolean forceReconnect) {
            return Futures.precomputed(null);
        }

        /**
         * {@link LocalChannel} instance that can be used to execute programs locally.
         *
         * @deprecated as of 1.558
         *      Use {@link FilePath#localChannel}
         */
        @Deprecated
        public static final LocalChannel localChannel = FilePath.localChannel;
    }

    /**
     * Shortcut for {@code Jenkins.getInstanceOrNull()?.lookup.get(type)}
     */
    public static @CheckForNull <T> T lookup(Class<T> type) {
        Jenkins j = Jenkins.getInstanceOrNull();
        return j != null ? j.lookup.get(type) : null;
    }

    /**
     * Live view of recent {@link LogRecord}s produced by Jenkins.
     */
    public static List<LogRecord> logRecords = Collections.emptyList(); // initialized to dummy value to avoid NPE

    /**
     * Thread-safe reusable {@link XStream}.
     */
    public static final XStream XSTREAM;

    /**
     * Alias to {@link #XSTREAM} so that one can access additional methods on {@link XStream2} more easily.
     */
    public static final XStream2 XSTREAM2;

    private static final int TWICE_CPU_NUM = Math.max(4, Runtime.getRuntime().availableProcessors() * 2);

    /**
     * Thread pool used to load configuration in parallel, to improve the start up time.
     * <p>
     * The idea here is to overlap the CPU and I/O, so we want more threads than CPU numbers.
     */
    /*package*/ transient final ExecutorService threadPoolForLoad = new ThreadPoolExecutor(
        TWICE_CPU_NUM, TWICE_CPU_NUM,
        5L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new NamingThreadFactory(new DaemonThreadFactory(), "Jenkins load"));


    private static void computeVersion(ServletContext context) {
        // set the version
        Properties props = new Properties();
        try (InputStream is = Jenkins.class.getResourceAsStream("jenkins-version.properties")) {
            if(is!=null)
                props.load(is);
        } catch (IOException e) {
            e.printStackTrace(); // if the version properties is missing, that's OK.
        }
        String ver = props.getProperty("version");
        if(ver==null)   ver = UNCOMPUTED_VERSION;
        if(Main.isDevelopmentMode && "${build.version}".equals(ver)) {
            // in dev mode, unable to get version (ahem Eclipse)
            try {
                File dir = new File(".").getAbsoluteFile();
                while(dir != null) {
                    File pom = new File(dir, "pom.xml");
                    if (pom.exists() && "pom".equals(XMLUtils.getValue("/project/artifactId", pom))) {
                        pom =  pom.getCanonicalFile();
                        LOGGER.info("Reading version from: " + pom.getAbsolutePath());
                        ver = XMLUtils.getValue("/project/version", pom);
                        break;
                    }
                    dir = dir.getParentFile();
                }
                LOGGER.info("Jenkins is in dev mode, using version: " + ver);
            } catch (Exception e) {
                LOGGER.log(Level.WARNING, "Unable to read Jenkins version: " + e.getMessage(), e);
            }
        }
        
        VERSION = ver;
        context.setAttribute("version",ver);

        VERSION_HASH = Util.getDigestOf(ver).substring(0, 8);
        SESSION_HASH = Util.getDigestOf(ver+System.currentTimeMillis()).substring(0, 8);

        if(ver.equals(UNCOMPUTED_VERSION) || SystemProperties.getBoolean("hudson.script.noCache"))
            RESOURCE_PATH = "";
        else
            RESOURCE_PATH = "/static/"+SESSION_HASH;

        VIEW_RESOURCE_PATH = "/resources/"+ SESSION_HASH;
    }

    /**
     * The version number before it is "computed" (by a call to computeVersion()).
     * @since 2.0
     */
    @Restricted(NoExternalUse.class)
    public static final String UNCOMPUTED_VERSION = "?";

    /**
     * Version number of this Jenkins.
     */
    public static String VERSION = UNCOMPUTED_VERSION;

    /**
     * Parses {@link #VERSION} into {@link VersionNumber}, or null if it's not parseable as a version number
     * (such as when Jenkins is run with "mvn hudson-dev:run")
     */
    public @CheckForNull static VersionNumber getVersion() {
        return toVersion(VERSION);
    }

    /**
     * Get the stored version of Jenkins, as stored by
     * {@link #doConfigSubmit(org.kohsuke.stapler.StaplerRequest, org.kohsuke.stapler.StaplerResponse)}.
     * <p>
     * Parses the version into {@link VersionNumber}, or null if it's not parseable as a version number
     * (such as when Jenkins is run with "mvn hudson-dev:run")
     * @since 2.0
     */
    @Restricted(NoExternalUse.class)
    public @CheckForNull static VersionNumber getStoredVersion() {
        return toVersion(Jenkins.getActiveInstance().version);
    }

    /**
     * Parses a version string into {@link VersionNumber}, or null if it's not parseable as a version number
     * (such as when Jenkins is run with "mvn hudson-dev:run")
     */
    private static @CheckForNull VersionNumber toVersion(@CheckForNull String versionString) {
        if (versionString == null) {
            return null;
        }

        try {
            return new VersionNumber(versionString);
        } catch (NumberFormatException e) {
            try {
                // for non-released version of Jenkins, this looks like "1.345 (private-foobar), so try to approximate.
                int idx = versionString.indexOf(' ');
                if (idx > 0) {
                    return new VersionNumber(versionString.substring(0,idx));
                }
            } catch (NumberFormatException _) {
                // fall through
            }

            // totally unparseable
            return null;
        } catch (IllegalArgumentException e) {
            // totally unparseable
            return null;
        }
    }

    /**
     * Hash of {@link #VERSION}.
     */
    public static String VERSION_HASH;

    /**
     * Unique random token that identifies the current session.
     * Used to make {@link #RESOURCE_PATH} unique so that we can set long "Expires" header.
     *
     * We used to use {@link #VERSION_HASH}, but making this session local allows us to
     * reuse the same {@link #RESOURCE_PATH} for static resources in plugins.
     */
    public static String SESSION_HASH;

    /**
     * Prefix to static resources like images and javascripts in the war file.
     * Either "" or strings like "/static/VERSION", which avoids Jenkins to pick up
     * stale cache when the user upgrades to a different version.
     * <p>
     * Value computed in {@link WebAppMain}.
     */
    public static String RESOURCE_PATH = "";

    /**
     * Prefix to resources alongside view scripts.
     * Strings like "/resources/VERSION", which avoids Jenkins to pick up
     * stale cache when the user upgrades to a different version.
     * <p>
     * Value computed in {@link WebAppMain}.
     */
    public static String VIEW_RESOURCE_PATH = "/resources/TBD";

    public static boolean PARALLEL_LOAD = Configuration.getBooleanConfigParameter("parallelLoad", true);
    public static boolean KILL_AFTER_LOAD = Configuration.getBooleanConfigParameter("killAfterLoad", false);
    /**
     * @deprecated No longer used.
     */
    @Deprecated
    public static boolean FLYWEIGHT_SUPPORT = true;

    /**
     * Tentative switch to activate the concurrent build behavior.
     * When we merge this back to the trunk, this allows us to keep
     * this feature hidden for a while until we iron out the kinks.
     * @see AbstractProject#isConcurrentBuild()
     * @deprecated as of 1.464
     *      This flag will have no effect.
     */
    @Restricted(NoExternalUse.class)
    @Deprecated
    public static boolean CONCURRENT_BUILD = true;

    /**
     * Switch to enable people to use a shorter workspace name.
     */
    private static final String WORKSPACE_DIRNAME = Configuration.getStringConfigParameter("workspaceDirName", "workspace");

    /**
     * Automatically try to launch an agent when Jenkins is initialized or a new agent computer is created.
     */
    public static boolean AUTOMATIC_SLAVE_LAUNCH = true;

    private static final Logger LOGGER = Logger.getLogger(Jenkins.class.getName());

    public static final PermissionGroup PERMISSIONS = Permission.HUDSON_PERMISSIONS;
    public static final Permission ADMINISTER = Permission.HUDSON_ADMINISTER;
    public static final Permission READ = new Permission(PERMISSIONS,"Read",Messages._Hudson_ReadPermission_Description(),Permission.READ,PermissionScope.JENKINS);
    public static final Permission RUN_SCRIPTS = new Permission(PERMISSIONS, "RunScripts", Messages._Hudson_RunScriptsPermission_Description(),ADMINISTER,PermissionScope.JENKINS);

    /**
     * Urls that are always visible without READ permission.
     *
     * <p>See also:{@link #getUnprotectedRootActions}.
     */
    private static final ImmutableSet<String> ALWAYS_READABLE_PATHS = ImmutableSet.of(
        "/login",
        "/logout",
        "/accessDenied",
        "/adjuncts/",
        "/error",
        "/oops",
        "/signup",
        "/tcpSlaveAgentListener",
        "/federatedLoginService/",
        "/securityRealm",
        "/instance-identity"
    );

    /**
     * {@link Authentication} object that represents the anonymous user.
     * Because Acegi creates its own {@link AnonymousAuthenticationToken} instances, the code must not
     * expect the singleton semantics. This is just a convenient instance.
     *
     * @since 1.343
     */
    public static final Authentication ANONYMOUS;

    static {
        try {
            ANONYMOUS = new AnonymousAuthenticationToken(
                    "anonymous", "anonymous", new GrantedAuthority[]{new GrantedAuthorityImpl("anonymous")});
            XSTREAM = XSTREAM2 = new XStream2();

            XSTREAM.alias("jenkins", Jenkins.class);
            XSTREAM.alias("slave", DumbSlave.class);
            XSTREAM.alias("jdk", JDK.class);
            // for backward compatibility with <1.75, recognize the tag name "view" as well.
            XSTREAM.alias("view", ListView.class);
            XSTREAM.alias("listView", ListView.class);
            XSTREAM.addImplicitArray(Jenkins.class, "_disabledAgentProtocols", "disabledAgentProtocol");
            XSTREAM.addImplicitArray(Jenkins.class, "_enabledAgentProtocols", "enabledAgentProtocol");
            XSTREAM2.addCriticalField(Jenkins.class, "securityRealm");
            XSTREAM2.addCriticalField(Jenkins.class, "authorizationStrategy");
            // this seems to be necessary to force registration of converter early enough
            Mode.class.getEnumConstants();

            // double check that initialization order didn't do any harm
            assert PERMISSIONS != null;
            assert ADMINISTER != null;

        } catch (RuntimeException | Error e) {
            // when loaded on an agent and this fails, subsequent NoClassDefFoundError will fail to chain the cause.
            // see http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8051847
            // As we don't know where the first exception will go, let's also send this to logging so that
            // we have a known place to look at.
            LOGGER.log(SEVERE, "Failed to load Jenkins.class", e);
            throw e;
        }
    }

    private static final class JenkinsJVMAccess extends JenkinsJVM {
        private static void _setJenkinsJVM(boolean jenkinsJVM) {
            JenkinsJVM.setJenkinsJVM(jenkinsJVM);
        }
    }

}
