ClassicPluginStrategy.java 30.4 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.JarFile;
70
import java.util.jar.Manifest;
S
Stuart McCulloch 已提交
71
import java.util.logging.Level;
72
import java.util.logging.Logger;
73
import org.jenkinsci.bytecode.Transformer;
74 75 76 77 78 79 80 81 82 83 84 85 86 87

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;

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
    public ClassicPluginStrategy(PluginManager pluginManager) {
        this.pluginManager = pluginManager;
    }

97 98 99 100 101 102 103 104 105 106 107 108 109 110
    @Override public String getShortName(File archive) throws IOException {
        Manifest manifest;
        if (isLinked(archive)) {
            manifest = loadLinkedManifest(archive);
        } else {
            JarFile jf = new JarFile(archive, false);
            try {
                manifest = jf.getManifest();
            } finally {
                jf.close();
            }
        }
        return PluginWrapper.computeShortName(manifest, archive);
    }
111

112 113 114
    private static boolean isLinked(File archive) {
        return archive.getName().endsWith(".hpl") || archive.getName().endsWith(".jpl");
    }
115

116
    private static Manifest loadLinkedManifest(File archive) throws IOException {
L
lacostej 已提交
117
            // resolve the .hpl file to the location of the manifest file
118
            final String firstLine = IOUtils.readFirstLine(new FileInputStream(archive), "UTF-8");
L
lacostej 已提交
119 120 121 122 123 124 125 126 127
            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 {
128
                return new Manifest(in);
L
lacostej 已提交
129
            } catch (IOException e) {
130
                throw new IOException("Failed to load " + archive, e);
L
lacostej 已提交
131 132 133
            } finally {
                in.close();
            }
134 135 136 137 138 139 140 141 142 143 144 145
    }

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

        File expandDir = null;
        // if .hpi, this is the directory where war is expanded

        boolean isLinked = isLinked(archive);
        if (isLinked) {
            manifest = loadLinkedManifest(archive);
L
lacostej 已提交
146
        } else {
K
kohsuke 已提交
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
            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 已提交
166
        }
167

168 169
        final Attributes atts = manifest.getMainAttributes();

L
lacostej 已提交
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
        // 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);
185
            if (libs != null)
186
                paths.addAll(Arrays.asList(libs));
187

L
lacostej 已提交
188 189
            baseResourceURL = expandDir.toURI().toURL();
        }
190 191 192 193
        File disableFile = new File(archive.getPath() + ".disabled");
        if (disableFile.exists()) {
            LOGGER.info("Plugin " + archive.getName() + " is disabled");
        }
194

L
lacostej 已提交
195 196 197 198 199 200 201 202 203 204 205 206 207 208
        // 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);
                }
            }
        }
209 210
        for (DetachedPlugin detached : DETACHED_LIST)
            detached.fix(atts,optionalDependencies);
211

212 213 214 215 216 217 218 219 220 221
        // 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));
222
        dependencyLoader = getBaseClassLoader(atts, dependencyLoader);
223

224
        return new PluginWrapper(pluginManager, archive, manifest, baseResourceURL,
225 226
                createClassLoader(paths, dependencyLoader, atts), disableFile, dependencies, optionalDependencies);
    }
227

228 229 230
    @Deprecated
    protected ClassLoader createClassLoader(List<File> paths, ClassLoader parent) throws IOException {
        return createClassLoader( paths, parent, null );
231 232 233 234 235
    }

    /**
     * Creates the classloader that can load all the specified jar files and delegate to the given parent.
     */
236 237 238 239 240 241 242 243 244 245 246
    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 已提交
247 248 249 250

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

253 254 255 256 257
    /**
     * Information about plugins that were originally in the core.
     */
    private static final class DetachedPlugin {
        private final String shortName;
258 259 260 261 262 263 264
        /**
         * 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.)
         */
265 266 267 268 269 270 271 272 273 274 275
        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
276 277
            String yourName = atts.getValue("Short-Name");
            if (shortName.equals(yourName))   return;
278 279

            // some earlier versions of maven-hpi-plugin apparently puts "null" as a literal in Hudson-Version. watch out for them.
K
Kohsuke Kawaguchi 已提交
280 281 282 283
            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)
284 285 286 287 288 289
                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 已提交
290
        new DetachedPlugin("subversion","1.310","1.0"),
291
        new DetachedPlugin("cvs","1.340","0.1"),
292
        new DetachedPlugin("ant","1.430.*","1.0"),
293
        new DetachedPlugin("javadoc","1.430.*","1.0"),
K
Kohsuke Kawaguchi 已提交
294 295
        new DetachedPlugin("external-monitor-job","1.467.*","1.0"),
        new DetachedPlugin("ldap","1.467.*","1.0"),
296
        new DetachedPlugin("pam-auth","1.467.*","1.0"),
J
Jesse Glick 已提交
297
        new DetachedPlugin("mailer","1.493.*","1.2"),
J
Jesse Glick 已提交
298
        new DetachedPlugin("matrix-auth","1.535.*","1.0.2"),
299
        new DetachedPlugin("windows-slaves","1.547.*","1.0"),
J
Jesse Glick 已提交
300
        new DetachedPlugin("antisamy-markup-formatter","1.553.*","1.0"),
301 302
        new DetachedPlugin("matrix-project","1.561.*","1.0"),
        new DetachedPlugin("junit","1.577.*","1.0")
303 304 305 306 307 308
    );

    /**
     * Computes the classloader that takes the class masking into account.
     *
     * <p>
K
typo  
Kohsuke Kawaguchi 已提交
309
     * This mechanism allows plugins to have their own versions for libraries that core bundles.
310
     */
311
    private ClassLoader getBaseClassLoader(Attributes atts, ClassLoader base) {
312 313 314 315 316 317 318
        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 已提交
319
    }
320

321
    public <T> List<ExtensionComponent<T>> findComponents(Class<T> type, Hudson hudson) {
S
Stuart McCulloch 已提交
322 323 324 325 326 327 328 329 330 331

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

        /**
332
         * See {@link ExtensionFinder#scout(Class, Hudson)} for the dead lock issue and what this does.
S
Stuart McCulloch 已提交
333 334 335 336 337 338 339
         */
        if (LOGGER.isLoggable(Level.FINER))
            LOGGER.log(Level.FINER,"Scout-loading ExtensionList: "+type, new Throwable());
        for (ExtensionFinder finder : finders) {
            finder.scout(type, hudson);
        }

340
        List<ExtensionComponent<T>> r = Lists.newArrayList();
S
Stuart McCulloch 已提交
341 342
        for (ExtensionFinder finder : finders) {
            try {
343
                r.addAll(finder.find(type, hudson));
S
Stuart McCulloch 已提交
344 345 346 347 348 349
            } catch (AbstractMethodError e) {
                // backward compatibility
                for (T t : finder.findExtensions(type, hudson))
                    r.add(new ExtensionComponent<T>(t));
            }
        }
350 351 352 353 354 355 356 357

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

        return filtered;
S
Stuart McCulloch 已提交
358 359
    }

L
lacostej 已提交
360
    public void load(PluginWrapper wrapper) throws IOException {
361 362
        // override the context classloader. This no longer makes sense,
        // but it is left for the backward compatibility
363 364 365
        ClassLoader old = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(wrapper.classLoader);
        try {
K
kohsuke 已提交
366 367 368
            String className = wrapper.getPluginClass();
            if(className==null) {
                // use the default dummy instance
369
                wrapper.setPlugin(new DummyImpl());
K
kohsuke 已提交
370 371
            } else {
                try {
372
                    Class<?> clazz = wrapper.classLoader.loadClass(className);
K
kohsuke 已提交
373 374 375 376 377
                    Object o = clazz.newInstance();
                    if(!(o instanceof Plugin)) {
                        throw new IOException(className+" doesn't extend from hudson.Plugin");
                    }
                    wrapper.setPlugin((Plugin) o);
378
                } catch (LinkageError e) {
379
                    throw new IOException("Unable to load " + className + " from " + wrapper.getShortName(),e);
K
kohsuke 已提交
380
                } catch (ClassNotFoundException e) {
381
                    throw new IOException("Unable to load " + className + " from " + wrapper.getShortName(),e);
K
kohsuke 已提交
382
                } catch (IllegalAccessException e) {
383
                    throw new IOException("Unable to create instance of " + className + " from " + wrapper.getShortName(),e);
K
kohsuke 已提交
384
                } catch (InstantiationException e) {
385
                    throw new IOException("Unable to create instance of " + className + " from " + wrapper.getShortName(),e);
386 387 388 389 390
                }
            }

            // initialize plugin
            try {
L
lacostej 已提交
391
                Plugin plugin = wrapper.getPlugin();
392 393 394 395
                plugin.setServletContext(pluginManager.context);
                startPlugin(wrapper);
            } catch(Throwable t) {
                // gracefully handle any error in plugin.
396
                throw new IOException("Failed to initialize",t);
397 398 399 400
            }
        } finally {
            Thread.currentThread().setContextClassLoader(old);
        }
L
lacostej 已提交
401 402 403 404 405
    }

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

407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436
    @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;
    }

437 438 439 440 441 442 443 444
    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);
    }

445
    private static void parseClassPath(Manifest manifest, File archive, List<File> paths, String attributeName, String separator) throws IOException {
446 447 448 449 450 451 452 453 454 455 456
        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() ) {
457
                    paths.add(new File(dir,included));
458 459 460 461
                }
            } else {
                if(!file.exists())
                    throw new IOException("No such file: "+file);
462
                paths.add(file);
463 464 465 466 467 468 469 470
            }
        }
    }

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

        // timestamp check
K
Kohsuke Kawaguchi 已提交
474
        File explodeTime = new File(destDir,".timestamp2");
475
        if(explodeTime.exists() && explodeTime.lastModified()==archive.lastModified())
476 477 478
            return; // no need to expand

        // delete the contents so that old files won't interfere with new files
479
        Util.deleteRecursive(destDir);
480 481

        try {
K
Kohsuke Kawaguchi 已提交
482 483 484
            Project prj = new Project();
            unzipExceptClasses(archive, destDir, prj);
            createClassJarFromWebInfClasses(archive, destDir, prj);
485
        } catch (BuildException x) {
486
            throw new IOException("Failed to expand " + archive,x);
487 488
        }

489 490 491 492 493
        try {
            new FilePath(explodeTime).touch(archive.lastModified());
        } catch (InterruptedException e) {
            throw new AssertionError(e); // impossible
        }
494 495
    }

K
Kohsuke Kawaguchi 已提交
496 497 498 499
    /**
     * Repackage classes directory into a jar file to make it remoting friendly.
     * The remoting layer can cache jar files but not class files.
     */
500
    private static void createClassJarFromWebInfClasses(File archive, File destDir, Project prj) throws IOException {
K
Kohsuke Kawaguchi 已提交
501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516
        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();
517 518 519 520 521 522
        // 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 已提交
523 524
            }
        };
525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546
        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 已提交
547 548 549 550 551 552 553 554 555 556 557 558 559 560
    }

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

561 562 563 564
    /**
     * Used to load classes from dependency plugins.
     */
    final class DependencyClassLoader extends ClassLoader {
565 566 567 568 569
        /**
         * This classloader is created for this plugin. Useful during debugging.
         */
        private final File _for;

L
lacostej 已提交
570
        private List<Dependency> dependencies;
571

572 573 574 575 576
        /**
         * Topologically sorted list of transient dependencies.
         */
        private volatile List<PluginWrapper> transientDependencies;

577
        public DependencyClassLoader(ClassLoader parent, File archive, List<Dependency> dependencies) {
578
            super(parent);
579
            this._for = archive;
K
Kohsuke Kawaguchi 已提交
580
            this.dependencies = dependencies;
581 582
        }

583 584 585 586 587
        private void updateTransientDependencies() {
            // This will be recalculated at the next time.
            transientDependencies = null;
        }

588 589 590 591 592 593 594 595
        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);
596
                            if (p!=null && p.isActive())
597 598 599 600 601 602 603 604 605
                                dep.add(p);
                        }
                        return dep;
                    }
                };

                try {
                    for (Dependency d : dependencies) {
                        PluginWrapper p = pluginManager.getPlugin(d.shortName);
606
                        if (p!=null && p.isActive())
607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626
                            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;
//        }

627
        @Override
628
        protected Class<?> findClass(String name) throws ClassNotFoundException {
629 630
            if (PluginManager.FAST_LOOKUP) {
                for (PluginWrapper pw : getTransitiveDependencies()) {
631
                    try {
632
                        Class<?> c = ClassLoaderReflectionToolkit._findLoadedClass(pw.classLoader, name);
633
                        if (c!=null)    return c;
634 635
                        return ClassLoaderReflectionToolkit._findClass(pw.classLoader, name);
                    } catch (ClassNotFoundException e) {
636
                        //not found. try next
637
                    }
638 639 640 641 642 643 644 645 646 647 648
                }
            } else {
                for (Dependency dep : dependencies) {
                    PluginWrapper p = pluginManager.getPlugin(dep.shortName);
                    if(p!=null)
                        try {
                            return p.classLoader.loadClass(name);
                        } catch (ClassNotFoundException _) {
                            // try next
                        }
                }
649 650 651 652 653
            }

            throw new ClassNotFoundException(name);
        }

654 655 656
        @Override
        protected Enumeration<URL> findResources(String name) throws IOException {
            HashSet<URL> result = new HashSet<URL>();
657 658 659

            if (PluginManager.FAST_LOOKUP) {
                    for (PluginWrapper pw : getTransitiveDependencies()) {
660
                        Enumeration<URL> urls = ClassLoaderReflectionToolkit._findResources(pw.classLoader, name);
661 662 663 664 665 666 667 668 669 670 671
                        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());
                    }
672 673 674 675 676
                }
            }

            return Collections.enumeration(result);
        }
677 678 679

        @Override
        protected URL findResource(String name) {
680 681
            if (PluginManager.FAST_LOOKUP) {
                    for (PluginWrapper pw : getTransitiveDependencies()) {
682
                        URL url = ClassLoaderReflectionToolkit._findResource(pw.classLoader, name);
683 684 685 686 687 688 689 690 691 692
                        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;
                    }
693 694 695 696 697
                }
            }

            return null;
        }
698
    }
699 700

    /**
701
     * {@link AntClassLoader} with a few methods exposed, {@link Closeable} support, and {@link Transformer} support.
702
     */
K
Kohsuke Kawaguchi 已提交
703
    private final class AntClassLoader2 extends AntClassLoader implements Closeable {
K
Kohsuke Kawaguchi 已提交
704 705
        private final Vector pathComponents;

706 707
        private AntClassLoader2(ClassLoader parent) {
            super(parent,true);
K
Kohsuke Kawaguchi 已提交
708 709 710 711 712 713 714 715 716 717

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

K
Kohsuke Kawaguchi 已提交
720

721 722 723 724 725 726 727 728
        public void addPathFiles(Collection<File> paths) throws IOException {
            for (File f : paths)
                addPathFile(f);
        }

        public void close() throws IOException {
            cleanup();
        }
K
Kohsuke Kawaguchi 已提交
729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750

        /**
         * 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 已提交
751 752 753

        @Override
        protected Class defineClassFromData(File container, byte[] classData, String classname) throws IOException {
K
Kohsuke Kawaguchi 已提交
754 755 756
            if (!DISABLE_TRANSFORMER)
                classData = pluginManager.getCompatibilityTransformer().transform(classname, classData);
            return super.defineClassFromData(container, classData, classname);
K
Kohsuke Kawaguchi 已提交
757
        }
758
    }
K
kohsuke 已提交
759 760

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