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 a94ee209..7be6ba09 100644 --- a/src/main/java/com/cloudbees/hudson/plugins/folder/AbstractFolder.java +++ b/src/main/java/com/cloudbees/hudson/plugins/folder/AbstractFolder.java @@ -34,9 +34,9 @@ import com.cloudbees.hudson.plugins.folder.views.DefaultFolderViewHolder; import hudson.BulkChange; import hudson.Extension; +import hudson.ExtensionList; import hudson.Util; import static hudson.Util.fixEmpty; -import hudson.XmlFile; import hudson.init.InitMilestone; import hudson.init.Initializer; import hudson.model.AbstractItem; @@ -73,13 +73,11 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletResponse; import java.io.File; -import java.io.FileNotFoundException; import java.io.IOException; import java.text.ParseException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -340,49 +338,7 @@ protected void initViews(List views) throws IOException { // TODO replace with ItemGroupMixIn.loadChildren once baseline core has JENKINS-41222 merged public static Map loadChildren(AbstractFolder parent, File modulesDir, Function key) { - CopyOnWriteMap.Tree configurations = new CopyOnWriteMap.Tree<>(); - if (!modulesDir.isDirectory() && !modulesDir.mkdirs()) { // make sure it exists - LOGGER.log(Level.SEVERE, "Could not create {0} for folder {1}", - new Object[]{modulesDir, parent.getFullName()}); - return configurations; - } - - File[] subdirs = modulesDir.listFiles(File::isDirectory); - if (subdirs == null) { - return configurations; - } - final ChildNameGenerator,V> childNameGenerator = parent.childNameGenerator(); - Map byDirName = new HashMap<>(); - if (parent.items != null) { - for (V item : parent.items.values()) { - byDirName.put(childNameGenerator.dirName(parent, item), item); - } - } - for (File subdir : subdirs) { - // Try to retain the identity of an existing child object if we can. - V item = byDirName.get(subdir.getName()); - try { - if (item == null) { - XmlFile xmlFile = Items.getConfigFile(subdir); - if (xmlFile.exists()) { - item = (V) xmlFile.read(); - } else { - throw new FileNotFoundException("Could not find configuration file " + xmlFile.getFile()); - } - } - String name = childNameGenerator.itemNameFromItem(parent, item); - if (name == null) { - name = subdir.getName(); - } - item.onLoad(parent, name); - configurations.put(key.apply(item), item); - } catch (Exception e) { - LOGGER.warning(() -> "could not load " + subdir + " due to " + e); - LOGGER.log(Level.FINE, null, e); - } - } - - return configurations; + return ExtensionList.lookupFirst(ChildLoader.class).loadChildren(parent, modulesDir, key); } @Override @@ -478,7 +434,7 @@ public void onLoad(ItemGroup parent, String name) throws IOExcep } } - private ChildNameGenerator,I> childNameGenerator() { + ChildNameGenerator,I> childNameGenerator() { return getDescriptor().childNameGenerator(); } diff --git a/src/main/java/com/cloudbees/hudson/plugins/folder/ChildLoader.java b/src/main/java/com/cloudbees/hudson/plugins/folder/ChildLoader.java new file mode 100644 index 00000000..8ddfb1e0 --- /dev/null +++ b/src/main/java/com/cloudbees/hudson/plugins/folder/ChildLoader.java @@ -0,0 +1,99 @@ +package com.cloudbees.hudson.plugins.folder; + +import edu.umd.cs.findbugs.annotations.CheckForNull; +import hudson.ExtensionPoint; +import hudson.XmlFile; +import hudson.model.Item; +import hudson.model.Items; +import hudson.model.TopLevelItem; +import java.io.File; +import java.io.FileNotFoundException; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.logging.Level; +import java.util.logging.Logger; + +public abstract class ChildLoader implements ExtensionPoint { + + private static final Logger LOGGER = Logger.getLogger(ChildLoader.class.getName()); + + /** + * Loads all the child {@link Item}s. + * + * @param parent the parent of the children. + * @param modulesDir Directory that contains sub-directories for each child item. + * @param key the key generating function. + * @param the key type + * @param the child type. + * @return a map of the children keyed by the generated keys. + */ + protected abstract Map loadChildren( + AbstractFolder parent, File modulesDir, Function key); + + /** + * Ensure that the specified directory exists. If the directory does not exist, attempt to create it. + * + * @param modulesDir the directory that should exist or needs to be created + * @param parent parent folder associated with the directory + * @return {@code true} if the directory exists or was successfully created; {@code false} otherwise + */ + protected boolean ensureDirExists(File modulesDir, AbstractFolder parent) { + if (!modulesDir.isDirectory() && !modulesDir.mkdirs()) { // make sure it exists + LOGGER.log(Level.SEVERE, "Could not create {0} for folder {1}", + new Object[]{modulesDir, parent.getFullName()}); + return false; + } + return true; + } + + /** + * Retrieve a map of items contained within a specified parent folder, keyed by their directory names. + * + * @param parent the parent folder containing the items + * @return a map where the keys are the directory names of the items and the values are the items themselves + */ + protected Map getItemsByDirName(AbstractFolder parent) { + Map byDirName = new HashMap<>(); + if (parent.items != null) { + final ChildNameGenerator, V> childNameGenerator = parent.childNameGenerator(); + for (V item : parent.items.values()) { + byDirName.put(childNameGenerator.dirName(parent, item), item); + } + } + return byDirName; + } + + /** + * Load a {@link TopLevelItem} from a given directory. The method attempts to read the item configuration, assign an + * appropriate name to the item, and initialize it within the provided parent folder. + * + * @param parent the parent folder that will contain the loaded item + * @param subdir the directory from which the item configuration can potentially be loaded + * @param item an optional pre-existing item that can be reused; if null, the item will be loaded from the directory + * @return the loaded item if successful, or {@code null} if an error occurs during loading + */ + @CheckForNull + public V loadItem(AbstractFolder parent, File subdir, @CheckForNull V item) { + try { + if (item == null) { + XmlFile xmlFile = Items.getConfigFile(subdir); + if (xmlFile.exists()) { + item = (V) xmlFile.read(); + } else { + throw new FileNotFoundException("Could not find configuration file " + xmlFile.getFile()); + } + } + String name = parent.childNameGenerator().itemNameFromItem(parent, item); + if (name == null) { + name = subdir.getName(); + } + item.onLoad(parent, name); + return item; + } catch (Exception e) { + LOGGER.warning(() -> "could not load " + subdir + " due to " + e); + LOGGER.log(Level.FINE, null, e); + return null; + } + } +} diff --git a/src/main/java/com/cloudbees/hudson/plugins/folder/DefaultChildLoader.java b/src/main/java/com/cloudbees/hudson/plugins/folder/DefaultChildLoader.java new file mode 100644 index 00000000..91d358b3 --- /dev/null +++ b/src/main/java/com/cloudbees/hudson/plugins/folder/DefaultChildLoader.java @@ -0,0 +1,38 @@ +package com.cloudbees.hudson.plugins.folder; + +import hudson.Extension; +import hudson.model.TopLevelItem; +import hudson.util.CopyOnWriteMap; +import java.io.File; +import java.util.Map; +import java.util.function.Function; + +@Extension +public final class DefaultChildLoader extends ChildLoader { + + @Override + public Map loadChildren( + AbstractFolder parent, File modulesDir, Function key) { + CopyOnWriteMap.Tree configurations = new CopyOnWriteMap.Tree<>(); + if (!ensureDirExists(modulesDir, parent)) { + return configurations; + } + + File[] subdirs = modulesDir.listFiles(File::isDirectory); + if (subdirs == null) { + return configurations; + } + + Map byDirName = getItemsByDirName(parent); + for (File subdir : subdirs) { + // Try to retain the identity of an existing child object if we can. + V existingItem = byDirName.get(subdir.getName()); + V item = loadItem(parent, subdir, existingItem); + if (item != null) { + configurations.put(key.apply(item), item); + } + } + + return configurations; + } +}