diff --git a/core/src/main/java/hudson/ClassicPluginStrategy.java b/core/src/main/java/hudson/ClassicPluginStrategy.java index ce0ff62fe1..860057312b 100644 --- a/core/src/main/java/hudson/ClassicPluginStrategy.java +++ b/core/src/main/java/hudson/ClassicPluginStrategy.java @@ -26,16 +26,25 @@ package hudson; import hudson.Plugin.DummyImpl; import hudson.PluginWrapper.Dependency; import hudson.model.Hudson; +import hudson.util.CyclicGraphDetector; +import hudson.util.CyclicGraphDetector.CycleDetectedException; import hudson.util.IOException2; import hudson.util.IOUtils; import hudson.util.MaskingClassLoader; import hudson.util.VersionNumber; +import jenkins.ClassLoaderReflectionToolkit; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Expand; +import org.apache.tools.ant.types.FileSet; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.FilenameFilter; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; @@ -50,15 +59,8 @@ import java.util.jar.Manifest; import java.util.logging.Level; import java.util.logging.Logger; -import org.apache.tools.ant.AntClassLoader; -import org.apache.tools.ant.BuildException; -import org.apache.tools.ant.Project; -import org.apache.tools.ant.taskdefs.Expand; -import org.apache.tools.ant.types.FileSet; - public class ClassicPluginStrategy implements PluginStrategy { - - private static final Logger LOGGER = Logger.getLogger(ClassicPluginStrategy.class.getName()); + private final ClassLoaderReflectionToolkit clt = new ClassLoaderReflectionToolkit(); /** * Filter for jar files. @@ -167,7 +169,8 @@ public class ClassicPluginStrategy implements PluginStrategy { for (DetachedPlugin detached : DETACHED_LIST) detached.fix(atts,optionalDependencies); - ClassLoader dependencyLoader = new DependencyClassLoader(getBaseClassLoader(atts), archive, Util.join(dependencies,optionalDependencies)); + ClassLoader dependencyLoader = new DependencyClassLoader(getClass().getClassLoader(), archive, Util.join(dependencies,optionalDependencies)); + dependencyLoader = getBaseClassLoader(atts, dependencyLoader); return new PluginWrapper(pluginManager, archive, manifest, baseResourceURL, createClassLoader(paths, dependencyLoader, atts), disableFile, dependencies, optionalDependencies); @@ -249,8 +252,7 @@ public class ClassicPluginStrategy implements PluginStrategy { *

* This mechanism allows plugins to have their own versions for libraries that core bundles. */ - private ClassLoader getBaseClassLoader(Attributes atts) { - ClassLoader base = getClass().getClassLoader(); + private ClassLoader getBaseClassLoader(Attributes atts, ClassLoader base) { String masked = atts.getValue("Mask-Classes"); if(masked!=null) base = new MaskingClassLoader(base, masked.trim().split("[ \t\r\n]+")); @@ -413,22 +415,78 @@ public class ClassicPluginStrategy implements PluginStrategy { private List dependencies; + /** + * Topologically sorted list of transient dependencies. + */ + private volatile List transientDependencies; + public DependencyClassLoader(ClassLoader parent, File archive, List dependencies) { super(parent); this._for = archive; this.dependencies = dependencies; } + private List getTransitiveDependencies() { + if (transientDependencies==null) { + CyclicGraphDetector cgd = new CyclicGraphDetector() { + @Override + protected List getEdges(PluginWrapper pw) { + List dep = new ArrayList(); + for (Dependency d : pw.getDependencies()) { + PluginWrapper p = pluginManager.getPlugin(d.shortName); + if (p!=null) + dep.add(p); + } + return dep; + } + }; + + try { + for (Dependency d : dependencies) { + PluginWrapper p = pluginManager.getPlugin(d.shortName); + if (p!=null) + cgd.run(Collections.singleton(p)); + } + } catch (CycleDetectedException e) { + throw new AssertionError(e); // such error should have been reported earlier + } + + transientDependencies = cgd.getSorted(); + } + return transientDependencies; + } + +// public List getDependencyPluginWrappers() { +// List r = new ArrayList(); +// for (Dependency d : dependencies) { +// PluginWrapper w = pluginManager.getPlugin(d.shortName); +// if (w!=null) r.add(w); +// } +// return r; +// } + @Override protected Class findClass(String name) throws ClassNotFoundException { - for (Dependency dep : dependencies) { - PluginWrapper p = pluginManager.getPlugin(dep.shortName); - if(p!=null) + if (PluginManager.FAST_LOOKUP) { + for (PluginWrapper pw : getTransitiveDependencies()) { try { - return p.classLoader.loadClass(name); - } catch (ClassNotFoundException _) { - // try next + Class c = clt.findLoadedClass(pw.classLoader,name); + if (c!=null) return c; + return clt.findClass(pw.classLoader,name); + } catch (InvocationTargetException e) { + //not found. try next } + } + } else { + for (Dependency dep : dependencies) { + PluginWrapper p = pluginManager.getPlugin(dep.shortName); + if(p!=null) + try { + return p.classLoader.loadClass(name); + } catch (ClassNotFoundException _) { + // try next + } + } } throw new ClassNotFoundException(name); @@ -437,12 +495,25 @@ public class ClassicPluginStrategy implements PluginStrategy { @Override protected Enumeration findResources(String name) throws IOException { HashSet result = new HashSet(); - for (Dependency dep : dependencies) { - PluginWrapper p = pluginManager.getPlugin(dep.shortName); - if (p!=null) { - Enumeration urls = p.classLoader.getResources(name); - while (urls != null && urls.hasMoreElements()) - result.add(urls.nextElement()); + + if (PluginManager.FAST_LOOKUP) { + try { + for (PluginWrapper pw : getTransitiveDependencies()) { + Enumeration urls = clt.findResources(pw.classLoader, name); + while (urls != null && urls.hasMoreElements()) + result.add(urls.nextElement()); + } + } catch (InvocationTargetException e) { + throw new Error(e); + } + } else { + for (Dependency dep : dependencies) { + PluginWrapper p = pluginManager.getPlugin(dep.shortName); + if (p!=null) { + Enumeration urls = p.classLoader.getResources(name); + while (urls != null && urls.hasMoreElements()) + result.add(urls.nextElement()); + } } } @@ -451,12 +522,23 @@ public class ClassicPluginStrategy implements PluginStrategy { @Override protected URL findResource(String name) { - for (Dependency dep : dependencies) { - PluginWrapper p = pluginManager.getPlugin(dep.shortName); - if(p!=null) { - URL url = p.classLoader.getResource(name); - if (url!=null) - return url; + if (PluginManager.FAST_LOOKUP) { + try { + for (PluginWrapper pw : getTransitiveDependencies()) { + URL url = clt.findResource(pw.classLoader,name); + if (url!=null) return url; + } + } catch (InvocationTargetException e) { + throw new Error(e); + } + } else { + for (Dependency dep : dependencies) { + PluginWrapper p = pluginManager.getPlugin(dep.shortName); + if(p!=null) { + URL url = p.classLoader.getResource(name); + if (url!=null) + return url; + } } } @@ -483,4 +565,5 @@ public class ClassicPluginStrategy implements PluginStrategy { } public static boolean useAntClassLoader = Boolean.getBoolean(ClassicPluginStrategy.class.getName()+".useAntClassLoader"); + private static final Logger LOGGER = Logger.getLogger(ClassicPluginStrategy.class.getName()); } diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index c6ed859acc..8c0e9f332c 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -34,6 +34,7 @@ import hudson.init.InitStrategy; import hudson.init.InitializerFinder; import hudson.model.AbstractModelObject; import hudson.model.Failure; +import jenkins.ClassLoaderReflectionToolkit; import jenkins.model.Jenkins; import hudson.model.UpdateCenter; import hudson.model.UpdateSite; @@ -645,26 +646,10 @@ public abstract class PluginManager extends AbstractModelObject { */ private ConcurrentMap> generatedClasses = new ConcurrentHashMap>(); - /** - * ClassLoader.findClass(String) for a call that bypasses access modifier. - */ - private final Method FIND_CLASS, FIND_LOADED_CLASS, FIND_RESOURCE, FIND_RESOURCES; + private ClassLoaderReflectionToolkit clt = new ClassLoaderReflectionToolkit(); public UberClassLoader() { super(PluginManager.class.getClassLoader()); - - try { - FIND_CLASS = ClassLoader.class.getDeclaredMethod("findClass",String.class); - FIND_CLASS.setAccessible(true); - FIND_LOADED_CLASS = ClassLoader.class.getDeclaredMethod("findLoadedClass",String.class); - FIND_LOADED_CLASS.setAccessible(true); - FIND_RESOURCE = ClassLoader.class.getDeclaredMethod("findResource",String.class); - FIND_RESOURCE.setAccessible(true); - FIND_RESOURCES = ClassLoader.class.getDeclaredMethod("findResources",String.class); - FIND_RESOURCES.setAccessible(true); - } catch (NoSuchMethodException e) { - throw new AssertionError(e); - } } public void addNamedClass(String className, Class c) { @@ -683,14 +668,12 @@ public abstract class PluginManager extends AbstractModelObject { if (FAST_LOOKUP) { for (PluginWrapper p : activePlugins) { try { - Class c = (Class)FIND_LOADED_CLASS.invoke(p.classLoader,name); + Class c = clt.findLoadedClass(p.classLoader,name); if (c!=null) return c; // calling findClass twice appears to cause LinkageError: duplicate class def - return (Class)FIND_CLASS.invoke(p.classLoader,name); + return clt.findClass(p.classLoader,name); } catch (InvocationTargetException e) { //not found. try next - } catch (IllegalAccessException e) { - throw (Error)new IllegalAccessError().initCause(e); } } } else { @@ -711,12 +694,10 @@ public abstract class PluginManager extends AbstractModelObject { if (FAST_LOOKUP) { try { for (PluginWrapper p : activePlugins) { - URL url = (URL)FIND_RESOURCE.invoke(p.classLoader,name); + URL url = clt.findResource(p.classLoader,name); if(url!=null) return url; } - } catch (IllegalAccessException e) { - throw new Error(e); } catch (InvocationTargetException e) { throw new Error(e); } @@ -736,10 +717,8 @@ public abstract class PluginManager extends AbstractModelObject { if (FAST_LOOKUP) { try { for (PluginWrapper p : activePlugins) { - resources.addAll(Collections.list((Enumeration)FIND_RESOURCES.invoke(p.classLoader, name))); + resources.addAll(Collections.list(clt.findResources(p.classLoader, name))); } - } catch (IllegalAccessException e) { - throw new Error(e); } catch (InvocationTargetException e) { throw new Error(e); } @@ -760,7 +739,7 @@ public abstract class PluginManager extends AbstractModelObject { private static final Logger LOGGER = Logger.getLogger(PluginManager.class.getName()); - public static boolean FAST_LOOKUP = Boolean.getBoolean("fastLookup"); + public static boolean FAST_LOOKUP = !Boolean.getBoolean(PluginManager.class.getName()+".noFastLookup"); /** * Remembers why a plugin failed to deploy. diff --git a/core/src/main/java/jenkins/ClassLoaderReflectionToolkit.java b/core/src/main/java/jenkins/ClassLoaderReflectionToolkit.java new file mode 100644 index 0000000000..8461a98e9e --- /dev/null +++ b/core/src/main/java/jenkins/ClassLoaderReflectionToolkit.java @@ -0,0 +1,73 @@ +package jenkins; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.util.Enumeration; + +/** + * Reflection access to various {@link ClassLoader} methods. + * + * @author Kohsuke Kawaguchi + */ +public class ClassLoaderReflectionToolkit { + /** + * ClassLoader.findClass(String) for a call that bypasses access modifier. + */ + private final Method FIND_CLASS, FIND_LOADED_CLASS, FIND_RESOURCE, FIND_RESOURCES; + + public ClassLoaderReflectionToolkit() { + try { + FIND_CLASS = ClassLoader.class.getDeclaredMethod("findClass",String.class); + FIND_CLASS.setAccessible(true); + FIND_LOADED_CLASS = ClassLoader.class.getDeclaredMethod("findLoadedClass",String.class); + FIND_LOADED_CLASS.setAccessible(true); + FIND_RESOURCE = ClassLoader.class.getDeclaredMethod("findResource",String.class); + FIND_RESOURCE.setAccessible(true); + FIND_RESOURCES = ClassLoader.class.getDeclaredMethod("findResources",String.class); + FIND_RESOURCES.setAccessible(true); + } catch (NoSuchMethodException e) { + throw new AssertionError(e); + } + } + + public Class findLoadedClass(ClassLoader cl, String name) throws InvocationTargetException { + try { + return (Class)FIND_LOADED_CLASS.invoke(cl,name); + } catch (IllegalAccessException e) { + throw new Error(e); + } + } + + public Class findClass(ClassLoader cl, String name) throws InvocationTargetException { + try { + return (Class)FIND_CLASS.invoke(cl,name); + } catch (IllegalAccessException e) { + throw new Error(e); + } + } + + public URL findResource(ClassLoader cl, String name) throws InvocationTargetException { + try { + return (URL)FIND_RESOURCE.invoke(cl,name); + } catch (IllegalAccessException e) { + throw new Error(e); + } + } + + public Enumeration findResources(ClassLoader cl, String name) throws InvocationTargetException { + try { + return (Enumeration)FIND_RESOURCES.invoke(cl,name); + } catch (IllegalAccessException e) { + throw new Error(e); + } + } + +// private void check(InvocationTargetException e) { +// Throwable t = e.getTargetException(); +// if (t instanceof Error) +// throw (Error)t; +// if (t instanceof RuntimeException) +// throw (RuntimeException)t; +// } +}