PluginManager.java 28.5 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
import hudson.model.AbstractModelObject;
import hudson.model.Failure;
35
import jenkins.model.Jenkins;
36
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
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;
46
import org.apache.commons.io.FilenameUtils;
47
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
import java.util.List;
K
Kohsuke Kawaguchi 已提交
72
import java.util.ListIterator;
K
kohsuke 已提交
73
import java.util.Set;
K
kohsuke 已提交
74 75
import java.util.Map;
import java.util.HashMap;
76 77
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
78
import java.util.concurrent.CopyOnWriteArrayList;
K
kohsuke 已提交
79
import java.util.logging.Level;
K
kohsuke 已提交
80 81 82 83 84 85 86
import java.util.logging.Logger;

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

    /**
     * All active plugins.
     */
96
    protected final List<PluginWrapper> activePlugins = new CopyOnWriteArrayList<PluginWrapper>();
K
kohsuke 已提交
97

98
    protected final List<FailedPlugin> failedPlugins = new ArrayList<FailedPlugin>();
99

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

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

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

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

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

144
        this.rootDir = rootDir;
K
kohsuke 已提交
145 146
        if(!rootDir.exists())
            rootDir.mkdirs();
147 148
        
        strategy = createPluginStrategy();
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
154
     * {@link jenkins.model.Jenkins#pluginManager}.
155
     */
K
kohsuke 已提交
156
    public TaskBuilder initTasks(final InitStrategy initStrategy) {
157 158 159 160 161 162 163 164 165 166 167 168
        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 已提交
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);
                                        } 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

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

230 231 232
                                            private void addTo(List<Dependency> dependencies, List<PluginWrapper> r) {
                                                for (Dependency d : dependencies) {
                                                    PluginWrapper p = getPlugin(d.shortName);
K
Kohsuke Kawaguchi 已提交
233
                                                    if (p != null)
234 235 236
                                                        r.add(p);
                                                }
                                            }
K
Kohsuke Kawaguchi 已提交
237 238 239 240 241 242 243 244 245 246 247
                                        };
                                        cgd.run(getPlugins());

                                        // obtain topologically sorted list and overwrite the list
                                        ListIterator<PluginWrapper> litr = plugins.listIterator();
                                        for (PluginWrapper p : cgd.getSorted()) {
                                            litr.next();
                                            litr.set(p);
                                            if(p.isActive())
                                                activePlugins.add(p);
                                        }
248 249 250 251
                                    } catch (CycleDetectedException e) {
                                        stop(); // disable all plugins since classloading from them can lead to StackOverflow
                                        throw e;    // let Hudson fail
                                    }
K
kohsuke 已提交
252 253
                                }
                            });
254 255 256 257

                            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 已提交
258
                        }
259 260 261 262 263 264
                    });
                }
            };
        } else {
            builder = TaskBuilder.EMPTY_BUILDER;
        }
K
kohsuke 已提交
265

266 267
        final InitializerFinder initializerFinder = new InitializerFinder(uberClassLoader);        // misc. stuff

268
        // lists up initialization tasks about loading plugins.
269 270
        return TaskBuilder.union(initializerFinder, // this scans @Initializer in the core once
            builder,new TaskGraphBuilder() {{
271 272 273 274 275
            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 {
276
                    Jenkins.getInstance().lookup.set(PluginInstanceStore.class,new PluginInstanceStore());
277 278 279 280 281 282
                    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 {
283
                                try {
284 285 286 287 288 289 290
                                    p.resolvePluginDependencies();
                                    strategy.load(p);
                                } catch (IOException e) {
                                    failedPlugins.add(new FailedPlugin(p.getShortName(), e));
                                    activePlugins.remove(p);
                                    plugins.remove(p);
                                    throw e;
291 292 293
                                }
                            }
                        });
294
                    }
295

296 297 298
                    // 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 已提交
299
                            public void run(Reactor session) throws Exception {
300 301 302 303 304 305 306
                                try {
                                    p.getPlugin().postInitialize();
                                } catch (Exception e) {
                                    failedPlugins.add(new FailedPlugin(p.getShortName(), e));
                                    activePlugins.remove(p);
                                    plugins.remove(p);
                                    throw e;
K
kohsuke 已提交
307 308 309 310
                                }
                            }
                        });
                    }
311

312 313 314 315 316 317 318
                    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));
                        }
                    });

319 320 321 322 323
                    // register them all
                    session.addAll(g.discoverTasks(session));
                }
            });
        }});
K
kohsuke 已提交
324 325
    }

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

336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
    /**
     * 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
355 356 357
        }
    }

358
    /**
359 360
     * Creates a hudson.PluginStrategy, looking at the corresponding system property. 
     */
M
mindless 已提交
361
    private PluginStrategy createPluginStrategy() {
362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386
		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 已提交
387 388 389 390 391 392 393 394 395
    }

    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.
396 397 398 399 400
     */
    public boolean isPluginUploaded() {
        return pluginUploaded;
    }
    
K
kohsuke 已提交
401 402 403 404
    public List<PluginWrapper> getPlugins() {
        return plugins;
    }

405 406 407 408
    public List<FailedPlugin> getFailedPlugins() {
        return failedPlugins;
    }

K
kohsuke 已提交
409 410 411 412 413 414 415 416
    public PluginWrapper getPlugin(String shortName) {
        for (PluginWrapper p : plugins) {
            if(p.getShortName().equals(shortName))
                return p;
        }
        return null;
    }

417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
    /**
     * 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);
    }

446
    public String getDisplayName() {
S
sogabe 已提交
447
        return Messages.PluginManager_DisplayName();
448 449 450 451 452 453
    }

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

K
kohsuke 已提交
454 455 456 457 458 459 460 461 462 463 464 465 466 467
    /**
     * 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;
    }

468 469 470 471 472 473 474 475 476 477 478 479 480 481
    /**
     * Return the {@link PluginWrapper} that loaded the given class 'c'.
     *
     * @since 1.402.
     */
    public PluginWrapper whichPlugin(Class c) {
        ClassLoader cl = c.getClassLoader();
        for (PluginWrapper p : activePlugins) {
            if (p.classLoader==cl)
                return p;
        }
        return null;
    }

K
kohsuke 已提交
482 483 484 485
    /**
     * Orderly terminates all the plugins.
     */
    public void stop() {
486
        for (PluginWrapper p : activePlugins) {
K
kohsuke 已提交
487
            p.stop();
488 489
            p.releaseClassLoader();
        }
490
        activePlugins.clear();
491 492 493
        // Work around a bug in commons-logging.
        // See http://www.szegedi.org/articles/memleak.html
        LogFactory.release(uberClassLoader);
K
kohsuke 已提交
494 495
    }

496
    public HttpResponse doUpdateSources(StaplerRequest req) throws IOException {
497
        Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
498 499

        if (req.hasParameter("remove")) {
500
            UpdateCenter uc = Jenkins.getInstance().getUpdateCenter();
501 502 503 504 505 506 507 508 509 510 511 512 513 514
            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");
    }

515 516 517 518 519 520 521 522 523
    /**
     * 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);
524 525
                if (n.indexOf(".") > 0) {
                    String[] pluginInfo = n.split("\\.");
526
                    UpdateSite.Plugin p = Jenkins.getInstance().getUpdateCenter().getById(pluginInfo[1]).getPlugin(pluginInfo[0]);
527 528 529 530
                    if(p==null)
                        throw new Failure("No such plugin: "+n);
                    p.deploy();
                }
531 532 533 534
            }
        }
        rsp.sendRedirect("../updateCenter/");
    }
535
    
536

537 538 539 540
    /**
     * Bare-minimum configuration mechanism to change the update center.
     */
    public HttpResponse doSiteConfigure(@QueryParameter String site) throws IOException {
541 542
        Jenkins hudson = Jenkins.getInstance();
        hudson.checkPermission(Jenkins.ADMINISTER);
543 544 545 546 547 548 549 550 551 552 553 554
        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();
    }


555
    public HttpResponse doProxyConfigure(
556 557 558
            @QueryParameter("proxy.server") String server,
            @QueryParameter("proxy.port") String port,
            @QueryParameter("proxy.userName") String userName,
559
            @QueryParameter("proxy.password") String password) throws IOException {
560 561
        Jenkins hudson = Jenkins.getInstance();
        hudson.checkPermission(Jenkins.ADMINISTER);
562

563 564 565 566
        server = Util.fixEmptyAndTrim(server);
        if(server==null) {
            hudson.proxy = null;
            ProxyConfiguration.getXmlFile().delete();
567 568
        } else try {
            hudson.proxy = new ProxyConfiguration(server,Integer.parseInt(Util.fixNull(port)),
569
                    Util.fixEmptyAndTrim(userName),Util.fixEmptyAndTrim(password));
570
            hudson.proxy.save();
571 572 573
        } catch (NumberFormatException nfe) {
            return HttpResponses.error(StaplerResponse.SC_BAD_REQUEST,
                    new IllegalArgumentException("Invalid proxy port: " + port, nfe));
574
        }
575
        return new HttpRedirect("advanced");
576 577
    }

578 579 580
    /**
     * Uploads a plugin.
     */
581
    public HttpResponse doUploadPlugin(StaplerRequest req) throws IOException, ServletException {
582
        try {
583
            Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
584 585 586 587 588 589

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

            // Parse the request
            FileItem fileItem = (FileItem) upload.parseRequest(req).get(0);
            String fileName = Util.getFileName(fileItem.getName());
590 591
            if("".equals(fileName))
                return new HttpRedirect("advanced");
592 593
            if(!fileName.endsWith(".hpi"))
                throw new Failure(hudson.model.Messages.Hudson_NotAPlugin(fileName));
594 595 596
            fileItem.write(new File(rootDir, fileName));
            fileItem.delete();

597 598 599 600
            PluginWrapper existing = getPlugin(FilenameUtils.getBaseName(fileName));
            if (existing!=null && existing.isBundled)
                existing.doPin();

M
mindless 已提交
601
            pluginUploaded = true;
602

603
            return new HttpRedirect(".");
604 605 606 607 608 609 610
        } catch (IOException e) {
            throw e;
        } catch (Exception e) {// grrr. fileItem.write throws this
            throw new ServletException(e);
        }
    }

K
kohsuke 已提交
611 612 613
    /**
     * {@link ClassLoader} that can see all plugins.
     */
614 615 616 617 618 619 620
    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 已提交
621 622 623 624
        public UberClassLoader() {
            super(PluginManager.class.getClassLoader());
        }

625 626 627 628
        public void addNamedClass(String className, Class c) {
            generatedClasses.put(className,new WeakReference<Class>(c));
        }

K
kohsuke 已提交
629 630
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
631 632 633 634 635 636 637
            WeakReference<Class> wc = generatedClasses.get(name);
            if (wc!=null) {
                Class c = wc.get();
                if (c!=null)    return c;
                else            generatedClasses.remove(name,wc);
            }

638 639 640
            // first, use the context classloader so that plugins that are loading
            // can use its own classloader first.
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
641
            if(cl!=null && cl!=this)
642 643 644 645 646 647
                try {
                    return cl.loadClass(name);
                } catch(ClassNotFoundException e) {
                    // not found. try next
                }

K
kohsuke 已提交
648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667
            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;
        }
668 669 670 671 672 673 674 675 676

        @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 已提交
677 678 679 680 681 682 683

        @Override
        public String toString()
        {
            // only for debugging purpose
            return "classLoader " +  getClass().getName();
        }
K
kohsuke 已提交
684 685 686 687
    }

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

688 689 690 691 692
    /**
     * Remembers why a plugin failed to deploy.
     */
    public static final class FailedPlugin {
        public final String name;
K
kohsuke 已提交
693
        public final Exception cause;
694

K
kohsuke 已提交
695
        public FailedPlugin(String name, Exception cause) {
696 697 698 699 700
            this.name = name;
            this.cause = cause;
        }

        public String getExceptionString() {
701
            return Functions.printThrowable(cause);
702 703
        }
    }
704 705 706 707 708 709 710

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