diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPlugin.java new file mode 100644 index 0000000000..deb532f4d8 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarPlugin.java @@ -0,0 +1,301 @@ +// Copyright (C) 2012 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.plugins; + +import com.google.common.base.Strings; +import com.google.common.collect.Lists; +import com.google.gerrit.extensions.annotations.PluginData; +import com.google.gerrit.extensions.annotations.PluginName; +import com.google.gerrit.extensions.registration.RegistrationHandle; +import com.google.gerrit.extensions.registration.ReloadableRegistrationHandle; +import com.google.gerrit.lifecycle.LifecycleManager; +import com.google.gerrit.server.PluginUser; +import com.google.gerrit.server.util.RequestContext; +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Module; +import com.google.inject.Provider; +import com.google.inject.ProvisionException; + +import org.eclipse.jgit.internal.storage.file.FileSnapshot; + +import java.io.File; +import java.util.List; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +import javax.annotation.Nullable; + +class JarPlugin extends Plugin { + + /** Unique key that changes whenever a plugin reloads. */ + public static final class CacheKey { + private final String name; + + CacheKey(String name) { + this.name = name; + } + + @Override + public String toString() { + int id = System.identityHashCode(this); + return String.format("Plugin[%s@%x]", name, id); + } + } + + static { + // Guice logs warnings about multiple injectors being created. + // Silence this in case HTTP plugins are used. + java.util.logging.Logger.getLogger("com.google.inject.servlet.GuiceFilter") + .setLevel(java.util.logging.Level.OFF); + } + + static ApiType getApiType(Manifest manifest) throws InvalidPluginException { + Attributes main = manifest.getMainAttributes(); + String v = main.getValue("Gerrit-ApiType"); + if (Strings.isNullOrEmpty(v) + || ApiType.EXTENSION.name().equalsIgnoreCase(v)) { + return ApiType.EXTENSION; + } else if (ApiType.PLUGIN.name().equalsIgnoreCase(v)) { + return ApiType.PLUGIN; + } else if (ApiType.JS.name().equalsIgnoreCase(v)) { + return ApiType.JS; + } else { + throw new InvalidPluginException("Invalid Gerrit-ApiType: " + v); + } + } + + private final FileSnapshot snapshot; + private final JarFile jarFile; + private final Manifest manifest; + private final File dataDir; + private final ClassLoader classLoader; + private final boolean disabled; + private Class sysModule; + private Class sshModule; + private Class httpModule; + + private Injector sysInjector; + private Injector sshInjector; + private Injector httpInjector; + private LifecycleManager manager; + private List> reloadableHandles; + + public JarPlugin(String name, + PluginUser pluginUser, + File srcJar, + FileSnapshot snapshot, + JarFile jarFile, + Manifest manifest, + File dataDir, + ApiType apiType, + ClassLoader classLoader, + @Nullable Class sysModule, + @Nullable Class sshModule, + @Nullable Class httpModule) { + super(name, srcJar, pluginUser, snapshot, apiType); + this.snapshot = snapshot; + this.jarFile = jarFile; + this.manifest = manifest; + this.dataDir = dataDir; + this.classLoader = classLoader; + this.disabled = srcJar.getName().endsWith(".disabled"); + this.sysModule = sysModule; + this.sshModule = sshModule; + this.httpModule = httpModule; + } + + File getSrcJar() { + return getSrcFile(); + } + + @Nullable + public String getVersion() { + Attributes main = manifest.getMainAttributes(); + return main.getValue(Attributes.Name.IMPLEMENTATION_VERSION); + } + + boolean canReload() { + Attributes main = manifest.getMainAttributes(); + String v = main.getValue("Gerrit-ReloadMode"); + if (Strings.isNullOrEmpty(v) || "reload".equalsIgnoreCase(v)) { + return true; + } else if ("restart".equalsIgnoreCase(v)) { + return false; + } else { + PluginLoader.log.warn(String.format( + "Plugin %s has invalid Gerrit-ReloadMode %s; assuming restart", + getName(), v)); + return false; + } + } + + boolean isModified(File jar) { + return snapshot.lastModified() != jar.lastModified(); + } + + public boolean isDisabled() { + return disabled; + } + + void start(PluginGuiceEnvironment env) throws Exception { + RequestContext oldContext = env.enter(this); + try { + startPlugin(env); + } finally { + env.exit(oldContext); + } + } + + private void startPlugin(PluginGuiceEnvironment env) throws Exception { + Injector root = newRootInjector(env); + manager = new LifecycleManager(); + + AutoRegisterModules auto = null; + if (sysModule == null && sshModule == null && httpModule == null) { + auto = new AutoRegisterModules(getName(), env, jarFile, classLoader); + auto.discover(); + } + + if (sysModule != null) { + sysInjector = root.createChildInjector(root.getInstance(sysModule)); + manager.add(sysInjector); + } else if (auto != null && auto.sysModule != null) { + sysInjector = root.createChildInjector(auto.sysModule); + manager.add(sysInjector); + } else { + sysInjector = root; + } + + if (env.hasSshModule()) { + List modules = Lists.newLinkedList(); + if (getApiType() == ApiType.PLUGIN) { + modules.add(env.getSshModule()); + } + if (sshModule != null) { + modules.add(sysInjector.getInstance(sshModule)); + sshInjector = sysInjector.createChildInjector(modules); + manager.add(sshInjector); + } else if (auto != null && auto.sshModule != null) { + modules.add(auto.sshModule); + sshInjector = sysInjector.createChildInjector(modules); + manager.add(sshInjector); + } + } + + if (env.hasHttpModule()) { + List modules = Lists.newLinkedList(); + if (getApiType() == ApiType.PLUGIN) { + modules.add(env.getHttpModule()); + } + if (httpModule != null) { + modules.add(sysInjector.getInstance(httpModule)); + httpInjector = sysInjector.createChildInjector(modules); + manager.add(httpInjector); + } else if (auto != null && auto.httpModule != null) { + modules.add(auto.httpModule); + httpInjector = sysInjector.createChildInjector(modules); + manager.add(httpInjector); + } + } + + manager.start(); + } + + private Injector newRootInjector(final PluginGuiceEnvironment env) { + List modules = Lists.newArrayListWithCapacity(4); + if (getApiType() == ApiType.PLUGIN) { + modules.add(env.getSysModule()); + } + modules.add(new AbstractModule() { + @Override + protected void configure() { + bind(PluginUser.class).toInstance(getPluginUser()); + bind(String.class) + .annotatedWith(PluginName.class) + .toInstance(getName()); + + bind(File.class) + .annotatedWith(PluginData.class) + .toProvider(new Provider() { + private volatile boolean ready; + + @Override + public File get() { + if (!ready) { + synchronized (dataDir) { + if (!dataDir.exists() && !dataDir.mkdirs()) { + throw new ProvisionException(String.format( + "Cannot create %s for plugin %s", + dataDir.getAbsolutePath(), getName())); + } + ready = true; + } + } + return dataDir; + } + }); + } + }); + return Guice.createInjector(modules); + } + + void stop(PluginGuiceEnvironment env) { + if (manager != null) { + RequestContext oldContext = env.enter(this); + try { + manager.stop(); + } finally { + env.exit(oldContext); + } + manager = null; + sysInjector = null; + sshInjector = null; + httpInjector = null; + } + } + + public JarFile getJarFile() { + return jarFile; + } + + public Injector getSysInjector() { + return sysInjector; + } + + @Nullable + public Injector getSshInjector() { + return sshInjector; + } + + @Nullable + public Injector getHttpInjector() { + return httpInjector; + } + + public void add(RegistrationHandle handle) { + if (manager != null) { + if (handle instanceof ReloadableRegistrationHandle) { + if (reloadableHandles == null) { + reloadableHandles = Lists.newArrayList(); + } + reloadableHandles.add((ReloadableRegistrationHandle) handle); + } + manager.add(handle); + } + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java index 8e7192eab3..0c69ee724c 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java @@ -16,19 +16,11 @@ package com.google.gerrit.server.plugins; import com.google.common.base.Strings; import com.google.common.collect.Lists; -import com.google.gerrit.extensions.annotations.PluginData; -import com.google.gerrit.extensions.annotations.PluginName; import com.google.gerrit.extensions.registration.RegistrationHandle; import com.google.gerrit.extensions.registration.ReloadableRegistrationHandle; import com.google.gerrit.lifecycle.LifecycleManager; import com.google.gerrit.server.PluginUser; -import com.google.gerrit.server.util.RequestContext; -import com.google.inject.AbstractModule; -import com.google.inject.Guice; import com.google.inject.Injector; -import com.google.inject.Module; -import com.google.inject.Provider; -import com.google.inject.ProvisionException; import org.eclipse.jgit.internal.storage.file.FileSnapshot; @@ -41,7 +33,7 @@ import java.util.jar.Manifest; import javax.annotation.Nullable; -public class Plugin { +public abstract class Plugin { public static enum ApiType { EXTENSION, PLUGIN, JS; } @@ -61,13 +53,6 @@ public class Plugin { } } - static { - // Guice logs warnings about multiple injectors being created. - // Silence this in case HTTP plugins are used. - java.util.logging.Logger.getLogger("com.google.inject.servlet.GuiceFilter") - .setLevel(java.util.logging.Level.OFF); - } - static ApiType getApiType(Manifest manifest) throws InvalidPluginException { Attributes main = manifest.getMainAttributes(); String v = main.getValue("Gerrit-ApiType"); @@ -83,238 +68,72 @@ public class Plugin { } } - private final CacheKey cacheKey; private final String name; - private final PluginUser pluginUser; - private final File srcJar; - private final FileSnapshot snapshot; - private final JarFile jarFile; - private final Manifest manifest; - private final File dataDir; + private final File srcFile; private final ApiType apiType; - private final ClassLoader classLoader; private final boolean disabled; - private Class sysModule; - private Class sshModule; - private Class httpModule; + private final CacheKey cacheKey; + private final PluginUser pluginUser; + private final FileSnapshot snapshot; + + protected LifecycleManager manager; - private Injector sysInjector; - private Injector sshInjector; - private Injector httpInjector; - private LifecycleManager manager; private List> reloadableHandles; public Plugin(String name, + File srcFile, PluginUser pluginUser, - File srcJar, FileSnapshot snapshot, - JarFile jarFile, - Manifest manifest, - File dataDir, - ApiType apiType, - ClassLoader classLoader, - @Nullable Class sysModule, - @Nullable Class sshModule, - @Nullable Class httpModule) { - this.cacheKey = new CacheKey(name); - this.pluginUser = pluginUser; + ApiType apiType) { this.name = name; - this.srcJar = srcJar; - this.snapshot = snapshot; - this.jarFile = jarFile; - this.manifest = manifest; - this.dataDir = dataDir; + this.srcFile = srcFile; this.apiType = apiType; - this.classLoader = classLoader; - this.disabled = srcJar.getName().endsWith(".disabled"); - this.sysModule = sysModule; - this.sshModule = sshModule; - this.httpModule = httpModule; + this.snapshot = snapshot; + this.pluginUser = pluginUser; + this.cacheKey = new Plugin.CacheKey(name); + this.disabled = srcFile.getName().endsWith(".disabled"); } - File getSrcJar() { - return srcJar; + File getSrcFile() { + return srcFile; } PluginUser getPluginUser() { return pluginUser; } - public CacheKey getCacheKey() { - return cacheKey; - } - public String getName() { return name; } @Nullable - public String getVersion() { - Attributes main = manifest.getMainAttributes(); - return main.getValue(Attributes.Name.IMPLEMENTATION_VERSION); - } + public abstract String getVersion(); public ApiType getApiType() { return apiType; } - boolean canReload() { - Attributes main = manifest.getMainAttributes(); - String v = main.getValue("Gerrit-ReloadMode"); - if (Strings.isNullOrEmpty(v) || "reload".equalsIgnoreCase(v)) { - return true; - } else if ("restart".equalsIgnoreCase(v)) { - return false; - } else { - PluginLoader.log.warn(String.format( - "Plugin %s has invalid Gerrit-ReloadMode %s; assuming restart", - name, v)); - return false; - } - } - - boolean isModified(File jar) { - return snapshot.lastModified() != jar.lastModified(); + public Plugin.CacheKey getCacheKey() { + return cacheKey; } public boolean isDisabled() { return disabled; } - void start(PluginGuiceEnvironment env) throws Exception { - RequestContext oldContext = env.enter(this); - try { - startPlugin(env); - } finally { - env.exit(oldContext); - } - } + abstract void start(PluginGuiceEnvironment env) throws Exception; - private void startPlugin(PluginGuiceEnvironment env) throws Exception { - Injector root = newRootInjector(env); - manager = new LifecycleManager(); + abstract void stop(PluginGuiceEnvironment env); - AutoRegisterModules auto = null; - if (sysModule == null && sshModule == null && httpModule == null) { - auto = new AutoRegisterModules(name, env, jarFile, classLoader); - auto.discover(); - } + public abstract JarFile getJarFile(); - if (sysModule != null) { - sysInjector = root.createChildInjector(root.getInstance(sysModule)); - manager.add(sysInjector); - } else if (auto != null && auto.sysModule != null) { - sysInjector = root.createChildInjector(auto.sysModule); - manager.add(sysInjector); - } else { - sysInjector = root; - } - - if (env.hasSshModule()) { - List modules = Lists.newLinkedList(); - if (apiType == ApiType.PLUGIN) { - modules.add(env.getSshModule()); - } - if (sshModule != null) { - modules.add(sysInjector.getInstance(sshModule)); - sshInjector = sysInjector.createChildInjector(modules); - manager.add(sshInjector); - } else if (auto != null && auto.sshModule != null) { - modules.add(auto.sshModule); - sshInjector = sysInjector.createChildInjector(modules); - manager.add(sshInjector); - } - } - - if (env.hasHttpModule()) { - List modules = Lists.newLinkedList(); - if (apiType == ApiType.PLUGIN) { - modules.add(env.getHttpModule()); - } - if (httpModule != null) { - modules.add(sysInjector.getInstance(httpModule)); - httpInjector = sysInjector.createChildInjector(modules); - manager.add(httpInjector); - } else if (auto != null && auto.httpModule != null) { - modules.add(auto.httpModule); - httpInjector = sysInjector.createChildInjector(modules); - manager.add(httpInjector); - } - } - - manager.start(); - } - - private Injector newRootInjector(final PluginGuiceEnvironment env) { - List modules = Lists.newArrayListWithCapacity(4); - if (apiType == ApiType.PLUGIN) { - modules.add(env.getSysModule()); - } - modules.add(new AbstractModule() { - @Override - protected void configure() { - bind(PluginUser.class).toInstance(pluginUser); - bind(String.class) - .annotatedWith(PluginName.class) - .toInstance(name); - - bind(File.class) - .annotatedWith(PluginData.class) - .toProvider(new Provider() { - private volatile boolean ready; - - @Override - public File get() { - if (!ready) { - synchronized (dataDir) { - if (!dataDir.exists() && !dataDir.mkdirs()) { - throw new ProvisionException(String.format( - "Cannot create %s for plugin %s", - dataDir.getAbsolutePath(), name)); - } - ready = true; - } - } - return dataDir; - } - }); - } - }); - return Guice.createInjector(modules); - } - - void stop(PluginGuiceEnvironment env) { - if (manager != null) { - RequestContext oldContext = env.enter(this); - try { - manager.stop(); - } finally { - env.exit(oldContext); - } - manager = null; - sysInjector = null; - sshInjector = null; - httpInjector = null; - } - } - - public JarFile getJarFile() { - return jarFile; - } - - public Injector getSysInjector() { - return sysInjector; - } + public abstract Injector getSysInjector(); @Nullable - public Injector getSshInjector() { - return sshInjector; - } + public abstract Injector getSshInjector(); @Nullable - public Injector getHttpInjector() { - return httpInjector; - } + public abstract Injector getHttpInjector(); public void add(RegistrationHandle handle) { if (manager != null) { @@ -339,4 +158,10 @@ public class Plugin { public String toString() { return "Plugin [" + name + "]"; } + + abstract boolean canReload(); + + boolean isModified(File jar) { + return snapshot.lastModified() != jar.lastModified(); + } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java index 035592cd48..073f012cf6 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java @@ -213,7 +213,7 @@ public class PluginLoader implements LifecycleListener { log.info(String.format("Disabling plugin %s", name)); File off = new File(pluginsDir, active.getName() + ".jar.disabled"); - active.getSrcJar().renameTo(off); + active.getSrcFile().renameTo(off); unloadPlugin(active); try { @@ -240,7 +240,7 @@ public class PluginLoader implements LifecycleListener { log.info(String.format("Enabling plugin %s", name)); File on = new File(pluginsDir, off.getName() + ".jar"); - off.getSrcJar().renameTo(on); + off.getSrcFile().renameTo(on); disabled.remove(name); runPlugin(name, on, null); @@ -303,7 +303,7 @@ public class PluginLoader implements LifecycleListener { String name = active.getName(); try { log.info(String.format("Reloading plugin %s", name)); - runPlugin(name, active.getSrcJar(), active); + runPlugin(name, active.getSrcFile(), active); } catch (PluginInstallException e) { log.warn(String.format("Cannot reload plugin %s", name), e.getCause()); throw e; @@ -469,7 +469,7 @@ public class PluginLoader implements LifecycleListener { Class sysModule = load(sysName, pluginLoader); Class sshModule = load(sshName, pluginLoader); Class httpModule = load(httpName, pluginLoader); - Plugin plugin = new Plugin(name, pluginUserFactory.create(name), + Plugin plugin = new JarPlugin(name, pluginUserFactory.create(name), srcJar, snapshot, jarFile, manifest, new File(dataDir, name), type, pluginLoader,