ClassicPluginStrategy.java 28.5 KB
Newer Older
K
kohsuke 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
/*
 * The MIT License
 * 
 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Jean-Baptiste Quenot, Tom Huybrechts
 * 
 * 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.
 */
24 25
package hudson;

26
import com.google.common.collect.Lists;
27
import hudson.Plugin.DummyImpl;
28
import hudson.PluginWrapper.Dependency;
29
import hudson.model.Hudson;
30 31
import hudson.util.CyclicGraphDetector;
import hudson.util.CyclicGraphDetector.CycleDetectedException;
32
import hudson.util.IOUtils;
33 34
import hudson.util.MaskingClassLoader;
import hudson.util.VersionNumber;
35
import jenkins.ClassLoaderReflectionToolkit;
36
import jenkins.ExtensionFilter;
K
Kohsuke Kawaguchi 已提交
37
import org.apache.commons.io.output.NullOutputStream;
38 39 40 41
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;
K
Kohsuke Kawaguchi 已提交
42
import org.apache.tools.ant.taskdefs.Zip;
43
import org.apache.tools.ant.types.FileSet;
K
Kohsuke Kawaguchi 已提交
44 45 46 47 48 49 50 51
import org.apache.tools.ant.types.PatternSet;
import org.apache.tools.ant.types.Resource;
import org.apache.tools.ant.types.ZipFileSet;
import org.apache.tools.ant.types.resources.MappedResourceCollection;
import org.apache.tools.ant.util.GlobPatternMapper;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipExtraField;
import org.apache.tools.zip.ZipOutputStream;
52

53
import java.io.Closeable;
54 55 56 57
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
K
Kohsuke Kawaguchi 已提交
58
import java.lang.reflect.Field;
59
import java.lang.reflect.InvocationTargetException;
60 61
import java.net.URL;
import java.util.ArrayList;
62 63
import java.util.Arrays;
import java.util.Collection;
64 65 66 67
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
K
Kohsuke Kawaguchi 已提交
68
import java.util.Vector;
69
import java.util.jar.Attributes;
70
import java.util.jar.Manifest;
S
Stuart McCulloch 已提交
71
import java.util.logging.Level;
72 73 74
import java.util.logging.Logger;

public class ClassicPluginStrategy implements PluginStrategy {
75
    private final ClassLoaderReflectionToolkit clt = new ClassLoaderReflectionToolkit();
76 77 78 79 80 81 82 83 84 85 86 87

    /**
     * Filter for jar files.
     */
    private static final FilenameFilter JAR_FILTER = new FilenameFilter() {
        public boolean accept(File dir,String name) {
            return name.endsWith(".jar");
        }
    };

    private PluginManager pluginManager;

88 89 90 91 92
    /**
     * All the plugins eventually delegate this classloader to load core, servlet APIs, and SE runtime.
     */
    private final MaskingClassLoader coreClassLoader = new MaskingClassLoader(getClass().getClassLoader());

L
lacostej 已提交
93 94 95 96 97 98 99
    public ClassicPluginStrategy(PluginManager pluginManager) {
        this.pluginManager = pluginManager;
    }

    public PluginWrapper createPluginWrapper(File archive) throws IOException {
        final Manifest manifest;
        URL baseResourceURL;
100

L
lacostej 已提交
101 102
        File expandDir = null;
        // if .hpi, this is the directory where war is expanded
103

I
imod 已提交
104
        boolean isLinked = archive.getName().endsWith(".hpl") || archive.getName().endsWith(".jpl");
L
lacostej 已提交
105 106
        if (isLinked) {
            // resolve the .hpl file to the location of the manifest file
107
            final String firstLine = IOUtils.readFirstLine(new FileInputStream(archive), "UTF-8");
L
lacostej 已提交
108 109 110 111 112 113 114 115 116 117 118
            if (firstLine.startsWith("Manifest-Version:")) {
                // this is the manifest already
            } else {
                // indirection
                archive = resolve(archive, firstLine);
            }
            // then parse manifest
            FileInputStream in = new FileInputStream(archive);
            try {
                manifest = new Manifest(in);
            } catch (IOException e) {
119
                throw new IOException("Failed to load " + archive, e);
L
lacostej 已提交
120 121 122 123
            } finally {
                in.close();
            }
        } else {
K
kohsuke 已提交
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
            if (archive.isDirectory()) {// already expanded
                expandDir = archive;
            } else {
                expandDir = new File(archive.getParentFile(), PluginWrapper.getBaseName(archive));
                explode(archive, expandDir);
            }

            File manifestFile = new File(expandDir, "META-INF/MANIFEST.MF");
            if (!manifestFile.exists()) {
                throw new IOException(
                        "Plugin installation failed. No manifest at "
                                + manifestFile);
            }
            FileInputStream fin = new FileInputStream(manifestFile);
            try {
                manifest = new Manifest(fin);
            } finally {
                fin.close();
            }
L
lacostej 已提交
143
        }
144

145 146
        final Attributes atts = manifest.getMainAttributes();

L
lacostej 已提交
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
        // TODO: define a mechanism to hide classes
        // String export = manifest.getMainAttributes().getValue("Export");

        List<File> paths = new ArrayList<File>();
        if (isLinked) {
            parseClassPath(manifest, archive, paths, "Libraries", ",");
            parseClassPath(manifest, archive, paths, "Class-Path", " +"); // backward compatibility

            baseResourceURL = resolve(archive,atts.getValue("Resource-Path")).toURI().toURL();
        } else {
            File classes = new File(expandDir, "WEB-INF/classes");
            if (classes.exists())
                paths.add(classes);
            File lib = new File(expandDir, "WEB-INF/lib");
            File[] libs = lib.listFiles(JAR_FILTER);
162
            if (libs != null)
163
                paths.addAll(Arrays.asList(libs));
164

L
lacostej 已提交
165 166
            baseResourceURL = expandDir.toURI().toURL();
        }
167 168 169 170
        File disableFile = new File(archive.getPath() + ".disabled");
        if (disableFile.exists()) {
            LOGGER.info("Plugin " + archive.getName() + " is disabled");
        }
171

L
lacostej 已提交
172 173 174 175 176 177 178 179 180 181 182 183 184 185
        // compute dependencies
        List<PluginWrapper.Dependency> dependencies = new ArrayList<PluginWrapper.Dependency>();
        List<PluginWrapper.Dependency> optionalDependencies = new ArrayList<PluginWrapper.Dependency>();
        String v = atts.getValue("Plugin-Dependencies");
        if (v != null) {
            for (String s : v.split(",")) {
                PluginWrapper.Dependency d = new PluginWrapper.Dependency(s);
                if (d.optional) {
                    optionalDependencies.add(d);
                } else {
                    dependencies.add(d);
                }
            }
        }
186 187
        for (DetachedPlugin detached : DETACHED_LIST)
            detached.fix(atts,optionalDependencies);
188

189 190 191 192 193 194 195 196 197 198
        // Register global classpath mask. This is useful for hiding JavaEE APIs that you might see from the container,
        // such as database plugin for JPA support. The Mask-Classes attribute is insufficient because those classes
        // also need to be masked by all the other plugins that depend on the database plugin.
        String masked = atts.getValue("Global-Mask-Classes");
        if(masked!=null) {
            for (String pkg : masked.trim().split("[ \t\r\n]+"))
                coreClassLoader.add(pkg);
        }

        ClassLoader dependencyLoader = new DependencyClassLoader(coreClassLoader, archive, Util.join(dependencies,optionalDependencies));
199
        dependencyLoader = getBaseClassLoader(atts, dependencyLoader);
200

201
        return new PluginWrapper(pluginManager, archive, manifest, baseResourceURL,
202 203
                createClassLoader(paths, dependencyLoader, atts), disableFile, dependencies, optionalDependencies);
    }
204

205 206 207
    @Deprecated
    protected ClassLoader createClassLoader(List<File> paths, ClassLoader parent) throws IOException {
        return createClassLoader( paths, parent, null );
208 209 210 211 212
    }

    /**
     * Creates the classloader that can load all the specified jar files and delegate to the given parent.
     */
213 214 215 216 217 218 219 220 221 222 223
    protected ClassLoader createClassLoader(List<File> paths, ClassLoader parent, Attributes atts) throws IOException {
        if (atts != null) {
            String usePluginFirstClassLoader = atts.getValue( "PluginFirstClassLoader" );
            if (Boolean.valueOf( usePluginFirstClassLoader )) {
                PluginFirstClassLoader classLoader = new PluginFirstClassLoader();
                classLoader.setParentFirst( false );
                classLoader.setParent( parent );
                classLoader.addPathFiles( paths );
                return classLoader;
            }
        }
K
Kohsuke Kawaguchi 已提交
224 225 226 227

        AntClassLoader2 classLoader = new AntClassLoader2(parent);
        classLoader.addPathFiles(paths);
        return classLoader;
L
lacostej 已提交
228
    }
229

230 231 232 233 234
    /**
     * Information about plugins that were originally in the core.
     */
    private static final class DetachedPlugin {
        private final String shortName;
235 236 237 238 239 240 241
        /**
         * Plugins built for this Jenkins version (and earlier) will automatically be assumed to have
         * this plugin in its dependency.
         *
         * When core/pom.xml version is 1.123-SNAPSHOT when the code is removed, then this value should
         * be "1.123.*" (because 1.124 will be the first version that doesn't include the removed code.)
         */
242 243 244 245 246 247 248 249 250 251 252
        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<PluginWrapper.Dependency> optionalDependencies) {
            // don't fix the dependency for yourself, or else we'll have a cycle
253 254
            String yourName = atts.getValue("Short-Name");
            if (shortName.equals(yourName))   return;
255 256

            // some earlier versions of maven-hpi-plugin apparently puts "null" as a literal in Hudson-Version. watch out for them.
K
Kohsuke Kawaguchi 已提交
257 258 259 260
            String jenkinsVersion = atts.getValue("Jenkins-Version");
            if (jenkinsVersion==null)
                jenkinsVersion = atts.getValue("Hudson-Version");
            if (jenkinsVersion == null || jenkinsVersion.equals("null") || new VersionNumber(jenkinsVersion).compareTo(splitWhen) <= 0)
261 262 263 264 265 266
                optionalDependencies.add(new PluginWrapper.Dependency(shortName+':'+requireVersion));
        }
    }

    private static final List<DetachedPlugin> DETACHED_LIST = Arrays.asList(
        new DetachedPlugin("maven-plugin","1.296","1.296"),
K
kohsuke 已提交
267
        new DetachedPlugin("subversion","1.310","1.0"),
268
        new DetachedPlugin("cvs","1.340","0.1"),
269
        new DetachedPlugin("ant","1.430.*","1.0"),
270
        new DetachedPlugin("javadoc","1.430.*","1.0"),
K
Kohsuke Kawaguchi 已提交
271 272
        new DetachedPlugin("external-monitor-job","1.467.*","1.0"),
        new DetachedPlugin("ldap","1.467.*","1.0"),
273
        new DetachedPlugin("pam-auth","1.467.*","1.0"),
J
Jesse Glick 已提交
274
        new DetachedPlugin("mailer","1.493.*","1.2"),
J
Jesse Glick 已提交
275
        new DetachedPlugin("matrix-auth","1.535.*","1.0.2"),
276 277
        new DetachedPlugin("windows-slaves","1.547.*","1.0"),
        new DetachedPlugin("antisamy-markup-formatter","1.553.*","1.0")
278 279 280 281 282 283
    );

    /**
     * Computes the classloader that takes the class masking into account.
     *
     * <p>
K
typo  
Kohsuke Kawaguchi 已提交
284
     * This mechanism allows plugins to have their own versions for libraries that core bundles.
285
     */
286
    private ClassLoader getBaseClassLoader(Attributes atts, ClassLoader base) {
287 288 289 290 291 292 293
        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) {
L
lacostej 已提交
294
    }
295

296
    public <T> List<ExtensionComponent<T>> findComponents(Class<T> type, Hudson hudson) {
S
Stuart McCulloch 已提交
297 298 299 300 301 302 303 304 305 306

        List<ExtensionFinder> finders;
        if (type==ExtensionFinder.class) {
            // Avoid infinite recursion of using ExtensionFinders to find ExtensionFinders
            finders = Collections.<ExtensionFinder>singletonList(new ExtensionFinder.Sezpoz());
        } else {
            finders = hudson.getExtensionList(ExtensionFinder.class);
        }

        /**
307
         * See {@link ExtensionFinder#scout(Class, Hudson)} for the dead lock issue and what this does.
S
Stuart McCulloch 已提交
308 309 310 311 312 313 314
         */
        if (LOGGER.isLoggable(Level.FINER))
            LOGGER.log(Level.FINER,"Scout-loading ExtensionList: "+type, new Throwable());
        for (ExtensionFinder finder : finders) {
            finder.scout(type, hudson);
        }

315
        List<ExtensionComponent<T>> r = Lists.newArrayList();
S
Stuart McCulloch 已提交
316 317 318 319 320 321 322 323 324
        for (ExtensionFinder finder : finders) {
            try {
                r.addAll(finder._find(type, hudson));
            } catch (AbstractMethodError e) {
                // backward compatibility
                for (T t : finder.findExtensions(type, hudson))
                    r.add(new ExtensionComponent<T>(t));
            }
        }
325 326 327 328 329 330 331 332

        List<ExtensionComponent<T>> filtered = Lists.newArrayList();
        for (ExtensionComponent<T> e : r) {
            if (ExtensionFilter.isAllowed(type,e))
                filtered.add(e);
        }

        return filtered;
S
Stuart McCulloch 已提交
333 334
    }

L
lacostej 已提交
335
    public void load(PluginWrapper wrapper) throws IOException {
336 337
        // override the context classloader. This no longer makes sense,
        // but it is left for the backward compatibility
338 339 340
        ClassLoader old = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(wrapper.classLoader);
        try {
K
kohsuke 已提交
341 342 343
            String className = wrapper.getPluginClass();
            if(className==null) {
                // use the default dummy instance
344
                wrapper.setPlugin(new DummyImpl());
K
kohsuke 已提交
345 346
            } else {
                try {
347
                    Class<?> clazz = wrapper.classLoader.loadClass(className);
K
kohsuke 已提交
348 349 350 351 352
                    Object o = clazz.newInstance();
                    if(!(o instanceof Plugin)) {
                        throw new IOException(className+" doesn't extend from hudson.Plugin");
                    }
                    wrapper.setPlugin((Plugin) o);
353
                } catch (LinkageError e) {
354
                    throw new IOException("Unable to load " + className + " from " + wrapper.getShortName(),e);
K
kohsuke 已提交
355
                } catch (ClassNotFoundException e) {
356
                    throw new IOException("Unable to load " + className + " from " + wrapper.getShortName(),e);
K
kohsuke 已提交
357
                } catch (IllegalAccessException e) {
358
                    throw new IOException("Unable to create instance of " + className + " from " + wrapper.getShortName(),e);
K
kohsuke 已提交
359
                } catch (InstantiationException e) {
360
                    throw new IOException("Unable to create instance of " + className + " from " + wrapper.getShortName(),e);
361 362 363 364 365
                }
            }

            // initialize plugin
            try {
L
lacostej 已提交
366
                Plugin plugin = wrapper.getPlugin();
367 368 369 370
                plugin.setServletContext(pluginManager.context);
                startPlugin(wrapper);
            } catch(Throwable t) {
                // gracefully handle any error in plugin.
371
                throw new IOException("Failed to initialize",t);
372 373 374 375
            }
        } finally {
            Thread.currentThread().setContextClassLoader(old);
        }
L
lacostej 已提交
376 377 378 379 380
    }

    public void startPlugin(PluginWrapper plugin) throws Exception {
        plugin.getPlugin().start();
    }
381 382 383 384 385 386 387 388 389

    private static File resolve(File base, String relative) {
        File rel = new File(relative);
        if(rel.isAbsolute())
            return rel;
        else
            return new File(base.getParentFile(),relative);
    }

390
    private static void parseClassPath(Manifest manifest, File archive, List<File> paths, String attributeName, String separator) throws IOException {
391 392 393 394 395 396 397 398 399 400 401
        String classPath = manifest.getMainAttributes().getValue(attributeName);
        if(classPath==null) return; // attribute not found
        for (String s : classPath.split(separator)) {
            File file = resolve(archive, s);
            if(file.getName().contains("*")) {
                // handle wildcard
                FileSet fs = new FileSet();
                File dir = file.getParentFile();
                fs.setDir(dir);
                fs.setIncludes(file.getName());
                for( String included : fs.getDirectoryScanner(new Project()).getIncludedFiles() ) {
402
                    paths.add(new File(dir,included));
403 404 405 406
                }
            } else {
                if(!file.exists())
                    throw new IOException("No such file: "+file);
407
                paths.add(file);
408 409 410 411 412 413 414 415
            }
        }
    }

    /**
     * Explodes the plugin into a directory, if necessary.
     */
    private static void explode(File archive, File destDir) throws IOException {
K
Kohsuke Kawaguchi 已提交
416
        destDir.mkdirs();
417 418

        // timestamp check
K
Kohsuke Kawaguchi 已提交
419
        File explodeTime = new File(destDir,".timestamp2");
420
        if(explodeTime.exists() && explodeTime.lastModified()==archive.lastModified())
421 422 423
            return; // no need to expand

        // delete the contents so that old files won't interfere with new files
424
        Util.deleteRecursive(destDir);
425 426

        try {
K
Kohsuke Kawaguchi 已提交
427 428 429
            Project prj = new Project();
            unzipExceptClasses(archive, destDir, prj);
            createClassJarFromWebInfClasses(archive, destDir, prj);
430
        } catch (BuildException x) {
431
            throw new IOException("Failed to expand " + archive,x);
432 433
        }

434 435 436 437 438
        try {
            new FilePath(explodeTime).touch(archive.lastModified());
        } catch (InterruptedException e) {
            throw new AssertionError(e); // impossible
        }
439 440
    }

K
Kohsuke Kawaguchi 已提交
441 442 443 444
    /**
     * Repackage classes directory into a jar file to make it remoting friendly.
     * The remoting layer can cache jar files but not class files.
     */
445
    private static void createClassJarFromWebInfClasses(File archive, File destDir, Project prj) throws IOException {
K
Kohsuke Kawaguchi 已提交
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461
        File classesJar = new File(destDir, "WEB-INF/lib/classes.jar");

        ZipFileSet zfs = new ZipFileSet();
        zfs.setProject(prj);
        zfs.setSrc(archive);
        zfs.setIncludes("WEB-INF/classes/");

        MappedResourceCollection mapper = new MappedResourceCollection();
        mapper.add(zfs);

        GlobPatternMapper gm = new GlobPatternMapper();
        gm.setFrom("WEB-INF/classes/*");
        gm.setTo("*");
        mapper.add(gm);

        final long dirTime = archive.lastModified();
462 463 464 465 466 467
        // this ZipOutputStream is reused and not created for each directory
        final ZipOutputStream wrappedZOut = new ZipOutputStream(new NullOutputStream()) {
            @Override
            public void putNextEntry(ZipEntry ze) throws IOException {
                ze.setTime(dirTime+1999);   // roundup
                super.putNextEntry(ze);
K
Kohsuke Kawaguchi 已提交
468 469
            }
        };
470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491
        try {
            Zip z = new Zip() {
                /**
                 * Forces the fixed timestamp for directories to make sure
                 * classes.jar always get a consistent checksum.
                 */
                protected void zipDir(Resource dir, ZipOutputStream zOut, String vPath,
                                      int mode, ZipExtraField[] extra)
                    throws IOException {
                    // use wrappedZOut instead of zOut
                    super.zipDir(dir,wrappedZOut,vPath,mode,extra);
                }
            };
            z.setProject(prj);
            z.setTaskType("zip");
            classesJar.getParentFile().mkdirs();
            z.setDestFile(classesJar);
            z.add(mapper);
            z.execute();
        } finally {
            wrappedZOut.close();
        }
K
Kohsuke Kawaguchi 已提交
492 493 494 495 496 497 498 499 500 501 502 503 504 505
    }

    private static void unzipExceptClasses(File archive, File destDir, Project prj) {
        Expand e = new Expand();
        e.setProject(prj);
        e.setTaskType("unzip");
        e.setSrc(archive);
        e.setDest(destDir);
        PatternSet p = new PatternSet();
        p.setExcludes("WEB-INF/classes/");
        e.addPatternset(p);
        e.execute();
    }

506 507 508 509
    /**
     * Used to load classes from dependency plugins.
     */
    final class DependencyClassLoader extends ClassLoader {
510 511 512 513 514
        /**
         * This classloader is created for this plugin. Useful during debugging.
         */
        private final File _for;

L
lacostej 已提交
515
        private List<Dependency> dependencies;
516

517 518 519 520 521
        /**
         * Topologically sorted list of transient dependencies.
         */
        private volatile List<PluginWrapper> transientDependencies;

522
        public DependencyClassLoader(ClassLoader parent, File archive, List<Dependency> dependencies) {
523
            super(parent);
524
            this._for = archive;
K
Kohsuke Kawaguchi 已提交
525
            this.dependencies = dependencies;
526 527
        }

528 529 530 531 532 533 534 535
        private List<PluginWrapper> getTransitiveDependencies() {
            if (transientDependencies==null) {
                CyclicGraphDetector<PluginWrapper> cgd = new CyclicGraphDetector<PluginWrapper>() {
                    @Override
                    protected List<PluginWrapper> getEdges(PluginWrapper pw) {
                        List<PluginWrapper> dep = new ArrayList<PluginWrapper>();
                        for (Dependency d : pw.getDependencies()) {
                            PluginWrapper p = pluginManager.getPlugin(d.shortName);
536
                            if (p!=null && p.isActive())
537 538 539 540 541 542 543 544 545
                                dep.add(p);
                        }
                        return dep;
                    }
                };

                try {
                    for (Dependency d : dependencies) {
                        PluginWrapper p = pluginManager.getPlugin(d.shortName);
546
                        if (p!=null && p.isActive())
547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566
                            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<PluginWrapper> getDependencyPluginWrappers() {
//            List<PluginWrapper> r = new ArrayList<PluginWrapper>();
//            for (Dependency d : dependencies) {
//                PluginWrapper w = pluginManager.getPlugin(d.shortName);
//                if (w!=null)    r.add(w);
//            }
//            return r;
//        }

567
        @Override
568
        protected Class<?> findClass(String name) throws ClassNotFoundException {
569 570
            if (PluginManager.FAST_LOOKUP) {
                for (PluginWrapper pw : getTransitiveDependencies()) {
571
                    try {
572 573 574 575 576
                        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
577
                    }
578 579 580 581 582 583 584 585 586 587 588
                }
            } else {
                for (Dependency dep : dependencies) {
                    PluginWrapper p = pluginManager.getPlugin(dep.shortName);
                    if(p!=null)
                        try {
                            return p.classLoader.loadClass(name);
                        } catch (ClassNotFoundException _) {
                            // try next
                        }
                }
589 590 591 592 593
            }

            throw new ClassNotFoundException(name);
        }

594 595 596
        @Override
        protected Enumeration<URL> findResources(String name) throws IOException {
            HashSet<URL> result = new HashSet<URL>();
597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615

            if (PluginManager.FAST_LOOKUP) {
                try {
                    for (PluginWrapper pw : getTransitiveDependencies()) {
                        Enumeration<URL> 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<URL> urls = p.classLoader.getResources(name);
                        while (urls != null && urls.hasMoreElements())
                            result.add(urls.nextElement());
                    }
616 617 618 619 620
                }
            }

            return Collections.enumeration(result);
        }
621 622 623

        @Override
        protected URL findResource(String name) {
624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
            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;
                    }
641 642 643 644 645
                }
            }

            return null;
        }
646
    }
647 648 649

    /**
     * {@link AntClassLoader} with a few methods exposed and {@link Closeable} support.
650
     * Deprecated as of Java 7, retained only for Java 6.
651
     */
K
Kohsuke Kawaguchi 已提交
652
    private final class AntClassLoader2 extends AntClassLoader implements Closeable {
K
Kohsuke Kawaguchi 已提交
653 654
        private final Vector pathComponents;

655 656
        private AntClassLoader2(ClassLoader parent) {
            super(parent,true);
K
Kohsuke Kawaguchi 已提交
657 658 659 660 661 662 663 664 665 666

            try {
                Field $pathComponents = AntClassLoader.class.getDeclaredField("pathComponents");
                $pathComponents.setAccessible(true);
                pathComponents = (Vector)$pathComponents.get(this);
            } catch (NoSuchFieldException e) {
                throw new Error(e);
            } catch (IllegalAccessException e) {
                throw new Error(e);
            }
667 668
        }

K
Kohsuke Kawaguchi 已提交
669

670 671 672 673 674 675 676 677
        public void addPathFiles(Collection<File> paths) throws IOException {
            for (File f : paths)
                addPathFile(f);
        }

        public void close() throws IOException {
            cleanup();
        }
K
Kohsuke Kawaguchi 已提交
678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699

        /**
         * As of 1.8.0, {@link AntClassLoader} doesn't implement {@link #findResource(String)}
         * in any meaningful way, which breaks fast lookup. Implement it properly.
         */
        @Override
        protected URL findResource(String name) {
            URL url = null;

            // try and load from this loader if the parent either didn't find
            // it or wasn't consulted.
            Enumeration e = pathComponents.elements();
            while (e.hasMoreElements() && url == null) {
                File pathComponent = (File) e.nextElement();
                url = getResourceURL(pathComponent, name);
                if (url != null) {
                    log("Resource " + name + " loaded from ant loader", Project.MSG_DEBUG);
                }
            }

            return url;
        }
K
Kohsuke Kawaguchi 已提交
700 701 702

        @Override
        protected Class defineClassFromData(File container, byte[] classData, String classname) throws IOException {
K
Kohsuke Kawaguchi 已提交
703 704 705
            if (!DISABLE_TRANSFORMER)
                classData = pluginManager.getCompatibilityTransformer().transform(classname, classData);
            return super.defineClassFromData(container, classData, classname);
K
Kohsuke Kawaguchi 已提交
706
        }
707
    }
K
kohsuke 已提交
708 709

    public static boolean useAntClassLoader = Boolean.getBoolean(ClassicPluginStrategy.class.getName()+".useAntClassLoader");
710
    private static final Logger LOGGER = Logger.getLogger(ClassicPluginStrategy.class.getName());
K
Kohsuke Kawaguchi 已提交
711
    public static boolean DISABLE_TRANSFORMER = Boolean.getBoolean(ClassicPluginStrategy.class.getName()+".noBytecodeTransformer");
712
}