diff --git a/core/src/main/java/hudson/matrix/MatrixProject.java b/core/src/main/java/hudson/matrix/MatrixProject.java index 7cf17920a750ec40318c523762d2417b43456f18..ecfa6a7f45b30402284d2a3dc6ad7ebfc6f09a68 100644 --- a/core/src/main/java/hudson/matrix/MatrixProject.java +++ b/core/src/main/java/hudson/matrix/MatrixProject.java @@ -456,6 +456,10 @@ public class MatrixProject extends AbstractProject im return getRootDirFor(child.getCombination()); } + public void onRenamed(MatrixConfiguration item, String oldName, String newName) throws IOException { + throw new UnsupportedOperationException(); + } + public File getRootDirFor(Combination combination) { File f = getConfigurationsDir(); for (Entry e : combination.entrySet()) diff --git a/core/src/main/java/hudson/model/AbstractItem.java b/core/src/main/java/hudson/model/AbstractItem.java index c0ade3f6ab100bb46a359aea16df2702145aa356..8877e40c455795fbc07465a2649ae4bf69962f3e 100644 --- a/core/src/main/java/hudson/model/AbstractItem.java +++ b/core/src/main/java/hudson/model/AbstractItem.java @@ -30,10 +30,13 @@ import hudson.Functions; import hudson.BulkChange; import hudson.cli.declarative.CLIMethod; import hudson.cli.declarative.CLIResolver; +import hudson.model.listeners.ItemListener; import hudson.model.listeners.SaveableListener; import hudson.security.AccessControlled; import hudson.security.Permission; import hudson.security.ACL; +import org.apache.tools.ant.taskdefs.Copy; +import org.apache.tools.ant.types.FileSet; import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; @@ -123,6 +126,121 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet this.name = name; } + /** + * Renames this item. + * Not all the Items need to support this operation, but if you decide to do so, + * you can use this method. + */ + protected void renameTo(String newName) throws IOException { + // always synchronize from bigger objects first + final ItemGroup parent = getParent(); + synchronized (parent) { + synchronized (this) { + // sanity check + if (newName == null) + throw new IllegalArgumentException("New name is not given"); + + // noop? + if (this.name.equals(newName)) + return; + + Item existing = parent.getItem(newName); + if (existing != null && existing!=this) + // the look up is case insensitive, so we need "existing!=this" + // to allow people to rename "Foo" to "foo", for example. + // see http://www.nabble.com/error-on-renaming-project-tt18061629.html + throw new IllegalArgumentException("Job " + newName + + " already exists"); + + String oldName = this.name; + File oldRoot = this.getRootDir(); + + doSetName(newName); + File newRoot = this.getRootDir(); + + boolean success = false; + + try {// rename data files + boolean interrupted = false; + boolean renamed = false; + + // try to rename the job directory. + // this may fail on Windows due to some other processes + // accessing a file. + // so retry few times before we fall back to copy. + for (int retry = 0; retry < 5; retry++) { + if (oldRoot.renameTo(newRoot)) { + renamed = true; + break; // succeeded + } + try { + Thread.sleep(500); + } catch (InterruptedException e) { + // process the interruption later + interrupted = true; + } + } + + if (interrupted) + Thread.currentThread().interrupt(); + + if (!renamed) { + // failed to rename. it must be that some lengthy + // process is going on + // to prevent a rename operation. So do a copy. Ideally + // we'd like to + // later delete the old copy, but we can't reliably do + // so, as before the VM + // shuts down there might be a new job created under the + // old name. + Copy cp = new Copy(); + cp.setProject(new org.apache.tools.ant.Project()); + cp.setTodir(newRoot); + FileSet src = new FileSet(); + src.setDir(getRootDir()); + cp.addFileset(src); + cp.setOverwrite(true); + cp.setPreserveLastModified(true); + cp.setFailOnError(false); // keep going even if + // there's an error + cp.execute(); + + // try to delete as much as possible + try { + Util.deleteRecursive(oldRoot); + } catch (IOException e) { + // but ignore the error, since we expect that + e.printStackTrace(); + } + } + + success = true; + } finally { + // if failed, back out the rename. + if (!success) + doSetName(oldName); + } + + callOnRenamed(newName, parent, oldName); + + for (ItemListener l : ItemListener.all()) + l.onRenamed(this, oldName, newName); + } + } + } + + /** + * A pointless function to work around what appears to be a HotSpot problem. See HUDSON-5756 and bug 6933067 + * on BugParade for more details. + */ + private void callOnRenamed(String newName, ItemGroup parent, String oldName) throws IOException { + try { + parent.onRenamed(this, oldName, newName); + } catch (AbstractMethodError _) { + // ignore + } + } + /** * Gets all the jobs that this {@link Item} contains as descendants. */ diff --git a/core/src/main/java/hudson/model/Hudson.java b/core/src/main/java/hudson/model/Hudson.java index bc837d62c00960238ee59ea07694b7b682ec0e6f..bcb1728a78df58ba411e18610b7c3f7f97f36882 100644 --- a/core/src/main/java/hudson/model/Hudson.java +++ b/core/src/main/java/hudson/model/Hudson.java @@ -59,7 +59,6 @@ import hudson.cli.declarative.CLIMethod; import hudson.cli.declarative.CLIResolver; import static hudson.init.InitMilestone.JOB_LOADED; import static hudson.init.InitMilestone.PLUGINS_STARTED; -import hudson.init.InitializerFinder; import hudson.init.InitMilestone; import hudson.init.InitReactorListener; import hudson.init.InitStrategy; @@ -2095,7 +2094,7 @@ public final class Hudson extends Node implements ItemGroup, Stapl * Called by {@link Job#renameTo(String)} to update relevant data structure. * assumed to be synchronized on Hudson by the caller. */ - /*package*/ void onRenamed(TopLevelItem job, String oldName, String newName) throws IOException { + public void onRenamed(TopLevelItem job, String oldName, String newName) throws IOException { items.remove(oldName); items.put(newName,job); diff --git a/core/src/main/java/hudson/model/ItemGroup.java b/core/src/main/java/hudson/model/ItemGroup.java index 76c50bee567800184ff62aee7705120afe61d0df..9713567109b038e4a08a38bbdb728ae17e321cb8 100644 --- a/core/src/main/java/hudson/model/ItemGroup.java +++ b/core/src/main/java/hudson/model/ItemGroup.java @@ -23,6 +23,7 @@ */ package hudson.model; +import java.io.IOException; import java.util.Collection; import java.io.File; @@ -71,4 +72,9 @@ public interface ItemGroup extends PersistenceRoot, ModelObject * Assigns the {@link Item#getRootDir() root directory} for children. */ File getRootDirFor(T child); + + /** + * Internal method. Called by {@link Item}s when they are renamed by users. + */ + void onRenamed(T item, String oldName, String newName) throws IOException; } diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java index 4ea567d005b2ec556b45daa61b0f96768c1dd484..d6ef7f0889d0a2a23dde7e6a2a407b0dc0aca4b3 100644 --- a/core/src/main/java/hudson/model/Job.java +++ b/core/src/main/java/hudson/model/Job.java @@ -470,107 +470,9 @@ public abstract class Job, RunT extends Run - * This method is defined on {@link Job} but really only applicable for - * {@link Job}s that are top-level items. */ public void renameTo(String newName) throws IOException { - // always synchronize from bigger objects first - final Hudson parent = Hudson.getInstance(); - assert this instanceof TopLevelItem; - synchronized (parent) { - synchronized (this) { - // sanity check - if (newName == null) - throw new IllegalArgumentException("New name is not given"); - TopLevelItem existing = parent.getItem(newName); - if (existing != null && existing!=this) - // the look up is case insensitive, so we need "existing!=this" - // to allow people to rename "Foo" to "foo", for example. - // see http://www.nabble.com/error-on-renaming-project-tt18061629.html - throw new IllegalArgumentException("Job " + newName - + " already exists"); - - // noop? - if (this.name.equals(newName)) - return; - - String oldName = this.name; - File oldRoot = this.getRootDir(); - - doSetName(newName); - File newRoot = this.getRootDir(); - - boolean success = false; - - try {// rename data files - boolean interrupted = false; - boolean renamed = false; - - // try to rename the job directory. - // this may fail on Windows due to some other processes - // accessing a file. - // so retry few times before we fall back to copy. - for (int retry = 0; retry < 5; retry++) { - if (oldRoot.renameTo(newRoot)) { - renamed = true; - break; // succeeded - } - try { - Thread.sleep(500); - } catch (InterruptedException e) { - // process the interruption later - interrupted = true; - } - } - - if (interrupted) - Thread.currentThread().interrupt(); - - if (!renamed) { - // failed to rename. it must be that some lengthy - // process is going on - // to prevent a rename operation. So do a copy. Ideally - // we'd like to - // later delete the old copy, but we can't reliably do - // so, as before the VM - // shuts down there might be a new job created under the - // old name. - Copy cp = new Copy(); - cp.setProject(new org.apache.tools.ant.Project()); - cp.setTodir(newRoot); - FileSet src = new FileSet(); - src.setDir(getRootDir()); - cp.addFileset(src); - cp.setOverwrite(true); - cp.setPreserveLastModified(true); - cp.setFailOnError(false); // keep going even if - // there's an error - cp.execute(); - - // try to delete as much as possible - try { - Util.deleteRecursive(oldRoot); - } catch (IOException e) { - // but ignore the error, since we expect that - e.printStackTrace(); - } - } - - success = true; - } finally { - // if failed, back out the rename. - if (!success) - doSetName(oldName); - } - - parent.onRenamed((TopLevelItem) this, oldName, newName); - - for (ItemListener l : ItemListener.all()) - l.onRenamed(this, oldName, newName); - } - } + super.renameTo(newName); } /**