PluginManager.java 26.9 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 30

import hudson.PluginWrapper.Dependency;
K
kohsuke 已提交
31
import hudson.init.InitStrategy;
32
import hudson.init.InitializerFinder;
33 34 35 36
import hudson.model.AbstractModelObject;
import hudson.model.Failure;
import hudson.model.Hudson;
import hudson.model.UpdateCenter;
K
kohsuke 已提交
37
import hudson.model.UpdateSite;
38 39
import hudson.util.CyclicGraphDetector;
import hudson.util.CyclicGraphDetector.CycleDetectedException;
40
import hudson.util.PersistedList;
K
kohsuke 已提交
41
import hudson.util.Service;
42 43 44 45 46
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 已提交
47 48 49 50
import org.jvnet.hudson.reactor.Executable;
import org.jvnet.hudson.reactor.Reactor;
import org.jvnet.hudson.reactor.TaskBuilder;
import org.jvnet.hudson.reactor.TaskGraphBuilder;
51 52
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
53
import org.kohsuke.stapler.HttpResponses;
54 55 56
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
K
kohsuke 已提交
57 58

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

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

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

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

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

102 103 104 105 106
    /**
     * @deprecated as of 1.355
     *      {@link PluginManager} can now live longer than {@link Hudson} instance, so
     *      use {@code Hudson.getInstance().servletContext} instead.
     */
K
kohsuke 已提交
107 108 109 110 111 112 113
    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 已提交
114
    // implementation is minimal --- just enough to run XStream
K
kohsuke 已提交
115 116 117
    // and load plugin-contributed classes.
    public final ClassLoader uberClassLoader = new UberClassLoader();

118 119 120 121 122
    /**
     * 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 已提交
123
    public volatile boolean pluginUploaded = false;
124 125 126 127 128 129 130 131

    /**
     * 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;
132 133 134 135
    
    /**
     * Strategy for creating and initializing plugins
     */
K
kohsuke 已提交
136
    private final PluginStrategy strategy;
137

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

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

148 149 150 151 152
    /**
     * 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 已提交
153
    public TaskBuilder initTasks(final InitStrategy initStrategy) {
154 155 156 157 158 159 160 161 162 163 164 165
        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 已提交
166

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
                    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 已提交
196

197 198 199 200 201 202 203 204 205 206
                                    /**
                                     * 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 已提交
207

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

214
                            g.requires(PLUGINS_PREPARED).add("Checking cyclic dependencies",new Executable() {
K
kohsuke 已提交
215
                                /**
216
                                 * Makes sure there's no cycle in dependencies.
K
kohsuke 已提交
217
                                 */
218 219 220 221 222 223 224 225 226 227
                                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 已提交
228

229 230 231 232 233 234 235 236 237 238 239 240
                                            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 已提交
241
                                    Collections.sort(plugins);
K
kohsuke 已提交
242 243
                                }
                            });
244 245 246 247

                            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 已提交
248
                        }
249 250 251 252 253 254
                    });
                }
            };
        } else {
            builder = TaskBuilder.EMPTY_BUILDER;
        }
K
kohsuke 已提交
255

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

258
        // lists up initialization tasks about loading plugins.
259 260
        return TaskBuilder.union(initializerFinder, // this scans @Initializer in the core once
            builder,new TaskGraphBuilder() {{
261 262 263 264 265 266 267 268 269 270 271 272
            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();

                    // 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 {
273
                                try {
274 275 276 277 278 279 280
                                    p.resolvePluginDependencies();
                                    strategy.load(p);
                                } catch (IOException e) {
                                    failedPlugins.add(new FailedPlugin(p.getShortName(), e));
                                    activePlugins.remove(p);
                                    plugins.remove(p);
                                    throw e;
281 282 283
                                }
                            }
                        });
284
                    }
285

286 287 288
                    // 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 已提交
289
                            public void run(Reactor session) throws Exception {
290 291 292 293 294 295 296
                                try {
                                    p.getPlugin().postInitialize();
                                } catch (Exception e) {
                                    failedPlugins.add(new FailedPlugin(p.getShortName(), e));
                                    activePlugins.remove(p);
                                    plugins.remove(p);
                                    throw e;
K
kohsuke 已提交
297 298 299 300
                                }
                            }
                        });
                    }
301

302 303 304 305 306 307 308
                    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));
                        }
                    });

309 310 311 312 313
                    // register them all
                    session.addAll(g.discoverTasks(session));
                }
            });
        }});
K
kohsuke 已提交
314 315
    }

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

326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
    /**
     * 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
345 346 347
        }
    }

348
    /**
349 350
     * Creates a hudson.PluginStrategy, looking at the corresponding system property. 
     */
K
Kohsuke Kawaguchi 已提交
351
    protected PluginStrategy createPluginStrategy() {
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
		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 已提交
377 378 379 380 381 382 383 384 385
    }

    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.
386 387 388 389 390
     */
    public boolean isPluginUploaded() {
        return pluginUploaded;
    }
    
K
kohsuke 已提交
391 392 393 394
    public List<PluginWrapper> getPlugins() {
        return plugins;
    }

395 396 397 398
    public List<FailedPlugin> getFailedPlugins() {
        return failedPlugins;
    }

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

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

436
    public String getDisplayName() {
S
sogabe 已提交
437
        return Messages.PluginManager_DisplayName();
438 439 440 441 442 443
    }

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

K
kohsuke 已提交
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461
    /**
     * 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() {
462
        for (PluginWrapper p : activePlugins) {
K
kohsuke 已提交
463
            p.stop();
464 465
            p.releaseClassLoader();
        }
466
        activePlugins.clear();
467 468 469
        // Work around a bug in commons-logging.
        // See http://www.szegedi.org/articles/memleak.html
        LogFactory.release(uberClassLoader);
K
kohsuke 已提交
470 471
    }

472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490
    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");
    }

491 492 493 494 495 496 497 498 499
    /**
     * 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);
500 501 502 503 504 505 506
                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();
                }
507 508 509 510
            }
        }
        rsp.sendRedirect("../updateCenter/");
    }
511
    
512

513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530
    /**
     * 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();
    }


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

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

554 555 556
    /**
     * Uploads a plugin.
     */
557
    public HttpResponse doUploadPlugin(StaplerRequest req) throws IOException, ServletException {
558 559 560 561 562 563 564 565
        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());
566 567
            if("".equals(fileName))
                return new HttpRedirect("advanced");
568 569
            if(!fileName.endsWith(".hpi"))
                throw new Failure(hudson.model.Messages.Hudson_NotAPlugin(fileName));
570 571 572
            fileItem.write(new File(rootDir, fileName));
            fileItem.delete();

M
mindless 已提交
573
            pluginUploaded = true;
574

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

K
kohsuke 已提交
583 584 585
    /**
     * {@link ClassLoader} that can see all plugins.
     */
586 587 588 589 590 591 592
    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 已提交
593 594 595 596
        public UberClassLoader() {
            super(PluginManager.class.getClassLoader());
        }

597 598 599 600
        public void addNamedClass(String className, Class c) {
            generatedClasses.put(className,new WeakReference<Class>(c));
        }

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

K
kohsuke 已提交
610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629
            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;
        }
630 631 632 633 634 635 636 637 638

        @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);
        }
S
Seiji Sogabe 已提交
639 640 641 642 643 644 645

        @Override
        public String toString()
        {
            // only for debugging purpose
            return "classLoader " +  getClass().getName();
        }
K
kohsuke 已提交
646 647 648 649
    }

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

650 651 652 653 654
    /**
     * Remembers why a plugin failed to deploy.
     */
    public static final class FailedPlugin {
        public final String name;
K
kohsuke 已提交
655
        public final Exception cause;
656

K
kohsuke 已提交
657
        public FailedPlugin(String name, Exception cause) {
658 659 660 661 662
            this.name = name;
            this.cause = cause;
        }

        public String getExceptionString() {
663
            return Functions.printThrowable(cause);
664 665
        }
    }
666 667 668 669 670 671 672

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