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

K
kohsuke 已提交
26 27 28
import static hudson.init.InitMilestone.PLUGINS_PREPARED;
import static hudson.init.InitMilestone.PLUGINS_STARTED;
import static hudson.init.InitMilestone.PLUGINS_LISTED;
29

K
Kohsuke Kawaguchi 已提交
30
import com.google.inject.Guice;
31
import hudson.PluginWrapper.Dependency;
K
kohsuke 已提交
32
import hudson.init.InitStrategy;
33
import hudson.init.InitializerFinder;
34 35 36 37
import hudson.model.AbstractModelObject;
import hudson.model.Failure;
import hudson.model.Hudson;
import hudson.model.UpdateCenter;
K
kohsuke 已提交
38
import hudson.model.UpdateSite;
39 40
import hudson.util.CyclicGraphDetector;
import hudson.util.CyclicGraphDetector.CycleDetectedException;
41
import hudson.util.PersistedList;
K
kohsuke 已提交
42
import hudson.util.Service;
43 44 45 46 47
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.LogFactory;
K
kohsuke 已提交
48 49 50 51
import org.jvnet.hudson.reactor.Executable;
import org.jvnet.hudson.reactor.Reactor;
import org.jvnet.hudson.reactor.TaskBuilder;
import org.jvnet.hudson.reactor.TaskGraphBuilder;
52 53
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
54
import org.kohsuke.stapler.HttpResponses;
55 56 57
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
K
kohsuke 已提交
58 59

import javax.servlet.ServletContext;
60
import javax.servlet.ServletException;
K
kohsuke 已提交
61 62
import java.io.File;
import java.io.IOException;
63
import java.lang.ref.WeakReference;
K
kohsuke 已提交
64 65 66
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
67
import java.util.Collections;
68
import java.util.Enumeration;
K
kohsuke 已提交
69
import java.util.HashSet;
70
import java.util.Hashtable;
K
kohsuke 已提交
71 72
import java.util.List;
import java.util.Set;
K
kohsuke 已提交
73 74
import java.util.Map;
import java.util.HashMap;
75 76
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
K
kohsuke 已提交
77
import java.util.logging.Level;
K
kohsuke 已提交
78 79 80 81 82 83 84
import java.util.logging.Logger;

/**
 * Manages {@link PluginWrapper}s.
 *
 * @author Kohsuke Kawaguchi
 */
85
public abstract class PluginManager extends AbstractModelObject {
K
kohsuke 已提交
86 87 88
    /**
     * All discovered plugins.
     */
89
    protected final List<PluginWrapper> plugins = new ArrayList<PluginWrapper>();
K
kohsuke 已提交
90 91 92 93

    /**
     * All active plugins.
     */
94
    protected final List<PluginWrapper> activePlugins = new ArrayList<PluginWrapper>();
K
kohsuke 已提交
95

96
    protected final List<FailedPlugin> failedPlugins = new ArrayList<FailedPlugin>();
97

K
kohsuke 已提交
98 99 100 101 102
    /**
     * Plug-in root directory.
     */
    public final File rootDir;

103 104 105 106 107
    /**
     * @deprecated as of 1.355
     *      {@link PluginManager} can now live longer than {@link Hudson} instance, so
     *      use {@code Hudson.getInstance().servletContext} instead.
     */
K
kohsuke 已提交
108 109 110 111 112 113 114
    public final ServletContext context;

    /**
     * {@link ClassLoader} that can load all the publicly visible classes from plugins
     * (and including the classloader that loads Hudson itself.)
     *
     */
K
kohsuke 已提交
115
    // implementation is minimal --- just enough to run XStream
K
kohsuke 已提交
116 117 118
    // and load plugin-contributed classes.
    public final ClassLoader uberClassLoader = new UberClassLoader();

119 120 121 122 123
    /**
     * Once plugin is uploaded, this flag becomes true.
     * This is used to report a message that Hudson needs to be restarted
     * for new plugins to take effect.
     */
M
mindless 已提交
124
    public volatile boolean pluginUploaded = false;
125 126 127 128 129 130 131 132

    /**
     * The initialization of {@link PluginManager} splits into two parts;
     * one is the part about listing them, extracting them, and preparing classloader for them.
     * The 2nd part is about creating instances. Once the former completes this flags become true,
     * as the 2nd part can be repeated for each Hudson instance.
     */
    private boolean pluginListed = false;
133 134 135 136
    
    /**
     * Strategy for creating and initializing plugins
     */
K
kohsuke 已提交
137
    private final PluginStrategy strategy;
138

139
    public PluginManager(ServletContext context, File rootDir) {
K
kohsuke 已提交
140
        this.context = context;
141

142
        this.rootDir = rootDir;
K
kohsuke 已提交
143 144
        if(!rootDir.exists())
            rootDir.mkdirs();
145 146
        
        strategy = createPluginStrategy();
147
    }
148

149 150 151 152 153
    /**
     * Called immediately after the construction.
     * This is a separate method so that code executed from here will see a valid value in
     * {@link Hudson#pluginManager}. 
     */
K
kohsuke 已提交
154
    public TaskBuilder initTasks(final InitStrategy initStrategy) {
155 156 157 158 159 160 161 162 163 164 165 166
        TaskBuilder builder;
        if (!pluginListed) {
            builder = new TaskGraphBuilder() {
                List<File> archives;
                Collection<String> bundledPlugins;

                {
                    Handle loadBundledPlugins = add("Loading bundled plugins", new Executable() {
                        public void run(Reactor session) throws Exception {
                            bundledPlugins = loadBundledPlugins();
                        }
                    });
K
kohsuke 已提交
167

168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
                    Handle listUpPlugins = requires(loadBundledPlugins).add("Listing up plugins", new Executable() {
                        public void run(Reactor session) throws Exception {
                            archives = initStrategy.listPluginArchives(PluginManager.this);
                        }
                    });

                    requires(listUpPlugins).attains(PLUGINS_LISTED).add("Preparing plugins",new Executable() {
                        public void run(Reactor session) throws Exception {
                            // once we've listed plugins, we can fill in the reactor with plugin-specific initialization tasks
                            TaskGraphBuilder g = new TaskGraphBuilder();

                            final Map<String,File> inspectedShortNames = new HashMap<String,File>();

                            for( final File arc : archives ) {
                                g.followedBy().notFatal().attains(PLUGINS_LISTED).add("Inspecting plugin " + arc, new Executable() {
                                    public void run(Reactor session1) throws Exception {
                                        try {
                                            PluginWrapper p = strategy.createPluginWrapper(arc);
                                            if (isDuplicate(p)) return;

                                            p.isBundled = bundledPlugins.contains(arc.getName());
                                            plugins.add(p);
                                            if(p.isActive())
                                                activePlugins.add(p);
                                        } catch (IOException e) {
                                            failedPlugins.add(new FailedPlugin(arc.getName(),e));
                                            throw e;
                                        }
                                    }
K
kohsuke 已提交
197

198 199 200 201 202 203 204 205 206 207
                                    /**
                                     * Inspects duplication. this happens when you run hpi:run on a bundled plugin,
                                     * as well as putting numbered hpi files, like "cobertura-1.0.hpi" and "cobertura-1.1.hpi"
                                     */
                                    private boolean isDuplicate(PluginWrapper p) {
                                        String shortName = p.getShortName();
                                        if (inspectedShortNames.containsKey(shortName)) {
                                            LOGGER.info("Ignoring "+arc+" because "+inspectedShortNames.get(shortName)+" is already loaded");
                                            return true;
                                        }
K
kohsuke 已提交
208

209 210
                                        inspectedShortNames.put(shortName,arc);
                                        return false;
K
kohsuke 已提交
211
                                    }
212 213
                                });
                            }
K
kohsuke 已提交
214

215
                            g.requires(PLUGINS_PREPARED).add("Checking cyclic dependencies",new Executable() {
K
kohsuke 已提交
216
                                /**
217
                                 * Makes sure there's no cycle in dependencies.
K
kohsuke 已提交
218
                                 */
219 220 221 222 223 224 225 226 227 228
                                public void run(Reactor reactor) throws Exception {
                                    try {
                                        new CyclicGraphDetector<PluginWrapper>() {
                                            @Override
                                            protected List<PluginWrapper> getEdges(PluginWrapper p) {
                                                List<PluginWrapper> next = new ArrayList<PluginWrapper>();
                                                addTo(p.getDependencies(),next);
                                                addTo(p.getOptionalDependencies(),next);
                                                return next;
                                            }
K
kohsuke 已提交
229

230 231 232 233 234 235 236 237 238 239 240 241
                                            private void addTo(List<Dependency> dependencies, List<PluginWrapper> r) {
                                                for (Dependency d : dependencies) {
                                                    PluginWrapper p = getPlugin(d.shortName);
                                                    if (p!=null)
                                                        r.add(p);
                                                }
                                            }
                                        }.run(getPlugins());
                                    } catch (CycleDetectedException e) {
                                        stop(); // disable all plugins since classloading from them can lead to StackOverflow
                                        throw e;    // let Hudson fail
                                    }
M
mindless 已提交
242
                                    Collections.sort(plugins);
K
kohsuke 已提交
243 244
                                }
                            });
245 246 247 248

                            session.addAll(g.discoverTasks(session));

                            pluginListed = true; // technically speaking this is still too early, as at this point tasks are merely scheduled, not necessarily executed.
K
kohsuke 已提交
249
                        }
250 251 252 253 254 255
                    });
                }
            };
        } else {
            builder = TaskBuilder.EMPTY_BUILDER;
        }
K
kohsuke 已提交
256

257 258
        final InitializerFinder initializerFinder = new InitializerFinder(uberClassLoader);        // misc. stuff

259
        // lists up initialization tasks about loading plugins.
260 261
        return TaskBuilder.union(initializerFinder, // this scans @Initializer in the core once
            builder,new TaskGraphBuilder() {{
262 263 264 265 266 267 268 269
            requires(PLUGINS_LISTED).attains(PLUGINS_PREPARED).add("Loading plugins",new Executable() {
                /**
                 * Once the plugins are listed, schedule their initialization.
                 */
                public void run(Reactor session) throws Exception {
                    Hudson.getInstance().lookup.set(PluginInstanceStore.class,new PluginInstanceStore());
                    TaskGraphBuilder g = new TaskGraphBuilder();

K
Kohsuke Kawaguchi 已提交
270 271
                    Hudson.getInstance().container = Guice.createInjector();

272 273 274 275
                    // schedule execution of loading plugins
                    for (final PluginWrapper p : activePlugins.toArray(new PluginWrapper[activePlugins.size()])) {
                        g.followedBy().notFatal().attains(PLUGINS_PREPARED).add("Loading plugin " + p.getShortName(), new Executable() {
                            public void run(Reactor session) throws Exception {
276
                                try {
277 278 279 280 281 282 283
                                    p.resolvePluginDependencies();
                                    strategy.load(p);
                                } catch (IOException e) {
                                    failedPlugins.add(new FailedPlugin(p.getShortName(), e));
                                    activePlugins.remove(p);
                                    plugins.remove(p);
                                    throw e;
284 285 286
                                }
                            }
                        });
287
                    }
288

289 290 291
                    // schedule execution of initializing plugins
                    for (final PluginWrapper p : activePlugins.toArray(new PluginWrapper[activePlugins.size()])) {
                        g.followedBy().notFatal().attains(PLUGINS_STARTED).add("Initializing plugin " + p.getShortName(), new Executable() {
K
kohsuke 已提交
292
                            public void run(Reactor session) throws Exception {
293 294 295 296 297 298 299
                                try {
                                    p.getPlugin().postInitialize();
                                } catch (Exception e) {
                                    failedPlugins.add(new FailedPlugin(p.getShortName(), e));
                                    activePlugins.remove(p);
                                    plugins.remove(p);
                                    throw e;
K
kohsuke 已提交
300 301 302 303
                                }
                            }
                        });
                    }
304

305 306 307 308 309 310 311
                    g.followedBy().attains(PLUGINS_STARTED).add("Discovering plugin initialization tasks", new Executable() {
                        public void run(Reactor reactor) throws Exception {
                            // rescan to find plugin-contributed @Initializer
                            reactor.addAll(initializerFinder.discoverTasks(reactor));
                        }
                    });

312 313 314 315 316
                    // register them all
                    session.addAll(g.discoverTasks(session));
                }
            });
        }});
K
kohsuke 已提交
317 318
    }

319 320
    /**
     * If the war file has any "/WEB-INF/plugins/*.hpi", extract them into the plugin directory.
321 322 323
     *
     * @return
     *      File names of the bundled plugins. Like {"ssh-slaves.hpi","subvesrion.hpi"}
324 325
     * @throws Exception
     *      Any exception will be reported and halt the startup.
326
     */
327
    protected abstract Collection<String> loadBundledPlugins() throws Exception;
328

329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
    /**
     * Copies the bundled plugin from the given URL to the destination of the given file name (like 'abc.hpi'),
     * with a reasonable up-to-date check. A convenience method to be used by the {@link #loadBundledPlugins()}.
     */
    protected void copyBundledPlugin(URL src, String fileName) throws IOException {
        long lastModified = src.openConnection().getLastModified();
        File file = new File(rootDir, fileName);
        File pinFile = new File(rootDir, fileName+".pinned");

        // update file if:
        //  - no file exists today
        //  - bundled version and current version differs (by timestamp), and the file isn't pinned.
        if (!file.exists() || (file.lastModified() != lastModified && !pinFile.exists())) {
            FileUtils.copyURLToFile(src, file);
            file.setLastModified(src.openConnection().getLastModified());
            // lastModified is set for two reasons:
            // - to avoid unpacking as much as possible, but still do it on both upgrade and downgrade
            // - to make sure the value is not changed after each restart, so we can avoid
            // unpacking the plugin itself in ClassicPluginStrategy.explode
348 349 350
        }
    }

351
    /**
352 353
     * Creates a hudson.PluginStrategy, looking at the corresponding system property. 
     */
M
mindless 已提交
354
    private PluginStrategy createPluginStrategy() {
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
		String strategyName = System.getProperty(PluginStrategy.class.getName());
		if (strategyName != null) {
			try {
				Class<?> klazz = getClass().getClassLoader().loadClass(strategyName);
				Object strategy = klazz.getConstructor(PluginManager.class)
						.newInstance(this);
				if (strategy instanceof PluginStrategy) {
					LOGGER.info("Plugin strategy: " + strategyName);
					return (PluginStrategy) strategy;
				} else {
					LOGGER.warning("Plugin strategy (" + strategyName + 
							") is not an instance of hudson.PluginStrategy");
				}
			} catch (ClassNotFoundException e) {
				LOGGER.warning("Plugin strategy class not found: "
						+ strategyName);
			} catch (Exception e) {
				LOGGER.log(Level.WARNING, "Could not instantiate plugin strategy: "
						+ strategyName + ". Falling back to ClassicPluginStrategy", e);
			}
			LOGGER.info("Falling back to ClassicPluginStrategy");
		}
		
		// default and fallback
		return new ClassicPluginStrategy(this);
M
mindless 已提交
380 381 382 383 384 385 386 387 388
    }

    public PluginStrategy getPluginStrategy() {
        return strategy;
    }

    /**
     * Returns true if any new plugin was added, which means a restart is required
     * for the change to take effect.
389 390 391 392 393
     */
    public boolean isPluginUploaded() {
        return pluginUploaded;
    }
    
K
kohsuke 已提交
394 395 396 397
    public List<PluginWrapper> getPlugins() {
        return plugins;
    }

398 399 400 401
    public List<FailedPlugin> getFailedPlugins() {
        return failedPlugins;
    }

K
kohsuke 已提交
402 403 404 405 406 407 408 409
    public PluginWrapper getPlugin(String shortName) {
        for (PluginWrapper p : plugins) {
            if(p.getShortName().equals(shortName))
                return p;
        }
        return null;
    }

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 437 438
    /**
     * Get the plugin instance that implements a specific class, use to find your plugin singleton.
     * Note: beware the classloader fun.
     * @param pluginClazz The class that your plugin implements.
     * @return The plugin singleton or <code>null</code> if for some reason the plugin is not loaded.
     */
    public PluginWrapper getPlugin(Class<? extends Plugin> pluginClazz) {
        for (PluginWrapper p : plugins) {
            if(pluginClazz.isInstance(p.getPlugin()))
                return p;
        }
        return null;
    }

    /**
     * Get the plugin instances that extend a specific class, use to find similar plugins.
     * Note: beware the classloader fun.
     * @param pluginSuperclass The class that your plugin is derived from.
     * @return The list of plugins implementing the specified class.
     */
    public List<PluginWrapper> getPlugins(Class<? extends Plugin> pluginSuperclass) {
        List<PluginWrapper> result = new ArrayList<PluginWrapper>();
        for (PluginWrapper p : plugins) {
            if(pluginSuperclass.isInstance(p.getPlugin()))
                result.add(p);
        }
        return Collections.unmodifiableList(result);
    }

439
    public String getDisplayName() {
S
sogabe 已提交
440
        return Messages.PluginManager_DisplayName();
441 442 443 444 445 446
    }

    public String getSearchUrl() {
        return "pluginManager";
    }

K
kohsuke 已提交
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464
    /**
     * Discover all the service provider implementations of the given class,
     * via <tt>META-INF/services</tt>.
     */
    public <T> Collection<Class<? extends T>> discover( Class<T> spi ) {
        Set<Class<? extends T>> result = new HashSet<Class<? extends T>>();

        for (PluginWrapper p : activePlugins) {
            Service.load(spi, p.classLoader, result);
        }

        return result;
    }

    /**
     * Orderly terminates all the plugins.
     */
    public void stop() {
465
        for (PluginWrapper p : activePlugins) {
K
kohsuke 已提交
466
            p.stop();
467 468
            p.releaseClassLoader();
        }
469
        activePlugins.clear();
470 471 472
        // Work around a bug in commons-logging.
        // See http://www.szegedi.org/articles/memleak.html
        LogFactory.release(uberClassLoader);
K
kohsuke 已提交
473 474
    }

475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
    public HttpResponse doUpdateSources(StaplerRequest req) throws IOException {
        Hudson.getInstance().checkPermission(Hudson.ADMINISTER);

        if (req.hasParameter("remove")) {
            UpdateCenter uc = Hudson.getInstance().getUpdateCenter();
            BulkChange bc = new BulkChange(uc);
            try {
                for (String id : req.getParameterValues("sources"))
                    uc.getSites().remove(uc.getById(id));
            } finally {
                bc.commit();
            }
        } else
        if (req.hasParameter("add"))
            return new HttpRedirect("addSite");

        return new HttpRedirect("./sites");
    }

494 495 496 497 498 499 500 501 502
    /**
     * Performs the installation of the plugins.
     */
    public void doInstall(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
        Enumeration<String> en = req.getParameterNames();
        while (en.hasMoreElements()) {
            String n =  en.nextElement();
            if(n.startsWith("plugin.")) {
                n = n.substring(7);
503 504 505 506 507 508 509
                if (n.indexOf(".") > 0) {
                    String[] pluginInfo = n.split("\\.");
                    UpdateSite.Plugin p = Hudson.getInstance().getUpdateCenter().getById(pluginInfo[1]).getPlugin(pluginInfo[0]);
                    if(p==null)
                        throw new Failure("No such plugin: "+n);
                    p.deploy();
                }
510 511 512 513
            }
        }
        rsp.sendRedirect("../updateCenter/");
    }
514
    
515

516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533
    /**
     * Bare-minimum configuration mechanism to change the update center.
     */
    public HttpResponse doSiteConfigure(@QueryParameter String site) throws IOException {
        Hudson hudson = Hudson.getInstance();
        hudson.checkPermission(Hudson.ADMINISTER);
        UpdateCenter uc = hudson.getUpdateCenter();
        PersistedList<UpdateSite> sites = uc.getSites();
        for (UpdateSite s : sites) {
            if (s.getId().equals("default"))
                sites.remove(s);
        }
        sites.add(new UpdateSite("default",site));
        
        return HttpResponses.redirectToContextRoot();
    }


534
    public HttpResponse doProxyConfigure(
535 536 537
            @QueryParameter("proxy.server") String server,
            @QueryParameter("proxy.port") String port,
            @QueryParameter("proxy.userName") String userName,
538
            @QueryParameter("proxy.password") String password) throws IOException {
539
        Hudson hudson = Hudson.getInstance();
540 541
        hudson.checkPermission(Hudson.ADMINISTER);

542 543 544 545
        server = Util.fixEmptyAndTrim(server);
        if(server==null) {
            hudson.proxy = null;
            ProxyConfiguration.getXmlFile().delete();
546 547
        } else try {
            hudson.proxy = new ProxyConfiguration(server,Integer.parseInt(Util.fixNull(port)),
548
                    Util.fixEmptyAndTrim(userName),Util.fixEmptyAndTrim(password));
549
            hudson.proxy.save();
550 551 552
        } catch (NumberFormatException nfe) {
            return HttpResponses.error(StaplerResponse.SC_BAD_REQUEST,
                    new IllegalArgumentException("Invalid proxy port: " + port, nfe));
553
        }
554
        return new HttpRedirect("advanced");
555 556
    }

557 558 559
    /**
     * Uploads a plugin.
     */
560
    public HttpResponse doUploadPlugin(StaplerRequest req) throws IOException, ServletException {
561 562 563 564 565 566 567 568
        try {
            Hudson.getInstance().checkPermission(Hudson.ADMINISTER);

            ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory());

            // Parse the request
            FileItem fileItem = (FileItem) upload.parseRequest(req).get(0);
            String fileName = Util.getFileName(fileItem.getName());
569 570
            if("".equals(fileName))
                return new HttpRedirect("advanced");
571 572
            if(!fileName.endsWith(".hpi"))
                throw new Failure(hudson.model.Messages.Hudson_NotAPlugin(fileName));
573 574 575
            fileItem.write(new File(rootDir, fileName));
            fileItem.delete();

M
mindless 已提交
576
            pluginUploaded = true;
577

578
            return new HttpRedirect(".");
579 580 581 582 583 584 585
        } catch (IOException e) {
            throw e;
        } catch (Exception e) {// grrr. fileItem.write throws this
            throw new ServletException(e);
        }
    }

K
kohsuke 已提交
586 587 588
    /**
     * {@link ClassLoader} that can see all plugins.
     */
589 590 591 592 593 594 595
    public final class UberClassLoader extends ClassLoader {
        /**
         * Make generated types visible.
         * Keyed by the generated class name.
         */
        private ConcurrentMap<String, WeakReference<Class>> generatedClasses = new ConcurrentHashMap<String, WeakReference<Class>>();

K
kohsuke 已提交
596 597 598 599
        public UberClassLoader() {
            super(PluginManager.class.getClassLoader());
        }

600 601 602 603
        public void addNamedClass(String className, Class c) {
            generatedClasses.put(className,new WeakReference<Class>(c));
        }

K
kohsuke 已提交
604 605
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
606 607 608 609 610 611 612
            WeakReference<Class> wc = generatedClasses.get(name);
            if (wc!=null) {
                Class c = wc.get();
                if (c!=null)    return c;
                else            generatedClasses.remove(name,wc);
            }

613 614 615
            // first, use the context classloader so that plugins that are loading
            // can use its own classloader first.
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
616
            if(cl!=null && cl!=this)
617 618 619 620 621 622
                try {
                    return cl.loadClass(name);
                } catch(ClassNotFoundException e) {
                    // not found. try next
                }

K
kohsuke 已提交
623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642
            for (PluginWrapper p : activePlugins) {
                try {
                    return p.classLoader.loadClass(name);
                } catch (ClassNotFoundException e) {
                    //not found. try next
                }
            }
            // not found in any of the classloader. delegate.
            throw new ClassNotFoundException(name);
        }

        @Override
        protected URL findResource(String name) {
            for (PluginWrapper p : activePlugins) {
                URL url = p.classLoader.getResource(name);
                if(url!=null)
                    return url;
            }
            return null;
        }
643 644 645 646 647 648 649 650 651

        @Override
        protected Enumeration<URL> findResources(String name) throws IOException {
            List<URL> resources = new ArrayList<URL>();
            for (PluginWrapper p : activePlugins) {
                resources.addAll(Collections.list(p.classLoader.getResources(name)));
            }
            return Collections.enumeration(resources);
        }
K
kohsuke 已提交
652 653 654 655
    }

    private static final Logger LOGGER = Logger.getLogger(PluginManager.class.getName());

656 657 658 659 660
    /**
     * Remembers why a plugin failed to deploy.
     */
    public static final class FailedPlugin {
        public final String name;
K
kohsuke 已提交
661
        public final Exception cause;
662

K
kohsuke 已提交
663
        public FailedPlugin(String name, Exception cause) {
664 665 666 667 668
            this.name = name;
            this.cause = cause;
        }

        public String getExceptionString() {
669
            return Functions.printThrowable(cause);
670 671
        }
    }
672 673 674 675 676 677 678

    /**
     * Stores {@link Plugin} instances.
     */
    /*package*/ static final class PluginInstanceStore {
        final Map<PluginWrapper,Plugin> store = new Hashtable<PluginWrapper,Plugin>();
    }
K
kohsuke 已提交
679
}