Merge "Make Plugin class abstract"

This commit is contained in:
Shawn Pearce
2013-07-26 16:54:17 +00:00
committed by Gerrit Code Review
3 changed files with 336 additions and 210 deletions

View File

@@ -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<? extends Module> sysModule;
private Class<? extends Module> sshModule;
private Class<? extends Module> httpModule;
private Injector sysInjector;
private Injector sshInjector;
private Injector httpInjector;
private LifecycleManager manager;
private List<ReloadableRegistrationHandle<?>> reloadableHandles;
public JarPlugin(String name,
PluginUser pluginUser,
File srcJar,
FileSnapshot snapshot,
JarFile jarFile,
Manifest manifest,
File dataDir,
ApiType apiType,
ClassLoader classLoader,
@Nullable Class<? extends Module> sysModule,
@Nullable Class<? extends Module> sshModule,
@Nullable Class<? extends Module> 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<Module> 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<Module> 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<Module> 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<File>() {
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);
}
}
}

View File

@@ -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<? extends Module> sysModule;
private Class<? extends Module> sshModule;
private Class<? extends Module> 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<ReloadableRegistrationHandle<?>> 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<? extends Module> sysModule,
@Nullable Class<? extends Module> sshModule,
@Nullable Class<? extends Module> 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<Module> 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<Module> 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<Module> 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<File>() {
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();
}
}

View File

@@ -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<? extends Module> sysModule = load(sysName, pluginLoader);
Class<? extends Module> sshModule = load(sshName, pluginLoader);
Class<? extends Module> 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,