Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #82 from stephenc/jenkins-41124
[JENKINS-41124] Provide a mechanism for a computed folder to mangle directory names
  • Loading branch information
stephenc committed Jan 22, 2017
2 parents 152375c + b633614 commit c6097da
Show file tree
Hide file tree
Showing 14 changed files with 2,445 additions and 26 deletions.
245 changes: 241 additions & 4 deletions src/main/java/com/cloudbees/hudson/plugins/folder/AbstractFolder.java
Expand Up @@ -35,6 +35,7 @@
import hudson.BulkChange;
import hudson.Extension;
import hudson.Util;
import hudson.XmlFile;
import hudson.init.InitMilestone;
import hudson.init.Initializer;
import hudson.model.AbstractItem;
Expand All @@ -44,6 +45,7 @@
import hudson.model.HealthReport;
import hudson.model.Item;
import hudson.model.ItemGroup;
import hudson.model.ItemGroupMixIn;
import hudson.model.Items;
import hudson.model.Job;
import hudson.model.ModifiableViewGroup;
Expand All @@ -69,6 +71,7 @@
import hudson.views.DefaultViewsTabBar;
import hudson.views.ViewsTabBar;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
Expand All @@ -77,6 +80,7 @@
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;
Expand All @@ -101,6 +105,8 @@
import org.acegisecurity.AccessDeniedException;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.accmod.restrictions.NoExternalUse;
Expand All @@ -114,7 +120,6 @@
import org.kohsuke.stapler.interceptor.RequirePOST;

import static hudson.Util.fixEmpty;
import static hudson.model.ItemGroupMixIn.loadChildren;

/**
* A general-purpose {@link ItemGroup}.
Expand Down Expand Up @@ -461,6 +466,218 @@ public boolean replaceActions(@Nonnull Class<? extends Action> clazz, Action a)
return !(old.isEmpty() && found);
}

/**
* Loads all the child {@link Item}s.
*
* @param modulesDir Directory that contains sub-directories for each child item.
*/
// TODO replace with ItemGroupMixIn.loadChildren once baseline core has JENKINS-41222 merged
public static <K, V extends TopLevelItem> Map<K, V> loadChildren(AbstractFolder<V> parent, File modulesDir,
Function1<? extends K, ? super V> key) {
CopyOnWriteMap.Tree<K, V> configurations = new CopyOnWriteMap.Tree<K, V>();
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(new FileFilter() {
public boolean accept(File child) {
return child.isDirectory();
}
});
if (subdirs == null) {
return configurations;
}
final ChildNameGenerator<AbstractFolder<V>,V> childNameGenerator = parent.childNameGenerator();
Map<String,V> byDirName = new HashMap<String, V>();
if (parent.items != null) {
if (childNameGenerator == null) {
for (V item : parent.items.values()) {
byDirName.put(item.getName(), item);
}
} else {
for (V item : parent.items.values()) {
String itemName = childNameGenerator.dirNameFromItem(parent, item);
if (itemName == null) {
itemName = childNameGenerator.dirNameFromLegacy(parent, item.getName());
}
byDirName.put(itemName, item);
}
}
}
for (File subdir : subdirs) {
try {
boolean legacy;
String childName;
if (childNameGenerator == null) {
// the directory name is the item name
childName = subdir.getName();
legacy = false;
} else {
File nameFile = new File(subdir, ChildNameGenerator.CHILD_NAME_FILE);
if (nameFile.isFile()) {
childName = StringUtils.trimToNull(FileUtils.readFileToString(nameFile, "UTF-8"));
if (childName == null) {
LOGGER.log(Level.WARNING, "{0} was empty, assuming child name is {1}",
new Object[]{nameFile, subdir.getName()});
legacy = true;
childName = subdir.getName();
} else {
legacy = false;
}
} else {
// this is a legacy name
legacy = true;
childName = subdir.getName();
}
}
// Try to retain the identity of an existing child object if we can.
V item = byDirName.get(childName);
boolean itemNeedsSave = false;
if (item == null) {
XmlFile xmlFile = Items.getConfigFile(subdir);
if (xmlFile.exists()) {
item = (V) xmlFile.read();
String name;
if (childNameGenerator == null) {
name = subdir.getName();
} else {
String dirName = childNameGenerator.dirNameFromItem(parent, item);
if (dirName == null) {
dirName = childNameGenerator.dirNameFromLegacy(parent, childName);
BulkChange bc = new BulkChange(item); // suppress any attempt to save as parent not set
try {
childNameGenerator.recordLegacyName(parent, item, childName);
itemNeedsSave = true;
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Ignoring {0} as could not record legacy name",
subdir);
continue;
} finally {
bc.abort();
}
}
if (!subdir.getName().equals(dirName)) {
File newSubdir = parent.getRootDirFor(dirName);
if (newSubdir.exists()) {
LOGGER.log(Level.WARNING, "Ignoring {0} as folder naming rules collide with {1}",
new Object[]{subdir, newSubdir});
continue;

}
LOGGER.log(Level.INFO, "Moving {0} to {1} in accordance with folder naming rules",
new Object[]{subdir, newSubdir});
if (!subdir.renameTo(newSubdir)) {
LOGGER.log(Level.WARNING, "Failed to move {0} to {1}. Ignoring this item",
new Object[]{subdir, newSubdir});
continue;
}
}
File nameFile = new File(parent.getRootDirFor(dirName), ChildNameGenerator.CHILD_NAME_FILE);
name = childNameGenerator.itemNameFromItem(parent, item);
if (name == null) {
name = childNameGenerator.itemNameFromLegacy(parent, childName);
FileUtils.writeStringToFile(nameFile, name, "UTF-8");
BulkChange bc = new BulkChange(item); // suppress any attempt to save as parent not set
try {
childNameGenerator.recordLegacyName(parent, item, childName);
itemNeedsSave = true;
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Ignoring {0} as could not record legacy name",
subdir);
continue;
} finally {
bc.abort();
}
} else if (!childName.equals(name) || legacy) {
FileUtils.writeStringToFile(nameFile, name, "UTF-8");
}
}
item.onLoad(parent, name);
} else {
LOGGER.log(Level.WARNING, "could not find file " + xmlFile.getFile());
continue;
}
} else {
String name;
if (childNameGenerator == null) {
name = subdir.getName();
} else {
File nameFile = new File(subdir, ChildNameGenerator.CHILD_NAME_FILE);
name = childNameGenerator.itemNameFromItem(parent, item);
if (name == null) {
name = childNameGenerator.itemNameFromLegacy(parent, childName);
FileUtils.writeStringToFile(nameFile, name, "UTF-8");
BulkChange bc = new BulkChange(item); // suppress any attempt to save as parent not set
try {
childNameGenerator.recordLegacyName(parent, item, childName);
itemNeedsSave = true;
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Ignoring {0} as could not record legacy name",
subdir);
continue;
} finally {
bc.abort();
}
} else if (!childName.equals(name) || legacy) {
FileUtils.writeStringToFile(nameFile, name, "UTF-8");
}
if (!subdir.getName().equals(name) && item instanceof AbstractItem
&& ((AbstractItem) item).getDisplayNameOrNull() == null) {
BulkChange bc = new BulkChange(item);
try {
((AbstractItem) item).setDisplayName(childName);
} finally {
bc.abort();
}
}
}
item.onLoad(parent, name);
}
if (itemNeedsSave) {
try {
item.save();
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Could not update {0} after applying folder naming rules",
item.getFullName());
}
}
configurations.put(key.call(item), item);
} catch (Exception e) {
Logger.getLogger(ItemGroupMixIn.class.getName()).log(Level.WARNING, "could not load " + subdir, e);
}
}

return configurations;
}


protected final I itemsPut(String name, I item) {
ChildNameGenerator<AbstractFolder<I>, I> childNameGenerator = childNameGenerator();
if (childNameGenerator != null) {
File nameFile = new File(getRootDirFor(item), ChildNameGenerator.CHILD_NAME_FILE);
String oldName;
if (nameFile.isFile()) {
try {
oldName = StringUtils.trimToNull(FileUtils.readFileToString(nameFile, "UTF-8"));
} catch (IOException e) {
oldName = null;
}
} else {
oldName = null;
}
if (!name.equals(oldName)) {
try {
FileUtils.writeStringToFile(nameFile, name, "UTF-8");
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Could not create " + nameFile);
}
}
}
return items.put(name, item);
}

/**
* {@inheritDoc}
*/
Expand All @@ -481,6 +698,7 @@ public void onLoad(ItemGroup<? extends Item> parent, String name) throws IOExcep
}
}

final ChildNameGenerator<AbstractFolder<I>,I> childNameGenerator = childNameGenerator();
items = loadChildren(this, getJobsDir(), new Function1<String,I>() {
@Override
public String call(I item) {
Expand All @@ -494,15 +712,26 @@ public String call(I item) {
LOGGER.log(Level.INFO, String.format("Loading job %s (%.1f%%)", fullName, percentage));
loadingTick = now;
}
// TODO loadChildren does not support decoding folder names
return item.getName();
if (childNameGenerator == null) {
return item.getName();
} else {
String name = childNameGenerator.itemNameFromItem(AbstractFolder.this, item);
if (name == null) {
return childNameGenerator.itemNameFromLegacy(AbstractFolder.this, item.getName());
}
return name;
}
}
});
} finally {
t.setName(n);
}
}

private ChildNameGenerator<AbstractFolder<I>,I> childNameGenerator() {
return getDescriptor().childNameGenerator();
}

/**
* {@inheritDoc}
*/
Expand Down Expand Up @@ -539,7 +768,15 @@ protected final File getRootDirFor(String name) {

@Override
public File getRootDirFor(I child) {
return getRootDirFor(child.getName()); // TODO see comment regarding loadChildren and encoding
ChildNameGenerator<AbstractFolder<I>,I> childNameGenerator = childNameGenerator();
if (childNameGenerator == null) {
return getRootDirFor(child.getName());
}
String name = childNameGenerator.dirNameFromItem(this, child);
if (name == null) {
name = childNameGenerator.dirNameFromLegacy(this, child.getName());
}
return getRootDirFor(name);
}

/**
Expand Down

0 comments on commit c6097da

Please sign in to comment.