Support serving static/ and Documentation/ from plugins
The static/ and Documentation/ resource directories of a plugin can be served over HTTP for any loaded and running plugin, even if it has no other HTTP handlers. This permits a plugin to supply icons or other graphics for the web UI, or documentation content to help users learn how to use the plugin. Change-Id: I267176cc76e161617d780438f88531fc50c1c2b8
This commit is contained in:
		| @@ -15,20 +15,31 @@ | ||||
| package com.google.gerrit.server.plugins; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.lang.ref.ReferenceQueue; | ||||
| import java.lang.ref.WeakReference; | ||||
| import java.util.jar.JarFile; | ||||
|  | ||||
| class CleanupHandle extends WeakReference<ClassLoader> { | ||||
|   private final File tmpFile; | ||||
|   private final JarFile jarFile; | ||||
|  | ||||
|   CleanupHandle(File jarFile, | ||||
|   CleanupHandle(File tmpFile, | ||||
|       JarFile jarFile, | ||||
|       ClassLoader ref, | ||||
|       ReferenceQueue<ClassLoader> queue) { | ||||
|     super(ref, queue); | ||||
|     this.tmpFile = jarFile; | ||||
|     this.tmpFile = tmpFile; | ||||
|     this.jarFile = jarFile; | ||||
|   } | ||||
|  | ||||
|   void cleanup() { | ||||
|     tmpFile.delete(); | ||||
|     try { | ||||
|       jarFile.close(); | ||||
|     } catch (IOException err) { | ||||
|     } | ||||
|     if (!tmpFile.delete() && tmpFile.exists()) { | ||||
|       PluginLoader.log.warn("Cannot delete " + tmpFile.getAbsolutePath()); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -27,6 +27,7 @@ import org.eclipse.jgit.storage.file.FileSnapshot; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.util.jar.Attributes; | ||||
| import java.util.jar.JarFile; | ||||
| import java.util.jar.Manifest; | ||||
|  | ||||
| import javax.annotation.Nullable; | ||||
| @@ -40,9 +41,10 @@ public class Plugin { | ||||
|   } | ||||
|  | ||||
|   private final String name; | ||||
|   private final File jar; | ||||
|   private final Manifest manifest; | ||||
|   private final File srcJar; | ||||
|   private final FileSnapshot snapshot; | ||||
|   private final JarFile jarFile; | ||||
|   private final Manifest manifest; | ||||
|   private Class<? extends Module> sysModule; | ||||
|   private Class<? extends Module> sshModule; | ||||
|   private Class<? extends Module> httpModule; | ||||
| @@ -53,23 +55,25 @@ public class Plugin { | ||||
|   private LifecycleManager manager; | ||||
|  | ||||
|   public Plugin(String name, | ||||
|       File jar, | ||||
|       Manifest manifest, | ||||
|       File srcJar, | ||||
|       FileSnapshot snapshot, | ||||
|       JarFile jarFile, | ||||
|       Manifest manifest, | ||||
|       @Nullable Class<? extends Module> sysModule, | ||||
|       @Nullable Class<? extends Module> sshModule, | ||||
|       @Nullable Class<? extends Module> httpModule) { | ||||
|     this.name = name; | ||||
|     this.jar = jar; | ||||
|     this.manifest = manifest; | ||||
|     this.srcJar = srcJar; | ||||
|     this.snapshot = snapshot; | ||||
|     this.jarFile = jarFile; | ||||
|     this.manifest = manifest; | ||||
|     this.sysModule = sysModule; | ||||
|     this.sshModule = sshModule; | ||||
|     this.httpModule = httpModule; | ||||
|   } | ||||
|  | ||||
|   File getJar() { | ||||
|     return jar; | ||||
|   File getSrcJar() { | ||||
|     return srcJar; | ||||
|   } | ||||
|  | ||||
|   public String getName() { | ||||
| @@ -151,6 +155,10 @@ public class Plugin { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   public JarFile getJarFile() { | ||||
|     return jarFile; | ||||
|   } | ||||
|  | ||||
|   @Nullable | ||||
|   public Injector getSshInjector() { | ||||
|     return sshInjector; | ||||
|   | ||||
| @@ -169,7 +169,7 @@ public class PluginLoader implements LifecycleListener { | ||||
|  | ||||
|         log.info(String.format("Disabling plugin %s", name)); | ||||
|         File off = new File(pluginsDir, active.getName() + ".jar.disabled"); | ||||
|         active.getJar().renameTo(off); | ||||
|         active.getSrcJar().renameTo(off); | ||||
|  | ||||
|         active.stop(); | ||||
|         running.remove(name); | ||||
| @@ -304,34 +304,45 @@ public class PluginLoader implements LifecycleListener { | ||||
|     return 0 < ext ? name.substring(0, ext) : name; | ||||
|   } | ||||
|  | ||||
|   private Plugin loadPlugin(String name, File jarFile, FileSnapshot snapshot) | ||||
|   private Plugin loadPlugin(String name, File srcJar, FileSnapshot snapshot) | ||||
|       throws IOException, ClassNotFoundException { | ||||
|     File tmp; | ||||
|     FileInputStream in = new FileInputStream(jarFile); | ||||
|     FileInputStream in = new FileInputStream(srcJar); | ||||
|     try { | ||||
|       tmp = asTemp(in, tempNameFor(name), ".jar", tmpDir); | ||||
|     } finally { | ||||
|       in.close(); | ||||
|     } | ||||
|  | ||||
|     Manifest manifest = new JarFile(tmp).getManifest(); | ||||
|     Attributes main = manifest.getMainAttributes(); | ||||
|     String sysName = main.getValue("Gerrit-Module"); | ||||
|     String sshName = main.getValue("Gerrit-SshModule"); | ||||
|     String httpName = main.getValue("Gerrit-HttpModule"); | ||||
|     JarFile jarFile = new JarFile(tmp); | ||||
|     boolean keep = false; | ||||
|     try { | ||||
|       Manifest manifest = jarFile.getManifest(); | ||||
|       Attributes main = manifest.getMainAttributes(); | ||||
|       String sysName = main.getValue("Gerrit-Module"); | ||||
|       String sshName = main.getValue("Gerrit-SshModule"); | ||||
|       String httpName = main.getValue("Gerrit-HttpModule"); | ||||
|  | ||||
|     URL[] urls = {tmp.toURI().toURL()}; | ||||
|     ClassLoader parentLoader = PluginLoader.class.getClassLoader(); | ||||
|     ClassLoader pluginLoader = new URLClassLoader(urls, parentLoader); | ||||
|     cleanupHandles.put( | ||||
|         new CleanupHandle(tmp, pluginLoader, cleanupQueue), | ||||
|         Boolean.TRUE); | ||||
|       URL[] urls = {tmp.toURI().toURL()}; | ||||
|       ClassLoader parentLoader = PluginLoader.class.getClassLoader(); | ||||
|       ClassLoader pluginLoader = new URLClassLoader(urls, parentLoader); | ||||
|       cleanupHandles.put( | ||||
|           new CleanupHandle(tmp, jarFile, pluginLoader, cleanupQueue), | ||||
|           Boolean.TRUE); | ||||
|  | ||||
|     Class<? extends Module> sysModule = load(sysName, pluginLoader); | ||||
|     Class<? extends Module> sshModule = load(sshName, pluginLoader); | ||||
|     Class<? extends Module> httpModule = load(httpName, pluginLoader); | ||||
|     return new Plugin(name, jarFile, manifest, snapshot, | ||||
|         sysModule, sshModule, httpModule); | ||||
|       Class<? extends Module> sysModule = load(sysName, pluginLoader); | ||||
|       Class<? extends Module> sshModule = load(sshName, pluginLoader); | ||||
|       Class<? extends Module> httpModule = load(httpName, pluginLoader); | ||||
|       keep = true; | ||||
|       return new Plugin(name, | ||||
|           srcJar, snapshot, | ||||
|           jarFile, manifest, | ||||
|           sysModule, sshModule, httpModule); | ||||
|     } finally { | ||||
|       if (!keep) { | ||||
|         jarFile.close(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private static String tempNameFor(String name) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Shawn O. Pearce
					Shawn O. Pearce