PluginWrapper.java 9.9 KB
Newer Older
K
kohsuke 已提交
1 2
package hudson;

K
kohsuke 已提交
3
import hudson.model.Hudson;
4
import hudson.model.UpdateCenter;
K
kohsuke 已提交
5 6 7 8 9 10 11 12 13 14 15

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.Manifest;
import java.util.logging.Logger;

16 17 18 19
import org.apache.commons.logging.LogFactory;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;

K
kohsuke 已提交
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
/**
 * Represents a Hudson plug-in and associated control information
 * for Hudson to control {@link Plugin}.
 *
 * <p>
 * A plug-in is packaged into a jar file whose extension is <tt>".hpi"</tt>,
 * 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>
 *  <li>Enabled/Disabled. If enabled, Hudson is going to use it
 *      next time Hudson runs. Otherwise the next run will ignore it.
 *  <li>Activated/Deactivated. If activated, that means Hudson is using
 *      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
 */
public final class PluginWrapper {
    /**
     * Plugin manifest.
     * Contains description of the plugin.
     */
    private final Manifest manifest;

    /**
     * Loaded plugin instance.
     * Null if disabled.
     */
53
    private Plugin plugin;
K
kohsuke 已提交
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74

    /**
     * {@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
     * <tt>hudson/plugin/SHORTNAME/</tt>.
     */
    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;

    /**
75 76 77 78
     * Short name of the plugin. The artifact Id of the plugin.
     * This is also used in the URL within Hudson, so it needs
     * to remain stable even when the *.hpi file name is changed
     * (like Maven does.)
K
kohsuke 已提交
79 80 81
     */
    private final String shortName;

82
	/**
83 84 85 86 87
     * 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;

88 89
    private final List<Dependency> dependencies;
	private final List<Dependency> optionalDependencies;
90

91
    static final class Dependency {
92 93
        public final String shortName;
        public final String version;
94
        public final boolean optional;
95 96 97

        public Dependency(String s) {
            int idx = s.indexOf(':');
K
kohsuke 已提交
98 99
            if(idx==-1)
                throw new IllegalArgumentException("Illegal dependency specifier "+s);
100 101
            this.shortName = s.substring(0,idx);
            this.version = s.substring(idx+1);
102 103 104 105 106 107 108 109 110 111
            
            boolean isOptional = false;
            String[] osgiProperties = s.split(";");
            for (int i = 1; i < osgiProperties.length; i++) {
                String osgiProperty = osgiProperties[i].trim();
                if (osgiProperty.equalsIgnoreCase("resolution:=optional")) {
                    isOptional = true;
                }
            }
            this.optional = isOptional;
112
        }
113 114 115 116 117

        @Override
        public String toString() {
            return shortName + " (" + version + ")";
        }        
118 119
    }

K
kohsuke 已提交
120 121 122
    /**
     * @param archive
     *      A .hpi archive file jar file, or a .hpl linked plugin.
123 124 125 126 127 128 129 130 131 132
     *  @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 已提交
133
     */
134 135 136 137 138 139 140 141 142 143 144 145
	public PluginWrapper(File archive, Manifest manifest, URL baseResourceURL, 
			ClassLoader classLoader, File disableFile, 
			List<Dependency> dependencies, List<Dependency> optionalDependencies) {
		this.manifest = manifest;
		this.shortName = computeShortName(manifest, archive);
		this.baseResourceURL = baseResourceURL;
		this.classLoader = classLoader;
		this.disableFile = disableFile;
		this.active = !disableFile.exists();
		this.dependencies = dependencies;
		this.optionalDependencies = optionalDependencies;
	}
K
kohsuke 已提交
146

147 148 149 150 151
    /**
     * Returns the URL of the index page jelly script.
     */
    public URL getIndexPage() {
        return classLoader.getResource("index.jelly");
152 153
    }

154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
    private String computeShortName(Manifest manifest, File archive) {
        // 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.
        return getBaseName(archive);
    }

K
kohsuke 已提交
169

170
    /**
171
     * Gets the "abc" portion from "abc.ext".
172
     */
173 174 175 176 177 178
    static String getBaseName(File archive) {
        String n = archive.getName();
        int idx = n.lastIndexOf('.');
        if(idx>=0)
            n = n.substring(0,idx);
        return n;
179 180
    }

181 182 183
    public List<Dependency> getDependencies() {
		return dependencies;
	}
184

185 186 187
	public List<Dependency> getOptionalDependencies() {
		return optionalDependencies;
	}
K
kohsuke 已提交
188 189 190 191 192 193 194 195 196


    /**
     * Returns the short name suitable for URL.
     */
    public String getShortName() {
        return shortName;
    }

197 198 199 200 201 202 203
    /**
     * Gets the instance of {@link Plugin} contributed by this plugin.
     */
    public Plugin getPlugin() {
        return plugin;
    }

204 205 206 207 208
    @Override
    public String toString() {
        return "Plugin:" + getShortName();
    }

K
kohsuke 已提交
209 210 211 212
    /**
     * Returns a one-line descriptive name of this plugin.
     */
    public String getLongName() {
213
        String name = manifest.getMainAttributes().getValue("Long-Name");
K
kohsuke 已提交
214 215 216 217
        if(name!=null)      return name;
        return shortName;
    }

218 219 220 221 222 223
    /**
     * Returns the version number of this plugin
     */
    public String getVersion() {
        String v = manifest.getMainAttributes().getValue("Plugin-Version");
        if(v!=null)      return v;
K
kohsuke 已提交
224 225 226 227 228

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

229 230 231
        return "???";
    }

K
kohsuke 已提交
232 233 234 235 236 237 238 239 240 241 242
    /**
     * Terminates the plugin.
     */
    void stop() {
        LOGGER.info("Stopping "+shortName);
        try {
            plugin.stop();
        } catch(Throwable t) {
            System.err.println("Failed to shut down "+shortName);
            System.err.println(t);
        }
243 244 245
        // Work around a bug in commons-logging.
        // See http://www.szegedi.org/articles/memleak.html
        LogFactory.release(classLoader);
K
kohsuke 已提交
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
    }

    /**
     * Enables this plugin next time Hudson runs.
     */
    public void enable() throws IOException {
        if(!disableFile.delete())
            throw new IOException("Failed to delete "+disableFile);
    }

    /**
     * Disables this plugin next time Hudson runs.
     */
    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.
     */
    public boolean isActive() {
269
        return active;
K
kohsuke 已提交
270 271 272 273 274 275 276 277 278 279
    }

    /**
     * If true, the plugin is going to be activated next time
     * Hudson runs.
     */
    public boolean isEnabled() {
        return !disableFile.exists();
    }

280 281 282 283 284 285 286 287 288 289 290 291 292
    public Manifest getManifest() {
		return manifest;
	}

	public void setPlugin(Plugin plugin) {
		this.plugin = plugin;
		plugin.wrapper = this;
	}
	
	public String getPluginClass() {
		return manifest.getMainAttributes().getValue("Plugin-Class");		
	}

293 294 295
    /**
     * If the plugin has {@link #getUpdateInfo() an update},
     * returns the {@link UpdateCenter.Plugin} object.
K
kohsuke 已提交
296 297 298 299
     *
     * @return
     *      This method may return null &mdash; for example,
     *      the user may have installed a plugin locally developed.
300 301 302 303
     */
    public UpdateCenter.Plugin getUpdateInfo() {
        UpdateCenter uc = Hudson.getInstance().getUpdateCenter();
        UpdateCenter.Plugin p = uc.getPlugin(getShortName());
K
kohsuke 已提交
304 305
        if(p!=null && p.isNewerThan(getVersion())) return p;
        return null;
306
    }
307 308 309 310 311 312 313 314
    
    /**
     * returns the {@link UpdateCenter.Plugin} object, or null.
     */
    public UpdateCenter.Plugin getInfo() {
        UpdateCenter uc = Hudson.getInstance().getUpdateCenter();
        return uc.getPlugin(getShortName());
    }
315 316 317 318 319 320 321 322 323 324 325 326

    /**
     * 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.
     */
    public boolean hasUpdate() {
        return getUpdateInfo()!=null;
    }

K
kohsuke 已提交
327 328 329 330 331 332
//
//
// Action methods
//
//
    public void doMakeEnabled(StaplerRequest req, StaplerResponse rsp) throws IOException {
333
    Hudson.getInstance().checkPermission(Hudson.ADMINISTER);
K
kohsuke 已提交
334 335 336 337
        enable();
        rsp.setStatus(200);
    }
    public void doMakeDisabled(StaplerRequest req, StaplerResponse rsp) throws IOException {
338
        Hudson.getInstance().checkPermission(Hudson.ADMINISTER);
K
kohsuke 已提交
339 340 341 342 343 344 345 346
        disable();
        rsp.setStatus(200);
    }


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

}