diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index b7a7a7f6c5e2d5aa855688cf313a49ceefc96bb7..375328cd1dfb9f653c2da696bae3a7e7826ed271 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -28,10 +28,12 @@ import hudson.init.InitMilestone; import hudson.init.InitStrategy; import hudson.init.InitializerFinder; import hudson.model.AbstractModelObject; +import hudson.model.AdministrativeMonitor; import hudson.model.Descriptor; import hudson.model.Failure; import hudson.model.UpdateCenter; import hudson.model.UpdateSite; +import hudson.model.UpdateSite.Data; import hudson.util.CyclicGraphDetector; import hudson.util.CyclicGraphDetector.CycleDetectedException; import hudson.util.FormValidation; @@ -243,6 +245,18 @@ public abstract class PluginManager extends AbstractModelObject { r.add(p); } } + + @Override + protected void reactOnCycle(PluginWrapper q, List cycle) + throws hudson.util.CyclicGraphDetector.CycleDetectedException { + + LOGGER.log(Level.SEVERE, "found cycle in plugin dependencies: (root="+q+", deactivating all involved) "+Util.join(cycle," -> ")); + for (PluginWrapper pluginWrapper : cycle) { + pluginWrapper.setHasCycleDependency(true); + failedPlugins.add(new FailedPlugin(pluginWrapper.getShortName(), new CycleDetectedException(cycle))); + } + } + }; cgd.run(getPlugins()); @@ -810,4 +824,32 @@ public abstract class PluginManager extends AbstractModelObject { /*package*/ static final class PluginInstanceStore { final Map store = new Hashtable(); } + + /** + * {@link AdministrativeMonitor} that checks if there's Jenkins update. + */ + @Extension + public static final class PluginCycleDependenciesMonitor extends AdministrativeMonitor { + + private transient volatile boolean isActive = false; + + private transient volatile List pluginsWithCycle; + + public boolean isActivated() { + if(pluginsWithCycle == null){ + pluginsWithCycle = new ArrayList(); + for (PluginWrapper p : Jenkins.getInstance().getPluginManager().getPlugins()) { + if(p.hasCycleDependency()){ + pluginsWithCycle.add(p.getShortName()); + isActive = true; + } + } + } + return true; + } + + public List getPluginsWithCycle() { + return pluginsWithCycle; + } + } } diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java index aa51b0a2b845b2a399533bd8af512327efe07f3c..c105393695bf4fa064e5378be69c98fe2986ef72 100644 --- a/core/src/main/java/hudson/PluginWrapper.java +++ b/core/src/main/java/hudson/PluginWrapper.java @@ -125,6 +125,8 @@ public class PluginWrapper implements Comparable { * The snapshot of disableFile.exists() as of the start up. */ private final boolean active; + + private boolean hasCycleDependency = false; private final List dependencies; private final List optionalDependencies; @@ -276,6 +278,8 @@ public class PluginWrapper implements Comparable { return null; } + + @Override public String toString() { @@ -379,9 +383,17 @@ public class PluginWrapper implements Comparable { * Returns true if this plugin is enabled for this session. */ public boolean isActive() { - return active; + return active && !hasCycleDependency(); + } + + public boolean hasCycleDependency(){ + return hasCycleDependency; } + public void setHasCycleDependency(boolean hasCycle){ + hasCycleDependency = hasCycle; + } + public boolean isBundled() { return isBundled; } diff --git a/core/src/main/java/hudson/util/CyclicGraphDetector.java b/core/src/main/java/hudson/util/CyclicGraphDetector.java index 7999e558ca003ae31b28c883bf89466cd5f5285e..74f7fdeb48afc155a1aa7ecfd22ffc087cb25c50 100644 --- a/core/src/main/java/hudson/util/CyclicGraphDetector.java +++ b/core/src/main/java/hudson/util/CyclicGraphDetector.java @@ -23,8 +23,9 @@ public abstract class CyclicGraphDetector { private final List topologicalOrder = new ArrayList(); public void run(Iterable allNodes) throws CycleDetectedException { - for (N n : allNodes) + for (N n : allNodes){ visit(n); + } } /** @@ -62,8 +63,18 @@ public abstract class CyclicGraphDetector { private void detectedCycle(N q) throws CycleDetectedException { int i = path.indexOf(q); path.push(q); - throw new CycleDetectedException(path.subList(i, path.size())); + reactOnCycle(q, path.subList(i, path.size())); } + + /** + * React on detected cycles - default implementation throws an exception. + * @param q + * @param cycle + * @throws CycleDetectedException + */ + protected void reactOnCycle(N q, List cycle) throws CycleDetectedException{ + throw new CycleDetectedException(cycle); + } public static final class CycleDetectedException extends Exception { public final List cycle;