ClassicPluginStrategy.java 29.6 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
import jenkins.util.AntClassLoader;
31 32
import hudson.util.CyclicGraphDetector;
import hudson.util.CyclicGraphDetector.CycleDetectedException;
33
import hudson.util.IOUtils;
34 35
import hudson.util.MaskingClassLoader;
import hudson.util.VersionNumber;
36
import jenkins.ClassLoaderReflectionToolkit;
37
import jenkins.ExtensionFilter;
K
Kohsuke Kawaguchi 已提交
38
import org.apache.commons.io.output.NullOutputStream;
39 40 41
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 60
import java.net.URL;
import java.util.ArrayList;
61 62
import java.util.Arrays;
import java.util.Collection;
63 64 65 66
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
K
Kohsuke Kawaguchi 已提交
67
import java.util.Vector;
68
import java.util.jar.Attributes;
69
import java.util.jar.Manifest;
S
Stuart McCulloch 已提交
70
import java.util.logging.Level;
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
import java.util.logging.Logger;

public class ClassicPluginStrategy implements PluginStrategy {

    /**
     * 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;

86 87 88 89 90
    /**
     * 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 已提交
91 92 93 94 95 96 97
    public ClassicPluginStrategy(PluginManager pluginManager) {
        this.pluginManager = pluginManager;
    }

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

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

I
imod 已提交
102
        boolean isLinked = archive.getName().endsWith(".hpl") || archive.getName().endsWith(".jpl");
L
lacostej 已提交
103 104
        if (isLinked) {
            // resolve the .hpl file to the location of the manifest file
105
            final String firstLine = IOUtils.readFirstLine(new FileInputStream(archive), "UTF-8");
L
lacostej 已提交
106 107 108 109 110 111 112 113 114 115 116
            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) {
117
                throw new IOException("Failed to load " + archive, e);
L
lacostej 已提交
118 119 120 121
            } finally {
                in.close();
            }
        } else {
K
kohsuke 已提交
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
            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 已提交
141
        }
142

143 144
        final Attributes atts = manifest.getMainAttributes();

L
lacostej 已提交
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
        // 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);
160
            if (libs != null)
161
                paths.addAll(Arrays.asList(libs));
162

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

L
lacostej 已提交
170 171 172 173 174 175 176 177 178 179 180 181 182 183
        // 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);
                }
            }
        }
184 185
        for (DetachedPlugin detached : DETACHED_LIST)
            detached.fix(atts,optionalDependencies);
186

187 188 189 190 191 192 193 194 195 196
        // 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));
197
        dependencyLoader = getBaseClassLoader(atts, dependencyLoader);
198

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

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

    /**
     * Creates the classloader that can load all the specified jar files and delegate to the given parent.
     */
211 212 213 214 215 216 217 218 219 220 221
    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 已提交
222 223 224 225

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

228 229 230 231 232
    /**
     * Information about plugins that were originally in the core.
     */
    private static final class DetachedPlugin {
        private final String shortName;
233 234 235 236 237 238 239
        /**
         * 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.)
         */
240 241 242 243 244 245 246 247 248 249 250
        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
251 252
            String yourName = atts.getValue("Short-Name");
            if (shortName.equals(yourName))   return;
253 254

            // some earlier versions of maven-hpi-plugin apparently puts "null" as a literal in Hudson-Version. watch out for them.
K
Kohsuke Kawaguchi 已提交
255 256 257 258
            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)
259 260 261 262 263 264
                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 已提交
265
        new DetachedPlugin("subversion","1.310","1.0"),
266
        new DetachedPlugin("cvs","1.340","0.1"),
267
        new DetachedPlugin("ant","1.430.*","1.0"),
268
        new DetachedPlugin("javadoc","1.430.*","1.0"),
K
Kohsuke Kawaguchi 已提交
269 270
        new DetachedPlugin("external-monitor-job","1.467.*","1.0"),
        new DetachedPlugin("ldap","1.467.*","1.0"),
271
        new DetachedPlugin("pam-auth","1.467.*","1.0"),
J
Jesse Glick 已提交
272
        new DetachedPlugin("mailer","1.493.*","1.2"),
J
Jesse Glick 已提交
273
        new DetachedPlugin("matrix-auth","1.535.*","1.0.2"),
274
        new DetachedPlugin("windows-slaves","1.547.*","1.0"),
J
Jesse Glick 已提交
275
        new DetachedPlugin("antisamy-markup-formatter","1.553.*","1.0"),
276
        new DetachedPlugin("matrix-project","1.561.*","1.0-beta-1")
277 278 279 280 281 282
    );

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

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

        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);
        }

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

314
        List<ExtensionComponent<T>> r = Lists.newArrayList();
S
Stuart McCulloch 已提交
315 316 317 318 319 320 321 322 323
        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));
            }
        }
324 325 326 327 328 329 330 331

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

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

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

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

    public void startPlugin(PluginWrapper plugin) throws Exception {
        plugin.getPlugin().start();
    }
380

381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410
    @Override
    public void updateDependency(PluginWrapper depender, PluginWrapper dependee) {
        DependencyClassLoader classLoader = findAncestorDependencyClassLoader(depender.classLoader);
        if (classLoader != null) {
            classLoader.updateTransientDependencies();
            LOGGER.log(Level.INFO, "Updated dependency of {0}", depender.getShortName());
        }
    }

    private DependencyClassLoader findAncestorDependencyClassLoader(ClassLoader classLoader)
    {
        for (; classLoader != null; classLoader = classLoader.getParent()) {
            if (classLoader instanceof DependencyClassLoader) {
                return (DependencyClassLoader)classLoader;
            }
            
            if (classLoader instanceof AntClassLoader) {
                // AntClassLoaders hold parents not only as AntClassLoader#getParent()
                // but also as AntClassLoader#getConfiguredParent()
                DependencyClassLoader ret = findAncestorDependencyClassLoader(
                        ((AntClassLoader)classLoader).getConfiguredParent()
                );
                if (ret != null) {
                    return ret;
                }
            }
        }
        return null;
    }

411 412 413 414 415 416 417 418
    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);
    }

419
    private static void parseClassPath(Manifest manifest, File archive, List<File> paths, String attributeName, String separator) throws IOException {
420 421 422 423 424 425 426 427 428 429 430
        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() ) {
431
                    paths.add(new File(dir,included));
432 433 434 435
                }
            } else {
                if(!file.exists())
                    throw new IOException("No such file: "+file);
436
                paths.add(file);
437 438 439 440 441 442 443 444
            }
        }
    }

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

        // timestamp check
K
Kohsuke Kawaguchi 已提交
448
        File explodeTime = new File(destDir,".timestamp2");
449
        if(explodeTime.exists() && explodeTime.lastModified()==archive.lastModified())
450 451 452
            return; // no need to expand

        // delete the contents so that old files won't interfere with new files
453
        Util.deleteRecursive(destDir);
454 455

        try {
K
Kohsuke Kawaguchi 已提交
456 457 458
            Project prj = new Project();
            unzipExceptClasses(archive, destDir, prj);
            createClassJarFromWebInfClasses(archive, destDir, prj);
459
        } catch (BuildException x) {
460
            throw new IOException("Failed to expand " + archive,x);
461 462
        }

463 464 465 466 467
        try {
            new FilePath(explodeTime).touch(archive.lastModified());
        } catch (InterruptedException e) {
            throw new AssertionError(e); // impossible
        }
468 469
    }

K
Kohsuke Kawaguchi 已提交
470 471 472 473
    /**
     * Repackage classes directory into a jar file to make it remoting friendly.
     * The remoting layer can cache jar files but not class files.
     */
474
    private static void createClassJarFromWebInfClasses(File archive, File destDir, Project prj) throws IOException {
K
Kohsuke Kawaguchi 已提交
475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490
        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();
491 492 493 494 495 496
        // 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 已提交
497 498
            }
        };
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
        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 已提交
521 522 523 524 525 526 527 528 529 530 531 532 533 534
    }

    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();
    }

535 536 537 538
    /**
     * Used to load classes from dependency plugins.
     */
    final class DependencyClassLoader extends ClassLoader {
539 540 541 542 543
        /**
         * This classloader is created for this plugin. Useful during debugging.
         */
        private final File _for;

L
lacostej 已提交
544
        private List<Dependency> dependencies;
545

546 547 548 549 550
        /**
         * Topologically sorted list of transient dependencies.
         */
        private volatile List<PluginWrapper> transientDependencies;

551
        public DependencyClassLoader(ClassLoader parent, File archive, List<Dependency> dependencies) {
552
            super(parent);
553
            this._for = archive;
K
Kohsuke Kawaguchi 已提交
554
            this.dependencies = dependencies;
555 556
        }

557 558 559 560 561
        private void updateTransientDependencies() {
            // This will be recalculated at the next time.
            transientDependencies = null;
        }

562 563 564 565 566 567 568 569
        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);
570
                            if (p!=null && p.isActive())
571 572 573 574 575 576 577 578 579
                                dep.add(p);
                        }
                        return dep;
                    }
                };

                try {
                    for (Dependency d : dependencies) {
                        PluginWrapper p = pluginManager.getPlugin(d.shortName);
580
                        if (p!=null && p.isActive())
581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600
                            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;
//        }

601
        @Override
602
        protected Class<?> findClass(String name) throws ClassNotFoundException {
603 604
            if (PluginManager.FAST_LOOKUP) {
                for (PluginWrapper pw : getTransitiveDependencies()) {
605
                    try {
606
                        Class<?> c = ClassLoaderReflectionToolkit._findLoadedClass(pw.classLoader, name);
607
                        if (c!=null)    return c;
608 609
                        return ClassLoaderReflectionToolkit._findClass(pw.classLoader, name);
                    } catch (ClassNotFoundException e) {
610
                        //not found. try next
611
                    }
612 613 614 615 616 617 618 619 620 621 622
                }
            } else {
                for (Dependency dep : dependencies) {
                    PluginWrapper p = pluginManager.getPlugin(dep.shortName);
                    if(p!=null)
                        try {
                            return p.classLoader.loadClass(name);
                        } catch (ClassNotFoundException _) {
                            // try next
                        }
                }
623 624 625 626 627
            }

            throw new ClassNotFoundException(name);
        }

628 629 630
        @Override
        protected Enumeration<URL> findResources(String name) throws IOException {
            HashSet<URL> result = new HashSet<URL>();
631 632 633

            if (PluginManager.FAST_LOOKUP) {
                    for (PluginWrapper pw : getTransitiveDependencies()) {
634
                        Enumeration<URL> urls = ClassLoaderReflectionToolkit._findResources(pw.classLoader, name);
635 636 637 638 639 640 641 642 643 644 645
                        while (urls != null && urls.hasMoreElements())
                            result.add(urls.nextElement());
                    }
            } 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());
                    }
646 647 648 649 650
                }
            }

            return Collections.enumeration(result);
        }
651 652 653

        @Override
        protected URL findResource(String name) {
654 655
            if (PluginManager.FAST_LOOKUP) {
                    for (PluginWrapper pw : getTransitiveDependencies()) {
656
                        URL url = ClassLoaderReflectionToolkit._findResource(pw.classLoader, name);
657 658 659 660 661 662 663 664 665 666
                        if (url!=null)    return url;
                    }
            } 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;
                    }
667 668 669 670 671
                }
            }

            return null;
        }
672
    }
673 674 675

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

681 682
        private AntClassLoader2(ClassLoader parent) {
            super(parent,true);
K
Kohsuke Kawaguchi 已提交
683 684 685 686 687 688 689 690 691 692

            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);
            }
693 694
        }

K
Kohsuke Kawaguchi 已提交
695

696 697 698 699 700 701 702 703
        public void addPathFiles(Collection<File> paths) throws IOException {
            for (File f : paths)
                addPathFile(f);
        }

        public void close() throws IOException {
            cleanup();
        }
K
Kohsuke Kawaguchi 已提交
704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725

        /**
         * 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 已提交
726 727 728

        @Override
        protected Class defineClassFromData(File container, byte[] classData, String classname) throws IOException {
K
Kohsuke Kawaguchi 已提交
729 730 731
            if (!DISABLE_TRANSFORMER)
                classData = pluginManager.getCompatibilityTransformer().transform(classname, classData);
            return super.defineClassFromData(container, classData, classname);
K
Kohsuke Kawaguchi 已提交
732
        }
733
    }
K
kohsuke 已提交
734 735

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