From 3a115dfb8bdae998d658492ef95cfe6f0698acae Mon Sep 17 00:00:00 2001 From: kohsuke Date: Mon, 13 Jul 2009 17:00:02 +0000 Subject: [PATCH] Plugins can now hide classes in the core so that they can ship their own versions. (report) git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@19658 71c3de6d-444a-0410-be80-ed276b4c234a --- .../java/hudson/ClassicPluginStrategy.java | 88 +++++++++++++------ .../java/hudson/util/MaskingClassLoader.java | 61 +++++++++++++ 2 files changed, 120 insertions(+), 29 deletions(-) create mode 100644 core/src/main/java/hudson/util/MaskingClassLoader.java diff --git a/core/src/main/java/hudson/ClassicPluginStrategy.java b/core/src/main/java/hudson/ClassicPluginStrategy.java index 6b239e1072..52e2aa18e9 100644 --- a/core/src/main/java/hudson/ClassicPluginStrategy.java +++ b/core/src/main/java/hudson/ClassicPluginStrategy.java @@ -25,7 +25,8 @@ package hudson; import hudson.PluginWrapper.Dependency; import hudson.util.IOException2; -import hudson.model.Hudson; +import hudson.util.MaskingClassLoader; +import hudson.util.VersionNumber; import java.io.BufferedReader; import java.io.File; @@ -40,6 +41,7 @@ import java.util.List; import java.util.Arrays; import java.util.Collection; import java.util.jar.Manifest; +import java.util.jar.Attributes; import java.util.logging.Logger; import org.apache.tools.ant.BuildException; @@ -70,7 +72,7 @@ public class ClassicPluginStrategy implements PluginStrategy { public PluginWrapper createPluginWrapper(File archive) throws IOException { LOGGER.info("Loading plugin: " + archive); - Manifest manifest; + final Manifest manifest; URL baseResourceURL; boolean isLinked = archive.getName().endsWith(".hpl"); @@ -115,18 +117,17 @@ public class ClassicPluginStrategy implements PluginStrategy { } } + final Attributes atts = manifest.getMainAttributes(); + // TODO: define a mechanism to hide classes // String export = manifest.getMainAttributes().getValue("Export"); List paths = new ArrayList(); - if (isLinked) { + if (isLinked) { parseClassPath(manifest, archive, paths, "Libraries", ","); - parseClassPath(manifest, archive, paths, "Class-Path", " +"); // backward - // compatibility + parseClassPath(manifest, archive, paths, "Class-Path", " +"); // backward compatibility - baseResourceURL = resolve(archive, - manifest.getMainAttributes().getValue("Resource-Path")) - .toURI().toURL(); + baseResourceURL = resolve(archive,atts.getValue("Resource-Path")).toURI().toURL(); } else { File classes = new File(expandDir, "WEB-INF/classes"); if (classes.exists()) @@ -146,7 +147,7 @@ public class ClassicPluginStrategy implements PluginStrategy { // compute dependencies List dependencies = new ArrayList(); List optionalDependencies = new ArrayList(); - String v = manifest.getMainAttributes().getValue("Plugin-Dependencies"); + String v = atts.getValue("Plugin-Dependencies"); if (v != null) { for (String s : v.split(",")) { PluginWrapper.Dependency d = new PluginWrapper.Dependency(s); @@ -157,26 +158,10 @@ public class ClassicPluginStrategy implements PluginStrategy { } } } + for (DetachedPlugin detached : DETACHED_LIST) + detached.fix(atts,optionalDependencies); - // native m2 support moved to a plugin starting 1.296, so plugins built before that - // needs to have an implicit dependency to the maven-plugin, or NoClassDefError will ensue. - String hudsonVersion = manifest.getMainAttributes().getValue("Hudson-Version"); - String shortName = manifest.getMainAttributes().getValue("Short-Name"); - if (!"maven-plugin".equals(shortName) && - // some earlier versions of maven-hpi-plugin apparently puts "null" as a literal here. Watch out for those. - (hudsonVersion == null || hudsonVersion.equals("null") || hudsonVersion.compareTo("1.296") <= 0)) { - optionalDependencies.add(new PluginWrapper.Dependency("maven-plugin:" + Hudson.VERSION)); - } - - // subversion support was split off into a plugin in 1.311, so plugins built before that should automatically get - // subversion plugin as a dependency - if (!"subversion".equals(shortName) && - (hudsonVersion == null || hudsonVersion.equals("null") || hudsonVersion.compareTo("1.310") <= 0)) { - optionalDependencies.add(new PluginWrapper.Dependency("subversion:1.0")); - } - - ClassLoader dependencyLoader = new DependencyClassLoader(getClass() - .getClassLoader(), Util.join(dependencies,optionalDependencies)); + ClassLoader dependencyLoader = new DependencyClassLoader(getBaseClassLoader(atts), Util.join(dependencies,optionalDependencies)); // using AntClassLoader with Closeable so that we can predictably release jar files opened by URLClassLoader AntClassLoader2 classLoader = new AntClassLoader2(dependencyLoader); @@ -186,7 +171,52 @@ public class ClassicPluginStrategy implements PluginStrategy { classLoader, disableFile, dependencies, optionalDependencies); } - public void initializeComponents(PluginWrapper plugin) { + /** + * Information about plugins that were originally in the core. + */ + private static final class DetachedPlugin { + private final String shortName; + private final VersionNumber splitWhen; + private final String requireVersion; + + private DetachedPlugin(String shortName, String splitWhen, String requireVersion) { + this.shortName = shortName; + this.splitWhen = new VersionNumber(splitWhen); + this.requireVersion = requireVersion; + } + + private void fix(Attributes atts, List optionalDependencies) { + // don't fix the dependency for yourself, or else we'll have a cycle + String shortName = atts.getValue("Short-Name"); + if (this.shortName.equals(shortName)) return; + + // some earlier versions of maven-hpi-plugin apparently puts "null" as a literal in Hudson-Version. watch out for them. + String hudsonVersion = atts.getValue("Hudson-Version"); + if (hudsonVersion == null || hudsonVersion.equals("null") || new VersionNumber(hudsonVersion).compareTo(splitWhen) <= 0) + optionalDependencies.add(new PluginWrapper.Dependency(shortName+':'+requireVersion)); + } + } + + private static final List DETACHED_LIST = Arrays.asList( + new DetachedPlugin("maven-plugin","1.296","1.296"), + new DetachedPlugin("subversion","1.310","1.0") + ); + + /** + * Computes the classloader that takes the class masking into account. + * + *

+ * This mechanism allows plugins to have their own verions for libraries that core bundles. + */ + private ClassLoader getBaseClassLoader(Attributes atts) { + ClassLoader base = getClass().getClassLoader(); + String masked = atts.getValue("Mask-Classes"); + if(masked!=null) + base = new MaskingClassLoader(base, masked.trim().split("[ \t\r\n]+")); + return base; + } + + public void initializeComponents(PluginWrapper plugin) { } public void load(PluginWrapper wrapper) throws IOException { diff --git a/core/src/main/java/hudson/util/MaskingClassLoader.java b/core/src/main/java/hudson/util/MaskingClassLoader.java new file mode 100644 index 0000000000..abbfd684ce --- /dev/null +++ b/core/src/main/java/hudson/util/MaskingClassLoader.java @@ -0,0 +1,61 @@ +/* + * The MIT License + * + * Copyright (c) 2004-2009, Sun Microsystems, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package hudson.util; + +import java.util.Collection; + +/** + * {@link ClassLoader} that masks a specified set of classes + * from its parent class loader. + * + *

+ * This code is used to create an isolated environment. + * + * @author Kohsuke Kawaguchi + */ +public class MaskingClassLoader extends ClassLoader { + /** + * Prefix of the packages that should be hidden. + */ + private final String[] masks; + + public MaskingClassLoader(ClassLoader parent, String... masks) { + super(parent); + this.masks = masks; + } + + public MaskingClassLoader(ClassLoader parent, Collection masks) { + this(parent, masks.toArray(new String[masks.size()])); + } + + @Override + protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + for (String mask : masks) { + if(name.startsWith(mask)) + throw new ClassNotFoundException(); + } + + return super.loadClass(name, resolve); + } +}