Skip to content
Permalink
Browse files

[JENKINS-41124] Every implementation will need to protect in case of …

…bad impls anyway
  • Loading branch information...
stephenc committed Jan 18, 2017
1 parent 9d6e119 commit 442d254e443adcbb969477010e02e4eb0a4bbbe6
Showing with 120 additions and 2 deletions.
  1. +120 −2 src/main/java/com/cloudbees/hudson/plugins/folder/ChildNameGenerator.java
@@ -26,10 +26,14 @@

import com.cloudbees.hudson.plugins.folder.computed.ComputedFolder;
import hudson.Util;
import hudson.model.AbstractItem;
import hudson.model.Action;
import hudson.model.Item;
import hudson.model.ItemGroup;
import hudson.model.JobProperty;
import hudson.model.TopLevelItem;
import java.util.Map;
import java.util.WeakHashMap;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;

@@ -39,9 +43,23 @@
* <p>
* <strong>NOTE:</strong> if you need to implement this functionality, you need to ensure that users cannot rename
* items within the {@link ComputedFolder} as renaming is not supported when using a {@link ChildNameGenerator}.
* <p>
* Challenges:
* <ul>
* <li>See the notes on {@link #itemNameFromItem(AbstractFolder, TopLevelItem)} and {@link #dirNameFromItem(AbstractFolder, TopLevelItem)} regarding the constraints on how to name things</li>
* <li>There are some items which need the {@link Item#getRootDir()} during construction (those are bold evil item types
* that leak side-effects, you should fix them if you find them). While you wait for them to be fixed you will need
* to work-arround the issue by ensuring that you call {@link #beforeCreateItem(AbstractFolder, String, String)}
* passing the {@link Item#getName()} you want the item to have <strong>and</strong> the ideal unmangled name
* <strong>before</strong> you call {@code new ChildItemType(parent,name)} and then call
* {@link #afterItemCreated(Trace)} when the constructor has returned. Then insure that your
* {@link #itemNameFromItem(AbstractFolder, TopLevelItem)} and {@link #dirNameFromItem(AbstractFolder, TopLevelItem)}
* fall back to {@link #idealNameFromItem(AbstractFolder, TopLevelItem)} when the magic property they are looking
* for is missing.</li>
* </ul>
*
* @param <P>
* @param <I>
* @param <P> the type of {@link AbstractFolder}.
* @param <I> the type of {@link TopLevelItem} within the folder.
* @since 5.17
*/
// TODO migrate this functionality into core so that we can support renaming
@@ -51,6 +69,53 @@
*/
public static final String CHILD_NAME_FILE = "name-utf8.txt";

private final Map<Trace,String> idealNames = new WeakHashMap<Trace,String>();

/**
* Work-around helper method to "fix" {@link Item} constructors that have on-disk side-effects and therefore
* need {@link Item#getRootDir()} to work during the constructor.
* @param project the {@link AbstractFolder}.
* @param itemName the name that will be returned by {@link Item#getName()} when the item is constructed. This is
* the second parameter of {@link AbstractItem#(ItemGroup,String)}. This one would be the one
* with URL path segment escaping.
* @param idealName the original name before whatever URL path segment escaping you applied
* @return the {@link Trace} to keep track of when we can remove the memory of the creation process.
*/
@Nonnull
public final Trace beforeCreateItem(@Nonnull P project, @Nonnull String itemName, @Nonnull String idealName) {
final Trace trace = new Trace(project, itemName);
synchronized (idealNames) {
idealNames.put(trace, idealName);
}
return trace;
}

/**
* Clean up for a creation {@link Trace}. Not strictly required to be called, but nice implementations will do this.
* @param trace the trace.
*/
public final void afterItemCreated(@Nonnull Trace trace) {
synchronized (idealNames) {
idealNames.remove(trace);
}
}

/**
* Looks up the {@link Item} to see if we stored the ideal name before invoking the constructor that is having
* on-disk side-effects before the object has escaped {@link #beforeCreateItem(AbstractFolder, String, String)}
* @param parent
* @param item
* @return
*/
@CheckForNull
protected final String idealNameFromItem(@Nonnull P parent, @Nonnull I item) {
String itemName = item.getName();
if (itemName == null) return null;
synchronized (idealNames) {
return idealNames.get(new Trace(parent, itemName));
}
}

/**
* Infers the {@link Item#getName()} from the {@link Item} instance itself. For a valid implementation, the
* {@link ComputedFolder} using this {@link ChildNameGenerator} will be attaching into the {@link Item} the
@@ -148,4 +213,57 @@
*/
@Nonnull
public abstract String dirNameFromLegacy(@Nonnull P parent, @Nonnull String legacyDirName);

/**
* Traces the creation of a new Item in a folder.
*/
public static final class Trace {
/**
* The folder.
*/
@Nonnull
private final AbstractFolder<?> folder;
/**
* The {@link Item#getName()} that we expect to be created in the very near future.
*/
@Nonnull
private final String itemName;

/**
* Constructor.
* @param folder the folder.
* @param itemName the item name
*/
private Trace(@Nonnull AbstractFolder<?> folder, @Nonnull String itemName) {
this.folder = folder;
this.itemName = itemName;
}

/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}

Trace that = (Trace) o;

return folder == that.folder && itemName.equals(that.itemName);
}

/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
int result = folder.hashCode();
result = 31 * result + itemName.hashCode();
return result;
}
}
}

0 comments on commit 442d254

Please sign in to comment.
You can’t perform that action at this time.