diff --git a/src/main/java/com/cloudbees/hudson/plugins/folder/AbstractFolder.java b/src/main/java/com/cloudbees/hudson/plugins/folder/AbstractFolder.java index 2c165e1f..d41f25e5 100644 --- a/src/main/java/com/cloudbees/hudson/plugins/folder/AbstractFolder.java +++ b/src/main/java/com/cloudbees/hudson/plugins/folder/AbstractFolder.java @@ -25,6 +25,7 @@ package com.cloudbees.hudson.plugins.folder; import com.cloudbees.hudson.plugins.folder.computed.ComputedFolder; +import com.cloudbees.hudson.plugins.folder.computed.FolderComputation; import com.cloudbees.hudson.plugins.folder.config.AbstractFolderConfiguration; import com.cloudbees.hudson.plugins.folder.health.FolderHealthMetric; import com.cloudbees.hudson.plugins.folder.health.FolderHealthMetricDescriptor; @@ -94,6 +95,7 @@ import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import javax.servlet.ServletException; +import jenkins.model.DirectlyModifiableTopLevelItemGroup; import jenkins.model.Jenkins; import jenkins.model.ModelObjectWithChildren; import jenkins.model.ProjectNamingStrategy; @@ -101,6 +103,7 @@ import net.sf.json.JSONObject; import org.acegisecurity.AccessDeniedException; import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.Beta; import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.stapler.HttpRedirect; import org.kohsuke.stapler.HttpResponse; @@ -148,6 +151,10 @@ public abstract class AbstractFolder extends AbstractIte private static final AtomicBoolean loadJobTotalRan = new AtomicBoolean(); private static final int TICK_INTERVAL = 15000; + /** Whether execution is currently inside {@link #reloadThis}. */ + @Restricted(NoExternalUse.class) + protected static final ThreadLocal reloadingThis = ThreadLocal.withInitial(() -> false); + @Initializer(before=InitMilestone.JOB_LOADED, fatal=false) public static void loadJobTotal() { if (!loadJobTotalRan.compareAndSet(false, true)) { @@ -404,6 +411,39 @@ protected final I itemsPut(String name, I item) { return items.put(name, item); } + /** + * Reloads this folder itself. + * Compared to {@link #load}, this method skips parts of {@link #onLoad} such as the call to {@link #loadChildren}. + * Nor will it set {@link Items#whileUpdatingByXml}. + * In the case of a {@link ComputedFolder} it also will not call {@link FolderComputation#load}. + */ + @SuppressWarnings("unchecked") + @Restricted(Beta.class) + public void reloadThis() throws IOException { + LOGGER.fine(() -> "reloadThis " + this); + checkPermission(Item.CONFIGURE); + getConfigFile().unmarshal(this); + boolean old = reloadingThis.get(); + try { + reloadingThis.set(true); + onLoad(getParent(), getParent().getItemName(getRootDir(), this)); + } finally { + reloadingThis.set(old); + } + } + + /** + * Adds an item to be the folder which was already loaded via {@link Items#load}. + * Unlike {@link DirectlyModifiableTopLevelItemGroup#add} this can be used even on a {@link ComputedFolder}. + */ + @Restricted(Beta.class) + public void addLoadedChild(I item, String name) throws IOException, IllegalArgumentException { + if (items.containsKey(name)) { + throw new IllegalArgumentException("already an item '" + name + "'"); + } + itemsPut(item.getName(), item); + } + /** * {@inheritDoc} */ @@ -411,6 +451,10 @@ protected final I itemsPut(String name, I item) { public void onLoad(ItemGroup parent, String name) throws IOException { super.onLoad(parent, name); init(); + if (reloadingThis.get()) { + LOGGER.fine(() -> this + " skipping the rest of onLoad"); + return; + } final Thread t = Thread.currentThread(); String n = t.getName(); try { diff --git a/src/main/java/com/cloudbees/hudson/plugins/folder/Folder.java b/src/main/java/com/cloudbees/hudson/plugins/folder/Folder.java index 9539ddc0..53a53a60 100644 --- a/src/main/java/com/cloudbees/hudson/plugins/folder/Folder.java +++ b/src/main/java/com/cloudbees/hudson/plugins/folder/Folder.java @@ -347,10 +347,7 @@ public DescriptorImpl getDescriptor() { if (!canAdd(item)) { throw new IllegalArgumentException(); } - if (items.containsKey(name)) { - throw new IllegalArgumentException("already an item '" + name + "'"); - } - itemsPut(item.getName(), item); + addLoadedChild(item, name); return item; } diff --git a/src/main/java/com/cloudbees/hudson/plugins/folder/computed/ComputedFolder.java b/src/main/java/com/cloudbees/hudson/plugins/folder/computed/ComputedFolder.java index fdfce402..722966db 100644 --- a/src/main/java/com/cloudbees/hudson/plugins/folder/computed/ComputedFolder.java +++ b/src/main/java/com/cloudbees/hudson/plugins/folder/computed/ComputedFolder.java @@ -209,6 +209,10 @@ public void onCreatedFromScratch() { @Override public void onLoad(ItemGroup parent, String name) throws IOException { super.onLoad(parent, name); + if (reloadingThis.get()) { + LOGGER.fine(() -> this + " skipping the rest of onLoad"); + return; + } try { FileUtils.forceMkdir(getComputationDir()); } catch (IOException x) {