Add support for HTTP plugins
Plugins may contribute to the /plugins/NAME/ URL space by providing a ServletModule in the manifest using Gerrit-HttpModule and binding servlets and filters using Guice bindings. All names are relative to the plugin's directory, so serve("/").with(IndexServlet.class); will handle /plugins/NAME/ and not "/" on the server. This makes a plugin automatically relocatable to match its SSH command name or the name in $site_dir/plugins. Change-Id: I17e3007f0310d2bf4989d652f18864a77c5d5f2e
This commit is contained in:

committed by
gerrit code review

parent
9c11044bf7
commit
2aefecf604
@@ -20,6 +20,7 @@ import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.servlet.GuiceFilter;
|
||||
|
||||
import org.eclipse.jgit.storage.file.FileSnapshot;
|
||||
|
||||
@@ -30,15 +31,24 @@ import java.util.jar.Manifest;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
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(GuiceFilter.class.getName())
|
||||
.setLevel(java.util.logging.Level.OFF);
|
||||
}
|
||||
|
||||
private final String name;
|
||||
private final File jar;
|
||||
private final Manifest manifest;
|
||||
private final FileSnapshot snapshot;
|
||||
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;
|
||||
|
||||
public Plugin(String name,
|
||||
@@ -46,13 +56,15 @@ public class Plugin {
|
||||
Manifest manifest,
|
||||
FileSnapshot snapshot,
|
||||
@Nullable Class<? extends Module> sysModule,
|
||||
@Nullable Class<? extends Module> sshModule) {
|
||||
@Nullable Class<? extends Module> sshModule,
|
||||
@Nullable Class<? extends Module> httpModule) {
|
||||
this.name = name;
|
||||
this.jar = jar;
|
||||
this.manifest = manifest;
|
||||
this.snapshot = snapshot;
|
||||
this.sysModule = sysModule;
|
||||
this.sshModule = sshModule;
|
||||
this.httpModule = httpModule;
|
||||
}
|
||||
|
||||
File getJar() {
|
||||
@@ -90,6 +102,13 @@ public class Plugin {
|
||||
manager.add(sshInjector);
|
||||
}
|
||||
|
||||
if (httpModule != null && env.hasHttpModule()) {
|
||||
httpInjector = sysInjector.createChildInjector(
|
||||
env.getHttpModule(),
|
||||
sysInjector.getInstance(httpModule));
|
||||
manager.add(httpInjector);
|
||||
}
|
||||
|
||||
manager.start();
|
||||
env.onStartPlugin(this);
|
||||
}
|
||||
@@ -113,6 +132,7 @@ public class Plugin {
|
||||
manager = null;
|
||||
sysInjector = null;
|
||||
sshInjector = null;
|
||||
httpInjector = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,6 +141,11 @@ public class Plugin {
|
||||
return sshInjector;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Injector getHttpInjector() {
|
||||
return httpInjector;
|
||||
}
|
||||
|
||||
public void add(final RegistrationHandle handle) {
|
||||
add(new LifecycleListener() {
|
||||
@Override
|
||||
|
@@ -45,6 +45,7 @@ public class PluginGuiceEnvironment {
|
||||
private final List<StartPluginListener> listeners;
|
||||
private Module sysModule;
|
||||
private Module sshModule;
|
||||
private Module httpModule;
|
||||
|
||||
@Inject
|
||||
PluginGuiceEnvironment(Injector sysInjector, CopyConfigModule ccm) {
|
||||
@@ -84,6 +85,19 @@ public class PluginGuiceEnvironment {
|
||||
return sshModule;
|
||||
}
|
||||
|
||||
public void setHttpInjector(Injector httpInjector) {
|
||||
httpModule = copy(httpInjector);
|
||||
listeners.addAll(getListeners(httpInjector));
|
||||
}
|
||||
|
||||
boolean hasHttpModule() {
|
||||
return httpModule != null;
|
||||
}
|
||||
|
||||
Module getHttpModule() {
|
||||
return httpModule;
|
||||
}
|
||||
|
||||
void onStartPlugin(Plugin plugin) {
|
||||
for (StartPluginListener l : listeners) {
|
||||
l.onStartPlugin(plugin);
|
||||
@@ -126,15 +140,74 @@ public class PluginGuiceEnvironment {
|
||||
|
||||
private static boolean shouldCopy(Key<?> key) {
|
||||
Class<?> type = key.getTypeLiteral().getRawType();
|
||||
if (type == LifecycleListener.class) {
|
||||
if (LifecycleListener.class.isAssignableFrom(type)) {
|
||||
return false;
|
||||
}
|
||||
if (type == StartPluginListener.class) {
|
||||
if (StartPluginListener.class.isAssignableFrom(type)) {
|
||||
return false;
|
||||
}
|
||||
if ("org.apache.sshd.server.Command".equals(type.getName())) {
|
||||
|
||||
if (type.getName().startsWith("com.google.inject.")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is("org.apache.sshd.server.Command", type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is("javax.servlet.Filter", type)) {
|
||||
return false;
|
||||
}
|
||||
if (is("javax.servlet.ServletContext", type)) {
|
||||
return false;
|
||||
}
|
||||
if (is("javax.servlet.ServletRequest", type)) {
|
||||
return false;
|
||||
}
|
||||
if (is("javax.servlet.ServletResponse", type)) {
|
||||
return false;
|
||||
}
|
||||
if (is("javax.servlet.http.HttpServlet", type)) {
|
||||
return false;
|
||||
}
|
||||
if (is("javax.servlet.http.HttpServletRequest", type)) {
|
||||
return false;
|
||||
}
|
||||
if (is("javax.servlet.http.HttpServletResponse", type)) {
|
||||
return false;
|
||||
}
|
||||
if (is("javax.servlet.http.HttpSession", type)) {
|
||||
return false;
|
||||
}
|
||||
if (Map.class.isAssignableFrom(type)
|
||||
&& key.getAnnotationType() != null
|
||||
&& "com.google.inject.servlet.RequestParameters"
|
||||
.equals(key.getAnnotationType().getName())) {
|
||||
return false;
|
||||
}
|
||||
if (type.getName().startsWith("com.google.gerrit.httpd.GitOverHttpServlet$")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean is(String name, Class<?> type) {
|
||||
Class<?> p = type;
|
||||
while (p != null) {
|
||||
if (name.equals(p.getName())) {
|
||||
return true;
|
||||
}
|
||||
p = p.getSuperclass();
|
||||
}
|
||||
|
||||
Class<?>[] interfaces = type.getInterfaces();
|
||||
if (interfaces != null) {
|
||||
for (Class<?> i : interfaces) {
|
||||
if (is(name, i)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -245,6 +245,7 @@ public class PluginLoader implements LifecycleListener {
|
||||
Attributes main = manifest.getMainAttributes();
|
||||
String sysName = main.getValue("Gerrit-Module");
|
||||
String sshName = main.getValue("Gerrit-SshModule");
|
||||
String httpName = main.getValue("Gerrit-HttpModule");
|
||||
|
||||
URL[] urls = {jarFile.toURI().toURL()};
|
||||
ClassLoader parentLoader = PluginLoader.class.getClassLoader();
|
||||
@@ -252,7 +253,9 @@ public class PluginLoader implements LifecycleListener {
|
||||
|
||||
Class<? extends Module> sysModule = load(sysName, pluginLoader);
|
||||
Class<? extends Module> sshModule = load(sshName, pluginLoader);
|
||||
return new Plugin(name, jarFile, manifest, snapshot, sysModule, sshModule);
|
||||
Class<? extends Module> httpModule = load(httpName, pluginLoader);
|
||||
return new Plugin(name, jarFile, manifest, snapshot,
|
||||
sysModule, sshModule, httpModule);
|
||||
}
|
||||
|
||||
private Class<? extends Module> load(String name, ClassLoader pluginLoader)
|
||||
|
Reference in New Issue
Block a user