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

27
import com.google.common.collect.ImmutableSet;
28
import hudson.PluginManager.PluginInstanceStore;
29
import hudson.model.Api;
K
Kohsuke Kawaguchi 已提交
30
import hudson.model.ModelObject;
31
import jenkins.YesNoMaybe;
32
import jenkins.model.Jenkins;
33
import hudson.model.UpdateCenter;
34
import hudson.model.UpdateSite;
35
import hudson.util.VersionNumber;
K
kohsuke 已提交
36 37 38 39 40

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
41
import java.io.Closeable;
K
kohsuke 已提交
42
import java.net.URL;
43
import java.util.ArrayList;
44 45
import java.util.Arrays;
import java.util.Collections;
K
kohsuke 已提交
46
import java.util.List;
47
import java.util.Set;
K
kohsuke 已提交
48 49
import java.util.jar.Manifest;
import java.util.logging.Logger;
50
import static java.util.logging.Level.WARNING;
K
Kohsuke Kawaguchi 已提交
51
import static org.apache.commons.io.FilenameUtils.getBaseName;
52
import org.apache.commons.lang.StringUtils;
53
import org.apache.commons.logging.LogFactory;
54 55
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
56 57
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
K
Kohsuke Kawaguchi 已提交
58
import org.kohsuke.stapler.interceptor.RequirePOST;
59

60 61
import java.util.Enumeration;
import java.util.jar.JarFile;
62
import java.util.logging.Level;
J
Jesse Glick 已提交
63
import javax.annotation.CheckForNull;
64
import javax.annotation.Nonnull;
65

K
kohsuke 已提交
66
/**
A
alanharder 已提交
67 68
 * Represents a Jenkins plug-in and associated control information
 * for Jenkins to control {@link Plugin}.
K
kohsuke 已提交
69 70
 *
 * <p>
71
 * A plug-in is packaged into a jar file whose extension is <tt>".jpi"</tt> (or <tt>".hpi"</tt> for backward compatibility),
K
kohsuke 已提交
72 73 74 75 76
 * A plugin needs to have a special manifest entry to identify what it is.
 *
 * <p>
 * At the runtime, a plugin has two distinct state axis.
 * <ol>
A
alanharder 已提交
77 78 79
 *  <li>Enabled/Disabled. If enabled, Jenkins is going to use it
 *      next time Jenkins runs. Otherwise the next run will ignore it.
 *  <li>Activated/Deactivated. If activated, that means Jenkins is using
K
kohsuke 已提交
80 81 82 83 84 85 86 87
 *      the plugin in this session. Otherwise it's not.
 * </ol>
 * <p>
 * For example, an activated but disabled plugin is still running but the next
 * time it won't.
 *
 * @author Kohsuke Kawaguchi
 */
88
@ExportedBean
K
Kohsuke Kawaguchi 已提交
89
public class PluginWrapper implements Comparable<PluginWrapper>, ModelObject {
90 91 92 93
    /**
     * A plugin won't be loaded unless his declared dependencies are present and match the required minimal version.
     * This can be set to false to disable the version check (legacy behaviour)
     */
94
    private static final boolean ENABLE_PLUGIN_DEPENDENCIES_VERSION_CHECK = Boolean.parseBoolean(System.getProperty(PluginWrapper.class.getName()+"." + "dependenciesVersionCheck.enabled", "true"));
95

K
kohsuke 已提交
96
    /**
97
     * {@link PluginManager} to which this belongs to.
K
kohsuke 已提交
98
     */
99
    public final PluginManager parent;
K
kohsuke 已提交
100 101

    /**
102 103
     * Plugin manifest.
     * Contains description of the plugin.
K
kohsuke 已提交
104
     */
105
    private final Manifest manifest;
K
kohsuke 已提交
106 107 108 109 110 111 112 113 114 115

    /**
     * {@link ClassLoader} for loading classes from this plugin.
     * Null if disabled.
     */
    public final ClassLoader classLoader;

    /**
     * Base URL for loading static resources from this plugin.
     * Null if disabled. The static resources are mapped under
K
Kohsuke Kawaguchi 已提交
116
     * <tt>CONTEXTPATH/plugin/SHORTNAME/</tt>.
K
kohsuke 已提交
117 118 119 120 121 122 123 124 125
     */
    public final URL baseResourceURL;

    /**
     * Used to control enable/disable setting of the plugin.
     * If this file exists, plugin will be disabled.
     */
    private final File disableFile;

K
Kohsuke Kawaguchi 已提交
126 127 128 129 130
    /**
     * A .jpi file, an exploded plugin directory, or a .jpl file.
     */
    private final File archive;

K
kohsuke 已提交
131
    /**
132
     * Short name of the plugin. The artifact Id of the plugin.
A
alanharder 已提交
133
     * This is also used in the URL within Jenkins, so it needs
I
imod 已提交
134
     * to remain stable even when the *.jpi file name is changed
135
     * (like Maven does.)
K
kohsuke 已提交
136 137 138
     */
    private final String shortName;

139
    /**
140 141 142 143
     * True if this plugin is activated for this session.
     * The snapshot of <tt>disableFile.exists()</tt> as of the start up.
     */
    private final boolean active;
144 145
    
    private boolean hasCycleDependency = false;
146

147
    private final List<Dependency> dependencies;
148
    private final List<Dependency> optionalDependencies;
149

150
    /**
A
alanharder 已提交
151
     * Is this plugin bundled in jenkins.war?
152 153 154
     */
    /*package*/ boolean isBundled;

155 156 157 158 159 160 161 162 163 164 165 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 196 197 198 199 200 201 202
    /**
     * List of plugins that depend on this plugin.
     */
    private Set<String> dependants = Collections.emptySet();

    /**
     * The core can depend on a plugin if it is bundled. Sometimes it's the only thing that
     * depends on the plugin e.g. UI support library bundle plugin.
     */
    private static Set<String> CORE_ONLY_DEPENDANT = ImmutableSet.copyOf(Arrays.asList("jenkins-core"));

    /**
     * Set the list of components that depend on this plugin.
     * @param dependants The list of components that depend on this plugin.
     */
    public void setDependants(@Nonnull Set<String> dependants) {
        this.dependants = dependants;
    }

    /**
     * Get the list of components that depend on this plugin.
     * @return The list of components that depend on this plugin.
     */
    public @Nonnull Set<String> getDependants() {
        if (isBundled && dependants.isEmpty()) {
            return CORE_ONLY_DEPENDANT;
        } else {
            return dependants;
        }
    }

    /**
     * Does this plugin have anything that depends on it.
     * @return {@code true} if something (Jenkins core, or another plugin) depends on this
     * plugin, otherwise {@code false}.
     */
    public boolean hasDependants() {
        return (isBundled || !dependants.isEmpty());
    }
    
    /**
     * Does this plugin depend on any other plugins.
     * @return {@code true} if this plugin depends on other plugins, otherwise {@code false}.
     */
    public boolean hasDependencies() {
        return (dependencies != null && !dependencies.isEmpty());
    }

203 204 205
    @ExportedBean
    public static final class Dependency {
        @Exported
206
        public final String shortName;
207
        @Exported
208
        public final String version;
209
        @Exported
210
        public final boolean optional;
211 212 213

        public Dependency(String s) {
            int idx = s.indexOf(':');
K
kohsuke 已提交
214 215
            if(idx==-1)
                throw new IllegalArgumentException("Illegal dependency specifier "+s);
216
            this.shortName = s.substring(0,idx);
217 218
            String version = s.substring(idx+1);

219
            boolean isOptional = false;
220
            String[] osgiProperties = version.split("[;]");
221 222 223 224 225 226 227
            for (int i = 1; i < osgiProperties.length; i++) {
                String osgiProperty = osgiProperties[i].trim();
                if (osgiProperty.equalsIgnoreCase("resolution:=optional")) {
                    isOptional = true;
                }
            }
            this.optional = isOptional;
228 229 230 231 232
            if (isOptional) {
                this.version = osgiProperties[0];
            } else {
                this.version = version;
            }
233
        }
234 235 236

        @Override
        public String toString() {
237
            return shortName + " (" + version + ") " + (optional ? "optional" : "");
238
        }        
239 240
    }

K
kohsuke 已提交
241 242
    /**
     * @param archive
I
imod 已提交
243
     *      A .jpi archive file jar file, or a .jpl linked plugin.
244 245 246 247 248 249 250 251 252 253
     *  @param manifest
     *  	The manifest for the plugin
     *  @param baseResourceURL
     *  	A URL pointing to the resources for this plugin
     *  @param classLoader
     *  	a classloader that loads classes from this plugin and its dependencies
     *  @param disableFile
     *  	if this file exists on startup, the plugin will not be activated
     *  @param dependencies a list of mandatory dependencies
     *  @param optionalDependencies a list of optional dependencies
K
kohsuke 已提交
254
     */
255
    public PluginWrapper(PluginManager parent, File archive, Manifest manifest, URL baseResourceURL, 
256 257
			ClassLoader classLoader, File disableFile, 
			List<Dependency> dependencies, List<Dependency> optionalDependencies) {
258
        this.parent = parent;
259
		this.manifest = manifest;
K
Kohsuke Kawaguchi 已提交
260
		this.shortName = computeShortName(manifest, archive.getName());
261 262 263 264 265 266
		this.baseResourceURL = baseResourceURL;
		this.classLoader = classLoader;
		this.disableFile = disableFile;
		this.active = !disableFile.exists();
		this.dependencies = dependencies;
		this.optionalDependencies = optionalDependencies;
K
Kohsuke Kawaguchi 已提交
267 268 269 270
        this.archive = archive;
    }

    public String getDisplayName() {
271
        return StringUtils.removeStart(getLongName(), "Jenkins ");
272
    }
K
kohsuke 已提交
273

274 275 276 277
    public Api getApi() {
        return new Api(this);
    }

278 279 280 281
    /**
     * Returns the URL of the index page jelly script.
     */
    public URL getIndexPage() {
282 283 284 285 286 287 288 289
        // In the current impl dependencies are checked first, so the plugin itself
        // will add the last entry in the getResources result.
        URL idx = null;
        try {
            Enumeration<URL> en = classLoader.getResources("index.jelly");
            while (en.hasMoreElements())
                idx = en.nextElement();
        } catch (IOException ignore) { }
290 291 292
        // In case plugin has dependencies but is missing its own index.jelly,
        // check that result has this plugin's artifactId in it:
        return idx != null && idx.toString().contains(shortName) ? idx : null;
293 294
    }

K
Kohsuke Kawaguchi 已提交
295
    static String computeShortName(Manifest manifest, String fileName) {
296 297 298 299 300 301 302 303 304 305 306
        // use the name captured in the manifest, as often plugins
        // depend on the specific short name in its URLs.
        String n = manifest.getMainAttributes().getValue("Short-Name");
        if(n!=null)     return n;

        // maven seems to put this automatically, so good fallback to check.
        n = manifest.getMainAttributes().getValue("Extension-Name");
        if(n!=null)     return n;

        // otherwise infer from the file name, since older plugins don't have
        // this entry.
K
Kohsuke Kawaguchi 已提交
307
        return getBaseName(fileName);
308 309
    }

310
    @Exported
311
    public List<Dependency> getDependencies() {
312 313
        return dependencies;
    }
314

315 316 317
    public List<Dependency> getOptionalDependencies() {
        return optionalDependencies;
    }
K
kohsuke 已提交
318 319 320 321 322


    /**
     * Returns the short name suitable for URL.
     */
323
    @Exported
K
kohsuke 已提交
324 325 326 327
    public String getShortName() {
        return shortName;
    }

328 329 330
    /**
     * Gets the instance of {@link Plugin} contributed by this plugin.
     */
J
Jesse Glick 已提交
331 332 333
    public @CheckForNull Plugin getPlugin() {
        PluginInstanceStore pis = Jenkins.lookup(PluginInstanceStore.class);
        return pis != null ? pis.store.get(this) : null;
334 335
    }

336 337 338 339 340 341
    /**
     * Gets the URL that shows more information about this plugin.
     * @return
     *      null if this information is unavailable.
     * @since 1.283
     */
342
    @Exported
343 344 345 346 347 348
    public String getUrl() {
        // first look for the manifest entry. This is new in maven-hpi-plugin 1.30
        String url = manifest.getMainAttributes().getValue("Url");
        if(url!=null)      return url;

        // fallback to update center metadata
349
        UpdateSite.Plugin ui = getInfo();
350 351 352 353
        if(ui!=null)    return ui.wiki;

        return null;
    }
354 355
    
    
356

357 358 359 360 361
    @Override
    public String toString() {
        return "Plugin:" + getShortName();
    }

K
kohsuke 已提交
362 363 364
    /**
     * Returns a one-line descriptive name of this plugin.
     */
365
    @Exported
K
kohsuke 已提交
366
    public String getLongName() {
367
        String name = manifest.getMainAttributes().getValue("Long-Name");
K
kohsuke 已提交
368 369 370 371
        if(name!=null)      return name;
        return shortName;
    }

372 373 374
    /**
     * Does this plugin supports dynamic loading?
     */
375
    @Exported
376 377 378 379 380 381
    public YesNoMaybe supportsDynamicLoad() {
        String v = manifest.getMainAttributes().getValue("Support-Dynamic-Loading");
        if (v==null) return YesNoMaybe.MAYBE;
        return Boolean.parseBoolean(v) ? YesNoMaybe.YES : YesNoMaybe.NO;
    }

382 383 384
    /**
     * Returns the version number of this plugin
     */
385
    @Exported
386
    public String getVersion() {
K
Kohsuke Kawaguchi 已提交
387 388 389 390
        return getVersionOf(manifest);
    }

    private String getVersionOf(Manifest manifest) {
391 392
        String v = manifest.getMainAttributes().getValue("Plugin-Version");
        if(v!=null)      return v;
K
kohsuke 已提交
393 394 395 396 397

        // plugins generated before maven-hpi-plugin 1.3 should still have this attribute
        v = manifest.getMainAttributes().getValue("Implementation-Version");
        if(v!=null)      return v;

398 399 400
        return "???";
    }

401 402 403
    /**
     * Returns the required Jenkins core version of this plugin.
     * @return the required Jenkins core version of this plugin.
404
     * @since 1.657
405 406 407 408 409 410 411 412 413 414 415
     */
    @Exported
    public @CheckForNull String getRequiredCoreVersion() {
        String v = manifest.getMainAttributes().getValue("Jenkins-Version");
        if (v!= null) return v;

        v = manifest.getMainAttributes().getValue("Hudson-Version");
        if (v!= null) return v;
        return null;
    }

416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
    /**
     * Returns the version number of this plugin
     */
    public VersionNumber getVersionNumber() {
        return new VersionNumber(getVersion());
    }

    /**
     * Returns true if the version of this plugin is older than the given version.
     */
    public boolean isOlderThan(VersionNumber v) {
        try {
            return getVersionNumber().compareTo(v) < 0;
        } catch (IllegalArgumentException e) {
            // if we can't figure out our current version, it probably means it's very old,
            // since the version information is missing only from the very old plugins 
            return true;
        }
    }

K
kohsuke 已提交
436 437 438
    /**
     * Terminates the plugin.
     */
439
    public void stop() {
J
Jesse Glick 已提交
440 441 442 443 444 445 446 447 448 449
        Plugin plugin = getPlugin();
        if (plugin != null) {
            try {
                LOGGER.log(Level.FINE, "Stopping {0}", shortName);
                plugin.stop();
            } catch (Throwable t) {
                LOGGER.log(WARNING, "Failed to shut down " + shortName, t);
            }
        } else {
            LOGGER.log(Level.FINE, "Could not find Plugin instance to stop for {0}", shortName);
K
kohsuke 已提交
450
        }
451 452 453
        // Work around a bug in commons-logging.
        // See http://www.szegedi.org/articles/memleak.html
        LogFactory.release(classLoader);
454 455 456
    }

    public void releaseClassLoader() {
457 458 459 460 461 462
        if (classLoader instanceof Closeable)
            try {
                ((Closeable) classLoader).close();
            } catch (IOException e) {
                LOGGER.log(WARNING, "Failed to shut down classloader",e);
            }
K
kohsuke 已提交
463 464 465
    }

    /**
A
alanharder 已提交
466
     * Enables this plugin next time Jenkins runs.
K
kohsuke 已提交
467 468 469 470 471 472 473
     */
    public void enable() throws IOException {
        if(!disableFile.delete())
            throw new IOException("Failed to delete "+disableFile);
    }

    /**
A
alanharder 已提交
474
     * Disables this plugin next time Jenkins runs.
K
kohsuke 已提交
475 476 477 478 479 480 481 482 483 484
     */
    public void disable() throws IOException {
        // creates an empty file
        OutputStream os = new FileOutputStream(disableFile);
        os.close();
    }

    /**
     * Returns true if this plugin is enabled for this session.
     */
485
    @Exported
K
kohsuke 已提交
486
    public boolean isActive() {
487 488 489 490 491
        return active && !hasCycleDependency();
    }
    
    public boolean hasCycleDependency(){
        return hasCycleDependency;
K
kohsuke 已提交
492 493
    }

494 495 496 497
    public void setHasCycleDependency(boolean hasCycle){
        hasCycleDependency = hasCycle;
    }
    
498
    @Exported
499 500 501 502
    public boolean isBundled() {
        return isBundled;
    }

K
kohsuke 已提交
503 504
    /**
     * If true, the plugin is going to be activated next time
A
alanharder 已提交
505
     * Jenkins runs.
K
kohsuke 已提交
506
     */
507
    @Exported
K
kohsuke 已提交
508 509 510 511
    public boolean isEnabled() {
        return !disableFile.exists();
    }

512
    public Manifest getManifest() {
513 514
        return manifest;
    }
515

516
    public void setPlugin(Plugin plugin) {
517
        Jenkins.lookup(PluginInstanceStore.class).store.put(this,plugin);
518 519 520 521 522 523
        plugin.wrapper = this;
    }

    public String getPluginClass() {
        return manifest.getMainAttributes().getValue("Plugin-Class");
    }
524

525 526 527 528 529 530 531 532 533
    public boolean hasLicensesXml() {
        try {
            new URL(baseResourceURL,"WEB-INF/licenses.xml").openStream().close();
            return true;
        } catch (IOException e) {
            return false;
        }
    }

534 535 536 537 538 539 540 541
    /**
     * Makes sure that all the dependencies exist, and then accept optional dependencies
     * as real dependencies.
     *
     * @throws IOException
     *             thrown if one or several mandatory dependencies doesn't exists.
     */
    /*package*/ void resolvePluginDependencies() throws IOException {
542 543 544 545 546 547 548 549
        if (ENABLE_PLUGIN_DEPENDENCIES_VERSION_CHECK) {
            String requiredCoreVersion = getRequiredCoreVersion();
            if (requiredCoreVersion == null) {
                LOGGER.warning(shortName + " doesn't declare required core version.");
            } else {
                if (Jenkins.getVersion().isOlderThan(new VersionNumber(requiredCoreVersion))) {
                    throw new IOException(shortName + " requires a more recent core version (" + requiredCoreVersion + ") than the current (" + Jenkins.getVersion() + ").");
                }
550 551
            }
        }
552
        List<String> missingDependencies = new ArrayList<String>();
553 554
        List<String> obsoleteDependencies = new ArrayList<String>();
        List<String> disabledDependencies = new ArrayList<String>();
555 556
        // make sure dependencies exist
        for (Dependency d : dependencies) {
557 558
            PluginWrapper dependency = parent.getPlugin(d.shortName);
            if (dependency == null) {
559
                missingDependencies.add(d.toString());
560 561 562 563 564 565 566 567
            } else {
                if (dependency.isActive()) {
                    if (ENABLE_PLUGIN_DEPENDENCIES_VERSION_CHECK && dependency.getVersionNumber().isOlderThan(new VersionNumber(d.version))) {
                        obsoleteDependencies.add(dependency.getShortName() + "(" + dependency.getVersion() + " < " + d.version + ")");
                    }
                } else {
                    disabledDependencies.add(d.toString());
                }
568

569 570
            }
        }
571 572
        // add the optional dependencies that exists
        for (Dependency d : optionalDependencies) {
573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592
            PluginWrapper dependency = parent.getPlugin(d.shortName);
            if (dependency != null && dependency.isActive()) {
                if (ENABLE_PLUGIN_DEPENDENCIES_VERSION_CHECK && dependency.getVersionNumber().isOlderThan(new VersionNumber(d.version))) {
                    obsoleteDependencies.add(dependency.getShortName() + "(" + dependency.getVersion() + " < " + d.version + ")");
                } else {
                    dependencies.add(d);
                }
            }
        }
        StringBuilder messageBuilder = new StringBuilder();
        if (!missingDependencies.isEmpty()) {
            boolean plural = missingDependencies.size() > 1;
            messageBuilder.append(plural ? "Dependencies " : "Dependency ")
                    .append(Util.join(missingDependencies, ", "))
                    .append(" ").append(plural ? "don't" : "doesn't")
                    .append(" exist. ");
        }
        if (!disabledDependencies.isEmpty()) {
            boolean plural = disabledDependencies.size() > 1;
            messageBuilder.append(plural ? "Dependencies " : "Dependency ")
593
                    .append(Util.join(missingDependencies, ", "))
594 595 596 597 598 599 600 601 602 603 604 605 606
                    .append(" ").append(plural ? "are" : "is")
                    .append(" disabled. ");
        }
        if (!obsoleteDependencies.isEmpty()) {
            boolean plural = obsoleteDependencies.size() > 1;
            messageBuilder.append(plural ? "Dependencies " : "Dependency ")
                    .append(Util.join(obsoleteDependencies, ", "))
                    .append(" ").append(plural ? "are" : "is")
                    .append(" older than required.");
        }
        String message = messageBuilder.toString();
        if (!message.isEmpty()) {
            throw new IOException(message);
607 608 609
        }
    }

610 611
    /**
     * If the plugin has {@link #getUpdateInfo() an update},
612
     * returns the {@link hudson.model.UpdateSite.Plugin} object.
K
kohsuke 已提交
613 614 615 616
     *
     * @return
     *      This method may return null &mdash; for example,
     *      the user may have installed a plugin locally developed.
617
     */
618
    public UpdateSite.Plugin getUpdateInfo() {
619
        UpdateCenter uc = Jenkins.getInstance().getUpdateCenter();
620
        UpdateSite.Plugin p = uc.getPlugin(getShortName());
K
kohsuke 已提交
621 622
        if(p!=null && p.isNewerThan(getVersion())) return p;
        return null;
623
    }
624 625
    
    /**
626
     * returns the {@link hudson.model.UpdateSite.Plugin} object, or null.
627
     */
628
    public UpdateSite.Plugin getInfo() {
629
        UpdateCenter uc = Jenkins.getInstance().getUpdateCenter();
630 631
        return uc.getPlugin(getShortName());
    }
632 633 634 635 636 637 638 639

    /**
     * Returns true if this plugin has update in the update center.
     *
     * <p>
     * This method is conservative in the sense that if the version number is incomprehensible,
     * it always returns false.
     */
640
    @Exported
641 642 643
    public boolean hasUpdate() {
        return getUpdateInfo()!=null;
    }
K
kohsuke 已提交
644
    
645
    @Exported
646
    @Deprecated // See https://groups.google.com/d/msg/jenkinsci-dev/kRobm-cxFw8/6V66uhibAwAJ
K
kohsuke 已提交
647
    public boolean isPinned() {
648
        return false;
K
kohsuke 已提交
649
    }
650

K
Kohsuke Kawaguchi 已提交
651 652 653 654 655 656 657 658 659 660
    /**
     * Returns true if this plugin is deleted.
     *
     * The plugin continues to function in this session, but in the next session it'll disappear.
     */
    @Exported
    public boolean isDeleted() {
        return !archive.exists();
    }

M
mindless 已提交
661 662 663 664 665 666 667
    /**
     * Sort by short name.
     */
    public int compareTo(PluginWrapper pw) {
        return shortName.compareToIgnoreCase(pw.shortName);
    }

668 669 670
    /**
     * returns true if backup of previous version of plugin exists
     */
671
    @Exported
672 673 674 675 676 677 678 679
    public boolean isDowngradable() {
        return getBackupFile().exists();
    }

    /**
     * Where is the backup file?
     */
    public File getBackupFile() {
680
        return new File(Jenkins.getInstance().getRootDir(),"plugins/"+getShortName() + ".bak");
681 682 683 684 685 686
    }

    /**
     * returns the version of the backed up plugin,
     * or null if there's no back up.
     */
687
    @Exported
688
    public String getBackupVersion() {
689 690
        File backup = getBackupFile();
        if (backup.exists()) {
691
            try {
692
                JarFile backupPlugin = new JarFile(backup);
693 694 695 696 697
                try {
                    return backupPlugin.getManifest().getMainAttributes().getValue("Plugin-Version");
                } finally {
                    backupPlugin.close();
                }
698
            } catch (IOException e) {
699
                LOGGER.log(WARNING, "Failed to get backup version from " + backup, e);
700 701 702 703 704 705
                return null;
            }
        } else {
            return null;
        }
    }
K
Kohsuke Kawaguchi 已提交
706 707 708 709

    /**
     * Checks if this plugin is pinned and that's forcing us to use an older version than the bundled one.
     */
710
    @Deprecated // See https://groups.google.com/d/msg/jenkinsci-dev/kRobm-cxFw8/6V66uhibAwAJ
K
Kohsuke Kawaguchi 已提交
711
    public boolean isPinningForcingOldVersion() {
712
        return false;
K
Kohsuke Kawaguchi 已提交
713 714
    }

K
kohsuke 已提交
715 716 717 718 719
//
//
// Action methods
//
//
K
Kohsuke Kawaguchi 已提交
720
    @RequirePOST
721
    public HttpResponse doMakeEnabled() throws IOException {
722
        Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
K
kohsuke 已提交
723
        enable();
724
        return HttpResponses.ok();
K
kohsuke 已提交
725
    }
726

K
Kohsuke Kawaguchi 已提交
727
    @RequirePOST
728
    public HttpResponse doMakeDisabled() throws IOException {
729
        Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
K
kohsuke 已提交
730
        disable();
731 732 733
        return HttpResponses.ok();
    }

K
Kohsuke Kawaguchi 已提交
734
    @RequirePOST
735
    @Deprecated
736
    public HttpResponse doPin() throws IOException {
737 738
        // See https://groups.google.com/d/msg/jenkinsci-dev/kRobm-cxFw8/6V66uhibAwAJ
        LOGGER.log(WARNING, "Call to pin plugin has been ignored. Plugin name: " + shortName);
739 740 741
        return HttpResponses.ok();
    }

K
Kohsuke Kawaguchi 已提交
742
    @RequirePOST
743
    @Deprecated
744
    public HttpResponse doUnpin() throws IOException {
745 746
        // See https://groups.google.com/d/msg/jenkinsci-dev/kRobm-cxFw8/6V66uhibAwAJ
        LOGGER.log(WARNING, "Call to unpin plugin has been ignored. Plugin name: " + shortName);
747
        return HttpResponses.ok();
K
kohsuke 已提交
748 749
    }

K
Kohsuke Kawaguchi 已提交
750 751
    @RequirePOST
    public HttpResponse doDoUninstall() throws IOException {
T
tfennelly 已提交
752 753 754
        Jenkins jenkins = Jenkins.getActiveInstance();
        
        jenkins.checkPermission(Jenkins.ADMINISTER);
K
Kohsuke Kawaguchi 已提交
755
        archive.delete();
756 757

        // Redo who depends on who.
T
tfennelly 已提交
758
        jenkins.getPluginManager().resolveDependantPlugins();
759

K
Kohsuke Kawaguchi 已提交
760 761 762
        return HttpResponses.redirectViaContextPath("/pluginManager/installed");   // send back to plugin manager
    }

K
kohsuke 已提交
763 764 765

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

K
Kohsuke Kawaguchi 已提交
766 767 768 769
    /**
     * Name of the plugin manifest file (to help find where we parse them.)
     */
    public static final String MANIFEST_FILENAME = "META-INF/MANIFEST.MF";
K
kohsuke 已提交
770
}