Automatically register plugin bindings
If a plugin has no modules declared in the manifest, automatically generate the modules for the plugin based on the class files that appear in the plugin and the @Export annotations that appear on these concrete classes. For any non-abstract command that extends SshCommand (or really the internal MINA SSHD Command interface that Gerrit uses), plugins may declare the command with @Export("name") to bind the implementation as that SSH command, e.g.: @Export("print-hello") class Hello extend SshCommand { Likewise HTTP servlets can also be bound to URLs, but this only works for standard servlet mappings like "/foo" or "/foo/*". Regex style bindings must use the Guice ServletModule declared in the manifest: @Export("/print-hello") class Hello extends HttpServlet { Change-Id: Iae4cffcd62d7d2911d3f2705e226fbe21434be68
This commit is contained in:
@@ -0,0 +1,69 @@
|
|||||||
|
// 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.httpd.plugins;
|
||||||
|
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.gerrit.extensions.annotations.Export;
|
||||||
|
import com.google.gerrit.server.plugins.InvalidPluginException;
|
||||||
|
import com.google.gerrit.server.plugins.ModuleGenerator;
|
||||||
|
import com.google.inject.Module;
|
||||||
|
import com.google.inject.Scopes;
|
||||||
|
import com.google.inject.servlet.ServletModule;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServlet;
|
||||||
|
|
||||||
|
class HttpAutoRegisterModuleGenerator extends ServletModule
|
||||||
|
implements ModuleGenerator {
|
||||||
|
private final Map<String, Class<HttpServlet>> serve = Maps.newHashMap();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configureServlets() {
|
||||||
|
for (Map.Entry<String, Class<HttpServlet>> e : serve.entrySet()) {
|
||||||
|
bind(e.getValue()).in(Scopes.SINGLETON);
|
||||||
|
serve(e.getKey()).with(e.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPluginName(String name) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public void export(Export export, Class<?> type)
|
||||||
|
throws InvalidPluginException {
|
||||||
|
if (HttpServlet.class.isAssignableFrom(type)) {
|
||||||
|
Class<HttpServlet> old = serve.get(export.value());
|
||||||
|
if (old != null) {
|
||||||
|
throw new InvalidPluginException(String.format(
|
||||||
|
"@Export(\"%s\") has duplicate bindings:\n %s\n %s",
|
||||||
|
export.value(), old.getName(), type.getName()));
|
||||||
|
}
|
||||||
|
serve.put(export.value(), (Class<HttpServlet>) type);
|
||||||
|
} else {
|
||||||
|
throw new InvalidPluginException(String.format(
|
||||||
|
"Class %s with @Export(\"%s\") must extend %s",
|
||||||
|
type.getName(), export.value(),
|
||||||
|
HttpServlet.class.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Module create() throws InvalidPluginException {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
package com.google.gerrit.httpd.plugins;
|
package com.google.gerrit.httpd.plugins;
|
||||||
|
|
||||||
|
import com.google.gerrit.server.plugins.ModuleGenerator;
|
||||||
import com.google.gerrit.server.plugins.ReloadPluginListener;
|
import com.google.gerrit.server.plugins.ReloadPluginListener;
|
||||||
import com.google.gerrit.server.plugins.StartPluginListener;
|
import com.google.gerrit.server.plugins.StartPluginListener;
|
||||||
import com.google.inject.internal.UniqueAnnotations;
|
import com.google.inject.internal.UniqueAnnotations;
|
||||||
@@ -32,5 +33,8 @@ public class HttpPluginModule extends ServletModule {
|
|||||||
bind(ReloadPluginListener.class)
|
bind(ReloadPluginListener.class)
|
||||||
.annotatedWith(UniqueAnnotations.create())
|
.annotatedWith(UniqueAnnotations.create())
|
||||||
.to(HttpPluginServlet.class);
|
.to(HttpPluginServlet.class);
|
||||||
|
|
||||||
|
bind(ModuleGenerator.class)
|
||||||
|
.to(HttpAutoRegisterModuleGenerator.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,254 @@
|
|||||||
|
// 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 static com.google.gerrit.server.plugins.PluginGuiceEnvironment.is;
|
||||||
|
|
||||||
|
import com.google.gerrit.extensions.annotations.Export;
|
||||||
|
import com.google.inject.Module;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.util.IO;
|
||||||
|
import org.objectweb.asm.AnnotationVisitor;
|
||||||
|
import org.objectweb.asm.Attribute;
|
||||||
|
import org.objectweb.asm.ClassReader;
|
||||||
|
import org.objectweb.asm.ClassVisitor;
|
||||||
|
import org.objectweb.asm.FieldVisitor;
|
||||||
|
import org.objectweb.asm.MethodVisitor;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
|
|
||||||
|
class AutoRegisterModules {
|
||||||
|
private static final int SKIP_ALL = ClassReader.SKIP_CODE
|
||||||
|
| ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
|
||||||
|
private final String pluginName;
|
||||||
|
private final JarFile jarFile;
|
||||||
|
private final ClassLoader classLoader;
|
||||||
|
private final ModuleGenerator sshGen;
|
||||||
|
private final ModuleGenerator httpGen;
|
||||||
|
|
||||||
|
Module sysModule;
|
||||||
|
Module sshModule;
|
||||||
|
Module httpModule;
|
||||||
|
|
||||||
|
AutoRegisterModules(String pluginName,
|
||||||
|
PluginGuiceEnvironment env,
|
||||||
|
JarFile jarFile,
|
||||||
|
ClassLoader classLoader) {
|
||||||
|
this.pluginName = pluginName;
|
||||||
|
this.jarFile = jarFile;
|
||||||
|
this.classLoader = classLoader;
|
||||||
|
this.sshGen = env.hasSshModule() ? env.newSshModuleGenerator() : null;
|
||||||
|
this.httpGen = env.hasHttpModule() ? env.newHttpModuleGenerator() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
AutoRegisterModules discover() throws InvalidPluginException {
|
||||||
|
if (sshGen != null) {
|
||||||
|
sshGen.setPluginName(pluginName);
|
||||||
|
}
|
||||||
|
if (httpGen != null) {
|
||||||
|
httpGen.setPluginName(pluginName);
|
||||||
|
}
|
||||||
|
|
||||||
|
scan();
|
||||||
|
|
||||||
|
if (sshGen != null) {
|
||||||
|
sshModule = sshGen.create();
|
||||||
|
}
|
||||||
|
if (httpGen != null) {
|
||||||
|
httpModule = httpGen.create();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scan() throws InvalidPluginException {
|
||||||
|
Enumeration<JarEntry> e = jarFile.entries();
|
||||||
|
while (e.hasMoreElements()) {
|
||||||
|
JarEntry entry = e.nextElement();
|
||||||
|
if (skip(entry)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassData def = new ClassData();
|
||||||
|
try {
|
||||||
|
new ClassReader(read(entry)).accept(def, SKIP_ALL);
|
||||||
|
} catch (IOException err) {
|
||||||
|
throw new InvalidPluginException("Cannot auto-register", err);
|
||||||
|
} catch (RuntimeException err) {
|
||||||
|
PluginLoader.log.warn(String.format(
|
||||||
|
"Plugin %s has invaild class file %s inside of %s",
|
||||||
|
pluginName, entry.getName(), jarFile.getName()), err);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (def.exportedAsName != null) {
|
||||||
|
if (def.isConcrete()) {
|
||||||
|
export(def);
|
||||||
|
} else {
|
||||||
|
PluginLoader.log.warn(String.format(
|
||||||
|
"Plugin %s tries to export abstract class %s",
|
||||||
|
pluginName, def.className));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void export(ClassData def) throws InvalidPluginException {
|
||||||
|
Class<?> clazz;
|
||||||
|
try {
|
||||||
|
clazz = Class.forName(def.className, false, classLoader);
|
||||||
|
} catch (ClassNotFoundException err) {
|
||||||
|
throw new InvalidPluginException(String.format(
|
||||||
|
"Cannot load %s with @Export(\"%s\")",
|
||||||
|
def.className, def.exportedAsName), err);
|
||||||
|
}
|
||||||
|
|
||||||
|
Export export = clazz.getAnnotation(Export.class);
|
||||||
|
if (export == null) {
|
||||||
|
PluginLoader.log.warn(String.format(
|
||||||
|
"In plugin %s asm incorrectly parsed %s with @Export(\"%s\")",
|
||||||
|
pluginName, clazz.getName(), def.exportedAsName));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is("org.apache.sshd.server.Command", clazz)) {
|
||||||
|
if (sshGen != null) {
|
||||||
|
sshGen.export(export, clazz);
|
||||||
|
}
|
||||||
|
} else if (is("javax.servlet.http.HttpServlet", clazz)) {
|
||||||
|
if (httpGen != null) {
|
||||||
|
httpGen.export(export, clazz);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new InvalidPluginException(String.format(
|
||||||
|
"Class %s with @Export(\"%s\") not supported",
|
||||||
|
clazz.getName(), export.value()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean skip(JarEntry entry) {
|
||||||
|
if (!entry.getName().endsWith(".class")) {
|
||||||
|
return true; // Avoid non-class resources.
|
||||||
|
}
|
||||||
|
if (entry.getSize() <= 0) {
|
||||||
|
return true; // Directories have 0 size.
|
||||||
|
}
|
||||||
|
if (entry.getSize() >= 1024 * 1024) {
|
||||||
|
return true; // Do not scan huge class files.
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] read(JarEntry entry) throws IOException {
|
||||||
|
byte[] data = new byte[(int) entry.getSize()];
|
||||||
|
InputStream in = jarFile.getInputStream(entry);
|
||||||
|
try {
|
||||||
|
IO.readFully(in, data, 0, data.length);
|
||||||
|
} finally {
|
||||||
|
in.close();
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ClassData implements ClassVisitor {
|
||||||
|
private static final String EXPORT = Type.getType(Export.class).getDescriptor();
|
||||||
|
String className;
|
||||||
|
int access;
|
||||||
|
String exportedAsName;
|
||||||
|
|
||||||
|
boolean isConcrete() {
|
||||||
|
return (access & Opcodes.ACC_ABSTRACT) == 0
|
||||||
|
&& (access & Opcodes.ACC_INTERFACE) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(int version, int access, String name, String signature,
|
||||||
|
String superName, String[] interfaces) {
|
||||||
|
this.className = Type.getObjectType(name).getClassName();
|
||||||
|
this.access = access;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
|
||||||
|
if (visible && EXPORT.equals(desc)) {
|
||||||
|
return new AbstractAnnotationVisitor() {
|
||||||
|
@Override
|
||||||
|
public void visit(String name, Object value) {
|
||||||
|
exportedAsName = (String) value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitSource(String arg0, String arg1) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitOuterClass(String arg0, String arg1, String arg2) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MethodVisitor visitMethod(int arg0, String arg1, String arg2,
|
||||||
|
String arg3, String[] arg4) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitInnerClass(String arg0, String arg1, String arg2, int arg3) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FieldVisitor visitField(int arg0, String arg1, String arg2,
|
||||||
|
String arg3, Object arg4) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitEnd() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitAttribute(Attribute arg0) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static abstract class AbstractAnnotationVisitor implements
|
||||||
|
AnnotationVisitor {
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitAnnotation(String arg0, String arg1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitArray(String arg0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitEnum(String arg0, String arg1, String arg2) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitEnd() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,27 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
public class InvalidPluginException extends Exception {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public InvalidPluginException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidPluginException(String message, Throwable why) {
|
||||||
|
super(message, why);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,26 @@
|
|||||||
|
// 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.gerrit.extensions.annotations.Export;
|
||||||
|
import com.google.inject.Module;
|
||||||
|
|
||||||
|
public interface ModuleGenerator {
|
||||||
|
void setPluginName(String name);
|
||||||
|
|
||||||
|
void export(Export export, Class<?> type) throws InvalidPluginException;
|
||||||
|
|
||||||
|
Module create() throws InvalidPluginException;
|
||||||
|
}
|
@@ -23,7 +23,6 @@ import com.google.inject.AbstractModule;
|
|||||||
import com.google.inject.Guice;
|
import com.google.inject.Guice;
|
||||||
import com.google.inject.Injector;
|
import com.google.inject.Injector;
|
||||||
import com.google.inject.Module;
|
import com.google.inject.Module;
|
||||||
import com.google.inject.servlet.GuiceFilter;
|
|
||||||
|
|
||||||
import org.eclipse.jgit.storage.file.FileSnapshot;
|
import org.eclipse.jgit.storage.file.FileSnapshot;
|
||||||
|
|
||||||
@@ -38,7 +37,7 @@ public class Plugin {
|
|||||||
static {
|
static {
|
||||||
// Guice logs warnings about multiple injectors being created.
|
// Guice logs warnings about multiple injectors being created.
|
||||||
// Silence this in case HTTP plugins are used.
|
// Silence this in case HTTP plugins are used.
|
||||||
java.util.logging.Logger.getLogger(GuiceFilter.class.getName())
|
java.util.logging.Logger.getLogger("com.google.inject.servlet.GuiceFilter")
|
||||||
.setLevel(java.util.logging.Level.OFF);
|
.setLevel(java.util.logging.Level.OFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,6 +46,7 @@ public class Plugin {
|
|||||||
private final FileSnapshot snapshot;
|
private final FileSnapshot snapshot;
|
||||||
private final JarFile jarFile;
|
private final JarFile jarFile;
|
||||||
private final Manifest manifest;
|
private final Manifest manifest;
|
||||||
|
private final ClassLoader classLoader;
|
||||||
private Class<? extends Module> sysModule;
|
private Class<? extends Module> sysModule;
|
||||||
private Class<? extends Module> sshModule;
|
private Class<? extends Module> sshModule;
|
||||||
private Class<? extends Module> httpModule;
|
private Class<? extends Module> httpModule;
|
||||||
@@ -61,6 +61,7 @@ public class Plugin {
|
|||||||
FileSnapshot snapshot,
|
FileSnapshot snapshot,
|
||||||
JarFile jarFile,
|
JarFile jarFile,
|
||||||
Manifest manifest,
|
Manifest manifest,
|
||||||
|
ClassLoader classLoader,
|
||||||
@Nullable Class<? extends Module> sysModule,
|
@Nullable Class<? extends Module> sysModule,
|
||||||
@Nullable Class<? extends Module> sshModule,
|
@Nullable Class<? extends Module> sshModule,
|
||||||
@Nullable Class<? extends Module> httpModule) {
|
@Nullable Class<? extends Module> httpModule) {
|
||||||
@@ -69,6 +70,7 @@ public class Plugin {
|
|||||||
this.snapshot = snapshot;
|
this.snapshot = snapshot;
|
||||||
this.jarFile = jarFile;
|
this.jarFile = jarFile;
|
||||||
this.manifest = manifest;
|
this.manifest = manifest;
|
||||||
|
this.classLoader = classLoader;
|
||||||
this.sysModule = sysModule;
|
this.sysModule = sysModule;
|
||||||
this.sshModule = sshModule;
|
this.sshModule = sshModule;
|
||||||
this.httpModule = httpModule;
|
this.httpModule = httpModule;
|
||||||
@@ -110,25 +112,48 @@ public class Plugin {
|
|||||||
Injector root = newRootInjector(env);
|
Injector root = newRootInjector(env);
|
||||||
manager = new LifecycleManager();
|
manager = new LifecycleManager();
|
||||||
|
|
||||||
|
AutoRegisterModules auto = null;
|
||||||
|
if (sysModule == null && sshModule == null && httpModule == null) {
|
||||||
|
auto = new AutoRegisterModules(name, env, jarFile, classLoader);
|
||||||
|
auto.discover();
|
||||||
|
}
|
||||||
|
|
||||||
if (sysModule != null) {
|
if (sysModule != null) {
|
||||||
sysInjector = root.createChildInjector(root.getInstance(sysModule));
|
sysInjector = root.createChildInjector(root.getInstance(sysModule));
|
||||||
manager.add(sysInjector);
|
manager.add(sysInjector);
|
||||||
|
} else if (auto != null && auto.sysModule != null) {
|
||||||
|
sysInjector = root.createChildInjector(auto.sysModule);
|
||||||
|
manager.add(sysInjector);
|
||||||
} else {
|
} else {
|
||||||
sysInjector = root;
|
sysInjector = root;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sshModule != null && env.hasSshModule()) {
|
if (env.hasSshModule()) {
|
||||||
sshInjector = sysInjector.createChildInjector(
|
if (sshModule != null) {
|
||||||
env.getSshModule(),
|
sshInjector = sysInjector.createChildInjector(
|
||||||
sysInjector.getInstance(sshModule));
|
env.getSshModule(),
|
||||||
manager.add(sshInjector);
|
sysInjector.getInstance(sshModule));
|
||||||
|
manager.add(sshInjector);
|
||||||
|
} else if (auto != null && auto.sshModule != null) {
|
||||||
|
sshInjector = sysInjector.createChildInjector(
|
||||||
|
env.getSshModule(),
|
||||||
|
auto.sshModule);
|
||||||
|
manager.add(sshInjector);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (httpModule != null && env.hasHttpModule()) {
|
if (env.hasHttpModule()) {
|
||||||
httpInjector = sysInjector.createChildInjector(
|
if (httpModule != null) {
|
||||||
env.getHttpModule(),
|
httpInjector = sysInjector.createChildInjector(
|
||||||
sysInjector.getInstance(httpModule));
|
env.getHttpModule(),
|
||||||
manager.add(httpInjector);
|
sysInjector.getInstance(httpModule));
|
||||||
|
manager.add(httpInjector);
|
||||||
|
} else if (auto != null && auto.httpModule != null) {
|
||||||
|
httpInjector = sysInjector.createChildInjector(
|
||||||
|
env.getHttpModule(),
|
||||||
|
auto.httpModule);
|
||||||
|
manager.add(httpInjector);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
manager.start();
|
manager.start();
|
||||||
|
@@ -22,6 +22,7 @@ import com.google.inject.Binding;
|
|||||||
import com.google.inject.Injector;
|
import com.google.inject.Injector;
|
||||||
import com.google.inject.Key;
|
import com.google.inject.Key;
|
||||||
import com.google.inject.Module;
|
import com.google.inject.Module;
|
||||||
|
import com.google.inject.Provider;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
import com.google.inject.TypeLiteral;
|
import com.google.inject.TypeLiteral;
|
||||||
|
|
||||||
@@ -48,6 +49,9 @@ public class PluginGuiceEnvironment {
|
|||||||
private Module sshModule;
|
private Module sshModule;
|
||||||
private Module httpModule;
|
private Module httpModule;
|
||||||
|
|
||||||
|
private Provider<ModuleGenerator> sshGen;
|
||||||
|
private Provider<ModuleGenerator> httpGen;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
PluginGuiceEnvironment(Injector sysInjector, CopyConfigModule ccm) {
|
PluginGuiceEnvironment(Injector sysInjector, CopyConfigModule ccm) {
|
||||||
this.sysInjector = sysInjector;
|
this.sysInjector = sysInjector;
|
||||||
@@ -79,6 +83,7 @@ public class PluginGuiceEnvironment {
|
|||||||
|
|
||||||
public void setSshInjector(Injector injector) {
|
public void setSshInjector(Injector injector) {
|
||||||
sshModule = copy(injector);
|
sshModule = copy(injector);
|
||||||
|
sshGen = injector.getProvider(ModuleGenerator.class);
|
||||||
onStart.addAll(listeners(injector, StartPluginListener.class));
|
onStart.addAll(listeners(injector, StartPluginListener.class));
|
||||||
onReload.addAll(listeners(injector, ReloadPluginListener.class));
|
onReload.addAll(listeners(injector, ReloadPluginListener.class));
|
||||||
}
|
}
|
||||||
@@ -91,8 +96,13 @@ public class PluginGuiceEnvironment {
|
|||||||
return sshModule;
|
return sshModule;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ModuleGenerator newSshModuleGenerator() {
|
||||||
|
return sshGen.get();
|
||||||
|
}
|
||||||
|
|
||||||
public void setHttpInjector(Injector injector) {
|
public void setHttpInjector(Injector injector) {
|
||||||
httpModule = copy(injector);
|
httpModule = copy(injector);
|
||||||
|
httpGen = injector.getProvider(ModuleGenerator.class);
|
||||||
onStart.addAll(listeners(injector, StartPluginListener.class));
|
onStart.addAll(listeners(injector, StartPluginListener.class));
|
||||||
onReload.addAll(listeners(injector, ReloadPluginListener.class));
|
onReload.addAll(listeners(injector, ReloadPluginListener.class));
|
||||||
}
|
}
|
||||||
@@ -105,6 +115,10 @@ public class PluginGuiceEnvironment {
|
|||||||
return httpModule;
|
return httpModule;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ModuleGenerator newHttpModuleGenerator() {
|
||||||
|
return httpGen.get();
|
||||||
|
}
|
||||||
|
|
||||||
void onStartPlugin(Plugin plugin) {
|
void onStartPlugin(Plugin plugin) {
|
||||||
for (StartPluginListener l : onStart) {
|
for (StartPluginListener l : onStart) {
|
||||||
l.onStartPlugin(plugin);
|
l.onStartPlugin(plugin);
|
||||||
@@ -202,22 +216,22 @@ public class PluginGuiceEnvironment {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean is(String name, Class<?> type) {
|
static boolean is(String name, Class<?> type) {
|
||||||
Class<?> p = type;
|
while (type != null) {
|
||||||
while (p != null) {
|
if (name.equals(type.getName())) {
|
||||||
if (name.equals(p.getName())) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
p = p.getSuperclass();
|
|
||||||
}
|
|
||||||
|
|
||||||
Class<?>[] interfaces = type.getInterfaces();
|
Class<?>[] interfaces = type.getInterfaces();
|
||||||
if (interfaces != null) {
|
if (interfaces != null) {
|
||||||
for (Class<?> i : interfaces) {
|
for (Class<?> i : interfaces) {
|
||||||
if (is(name, i)) {
|
if (is(name, i)) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type = type.getSuperclass();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@@ -337,6 +337,7 @@ public class PluginLoader implements LifecycleListener {
|
|||||||
return new Plugin(name,
|
return new Plugin(name,
|
||||||
srcJar, snapshot,
|
srcJar, snapshot,
|
||||||
jarFile, manifest,
|
jarFile, manifest,
|
||||||
|
pluginLoader,
|
||||||
sysModule, sshModule, httpModule);
|
sysModule, sshModule, httpModule);
|
||||||
} finally {
|
} finally {
|
||||||
if (!keep) {
|
if (!keep) {
|
||||||
|
@@ -0,0 +1,74 @@
|
|||||||
|
// 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.sshd;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.gerrit.extensions.annotations.Export;
|
||||||
|
import com.google.gerrit.server.plugins.InvalidPluginException;
|
||||||
|
import com.google.gerrit.server.plugins.ModuleGenerator;
|
||||||
|
import com.google.inject.AbstractModule;
|
||||||
|
import com.google.inject.Module;
|
||||||
|
|
||||||
|
import org.apache.sshd.server.Command;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
class SshAutoRegisterModuleGenerator
|
||||||
|
extends AbstractModule
|
||||||
|
implements ModuleGenerator {
|
||||||
|
private final Map<String, Class<Command>> commands = Maps.newHashMap();
|
||||||
|
private CommandName command;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
bind(Commands.key(command))
|
||||||
|
.toProvider(new DispatchCommandProvider(command));
|
||||||
|
for (Map.Entry<String, Class<Command>> e : commands.entrySet()) {
|
||||||
|
bind(Commands.key(command, e.getKey())).to(e.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPluginName(String name) {
|
||||||
|
command = Commands.named(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public void export(Export export, Class<?> type)
|
||||||
|
throws InvalidPluginException {
|
||||||
|
Preconditions.checkState(command != null, "pluginName must be provided");
|
||||||
|
if (Command.class.isAssignableFrom(type)) {
|
||||||
|
Class<Command> old = commands.get(export.value());
|
||||||
|
if (old != null) {
|
||||||
|
throw new InvalidPluginException(String.format(
|
||||||
|
"@Export(\"%s\") has duplicate bindings:\n %s\n %s",
|
||||||
|
export.value(), old.getName(), type.getName()));
|
||||||
|
}
|
||||||
|
commands.put(export.value(), (Class<Command>) type);
|
||||||
|
} else {
|
||||||
|
throw new InvalidPluginException(String.format(
|
||||||
|
"Class %s with @Export(\"%s\") must extend %s or implement %s",
|
||||||
|
type.getName(), export.value(),
|
||||||
|
SshCommand.class.getName(), Command.class.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Module create() throws InvalidPluginException {
|
||||||
|
Preconditions.checkState(command != null, "pluginName must be provided");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@@ -30,6 +30,7 @@ import com.google.gerrit.server.config.FactoryModule;
|
|||||||
import com.google.gerrit.server.config.GerritRequestModule;
|
import com.google.gerrit.server.config.GerritRequestModule;
|
||||||
import com.google.gerrit.server.git.QueueProvider;
|
import com.google.gerrit.server.git.QueueProvider;
|
||||||
import com.google.gerrit.server.git.WorkQueue;
|
import com.google.gerrit.server.git.WorkQueue;
|
||||||
|
import com.google.gerrit.server.plugins.ModuleGenerator;
|
||||||
import com.google.gerrit.server.plugins.ReloadPluginListener;
|
import com.google.gerrit.server.plugins.ReloadPluginListener;
|
||||||
import com.google.gerrit.server.plugins.StartPluginListener;
|
import com.google.gerrit.server.plugins.StartPluginListener;
|
||||||
import com.google.gerrit.server.project.ProjectControl;
|
import com.google.gerrit.server.project.ProjectControl;
|
||||||
@@ -92,6 +93,7 @@ public class SshModule extends FactoryModule {
|
|||||||
install(new LifecycleModule() {
|
install(new LifecycleModule() {
|
||||||
@Override
|
@Override
|
||||||
protected void configure() {
|
protected void configure() {
|
||||||
|
bind(ModuleGenerator.class).to(SshAutoRegisterModuleGenerator.class);
|
||||||
bind(SshPluginStarterCallback.class);
|
bind(SshPluginStarterCallback.class);
|
||||||
bind(StartPluginListener.class)
|
bind(StartPluginListener.class)
|
||||||
.annotatedWith(UniqueAnnotations.create())
|
.annotatedWith(UniqueAnnotations.create())
|
||||||
|
Reference in New Issue
Block a user