Dynamically load plugins in new private injectors

Each plugin is given its own Guice injector that is isolated from the
main server, and from each other. Explicit bindings in the main server
are copied down into the plugin's private environment, making any
object that is bound in a module (e.g. GerritGlobalModule)
automatically available, but hiding anything that is loaded by a
just-in-time implicit binding.

These private injectors ensure plugins can't accidentally load a
just-in-time binding into the sysInjector and cause them to be unable
to garbage collect, or to confuse another plugin with a bogus binding.

Change-Id: I7bc54c84fba30381cfb58d24b88871b2714c335a
This commit is contained in:
Shawn O. Pearce
2012-05-08 19:16:30 -07:00
committed by gerrit code review
parent b4992582d6
commit 4c847cf912
15 changed files with 549 additions and 109 deletions

View File

@@ -46,11 +46,12 @@ import com.google.gerrit.server.git.ReceiveCommitsExecutorModule;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
import com.google.gerrit.server.mail.SmtpEmailSender;
import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
import com.google.gerrit.server.plugins.PluginModule;
import com.google.gerrit.server.schema.SchemaVersionCheck;
import com.google.gerrit.server.ssh.NoSshModule;
import com.google.gerrit.sshd.SshModule;
import com.google.gerrit.sshd.commands.MasterCommandModule;
import com.google.gerrit.sshd.commands.MasterPluginsModule;
import com.google.gerrit.sshd.commands.SlaveCommandModule;
import com.google.inject.Injector;
import com.google.inject.Module;
@@ -141,6 +142,8 @@ public class Daemon extends SiteProgram {
dbInjector = createDbInjector(MULTI_USER);
cfgInjector = createCfgInjector();
sysInjector = createSysInjector();
sysInjector.getInstance(PluginGuiceEnvironment.class)
.setCfgInjector(cfgInjector);
manager.add(dbInjector, cfgInjector, sysInjector);
if (sshd) {
@@ -209,6 +212,7 @@ public class Daemon extends SiteProgram {
modules.add(new SmtpEmailSender.Module());
modules.add(new SignedTokenEmailTokenVerifier.Module());
modules.add(new PushReplication.Module());
modules.add(new PluginModule());
if (httpd) {
modules.add(new CanonicalWebUrlModule() {
@Override
@@ -232,6 +236,8 @@ public class Daemon extends SiteProgram {
private void initSshd() {
sshInjector = createSshInjector();
sysInjector.getInstance(PluginGuiceEnvironment.class)
.setSshInjector(sshInjector);
manager.add(sshInjector);
}
@@ -243,7 +249,6 @@ public class Daemon extends SiteProgram {
modules.add(new SlaveCommandModule());
} else {
modules.add(new MasterCommandModule());
modules.add(cfgInjector.getInstance(MasterPluginsModule.class));
}
} else {
modules.add(new NoSshModule());

View File

@@ -0,0 +1,102 @@
// 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.reviewdb.server.ReviewDb;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.config.TrackingFooters;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import org.eclipse.jgit.lib.Config;
import java.io.File;
/**
* Copies critical objects from the {@code dbInjector} into a plugin.
* <p>
* Most explicit bindings are copied automatically from the cfgInjector and
* sysInjector to be made available to a plugin's private world. This module is
* necessary to get things bound in the dbInjector that are not otherwise easily
* available, but that a plugin author might expect to exist.
*/
@Singleton
class CopyConfigModule extends AbstractModule {
@Inject
@SitePath
private File sitePath;
@Provides
@SitePath
File getSitePath() {
return sitePath;
}
@Inject
private SitePaths sitePaths;
@Provides
SitePaths getSitePaths() {
return sitePaths;
}
@Inject
private TrackingFooters trackingFooters;
@Provides
TrackingFooters getTrackingFooters() {
return trackingFooters;
}
@Inject
@GerritServerConfig
private Config gerritServerConfig;
@Provides
@GerritServerConfig
Config getGerritServerConfig() {
return gerritServerConfig;
}
@Inject
private SchemaFactory<ReviewDb> schemaFactory;
@Provides
SchemaFactory<ReviewDb> getSchemaFactory() {
return schemaFactory;
}
@Inject
private GitRepositoryManager gitRepositoryManager;
@Provides
GitRepositoryManager getGitRepositoryManager() {
return gitRepositoryManager;
}
@Inject
CopyConfigModule() {
}
@Override
protected void configure() {
}
}

View File

@@ -0,0 +1,120 @@
// 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.lifecycle.LifecycleListener;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import javax.annotation.Nullable;
public class Plugin {
private final String name;
private final ClassLoader loader;
private Class<? extends Module> sysModule;
private Class<? extends Module> sshModule;
private Injector sysInjector;
private Injector sshInjector;
private LifecycleManager manager;
public Plugin(String name, ClassLoader loader,
@Nullable Class<? extends Module> sysModule,
@Nullable Class<? extends Module> sshModule) {
this.name = name;
this.loader = loader;
this.sysModule = sysModule;
this.sshModule = sshModule;
}
public String getName() {
return name;
}
public void start(PluginGuiceEnvironment env) throws Exception {
Injector root = newRootInjector(env);
manager = new LifecycleManager();
if (sysModule != null) {
sysInjector = root.createChildInjector(root.getInstance(sysModule));
manager.add(sysInjector);
} else {
sysInjector = root;
}
if (sshModule != null && env.hasSshModule()) {
sshInjector = sysInjector.createChildInjector(
env.getSshModule(),
sysInjector.getInstance(sshModule));
manager.add(sshInjector);
}
manager.start();
env.onStartPlugin(this);
}
private Injector newRootInjector(PluginGuiceEnvironment env) {
return Guice.createInjector(
env.getSysModule(),
new AbstractModule() {
@Override
protected void configure() {
bind(String.class)
.annotatedWith(PluginName.class)
.toInstance(name);
}
});
}
public void stop() {
if (manager != null) {
manager.stop();
sysInjector = null;
sshInjector = null;
manager = null;
}
}
@Nullable
public Injector getSshInjector() {
return sshInjector;
}
public void add(final RegistrationHandle handle) {
add(new LifecycleListener() {
@Override
public void start() {
}
@Override
public void stop() {
handle.remove();
}
});
}
public void add(LifecycleListener listener) {
manager.add(listener);
}
@Override
public String toString() {
return "Plugin [" + name + "]";
}
}

View File

@@ -0,0 +1,140 @@
// 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.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gerrit.lifecycle.LifecycleListener;
import com.google.inject.AbstractModule;
import com.google.inject.Binding;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.inject.Inject;
/**
* Tracks Guice bindings that should be exposed to loaded plugins.
* <p>
* This is an internal implementation detail of how the main server is able to
* export its explicit Guice bindings to tightly coupled plugins, giving them
* access to singletons and request scoped resources just like any core code.
*/
@Singleton
public class PluginGuiceEnvironment {
private final Injector sysInjector;
private final CopyConfigModule copyConfigModule;
private final List<StartPluginListener> listeners;
private Module sysModule;
private Module sshModule;
@Inject
PluginGuiceEnvironment(Injector sysInjector, CopyConfigModule ccm) {
this.sysInjector = sysInjector;
this.copyConfigModule = ccm;
this.listeners = new CopyOnWriteArrayList<StartPluginListener>();
this.listeners.addAll(getListeners(sysInjector));
}
Module getSysModule() {
return sysModule;
}
public void setCfgInjector(Injector cfgInjector) {
final Module cm = copy(cfgInjector);
final Module sm = copy(sysInjector);
sysModule = new AbstractModule() {
@Override
protected void configure() {
install(copyConfigModule);
install(cm);
install(sm);
}
};
}
public void setSshInjector(Injector sshInjector) {
sshModule = copy(sshInjector);
listeners.addAll(getListeners(sshInjector));
}
boolean hasSshModule() {
return sshModule != null;
}
Module getSshModule() {
return sshModule;
}
void onStartPlugin(Plugin plugin) {
for (StartPluginListener l : listeners) {
l.onStartPlugin(plugin);
}
}
private static List<StartPluginListener> getListeners(Injector src) {
List<Binding<StartPluginListener>> bindings =
src.findBindingsByType(new TypeLiteral<StartPluginListener>() {});
List<StartPluginListener> found =
Lists.newArrayListWithCapacity(bindings.size());
for (Binding<StartPluginListener> b : bindings) {
found.add(b.getProvider().get());
}
return found;
}
private static Module copy(Injector src) {
final Map<Key<?>, Binding<?>> bindings = Maps.newLinkedHashMap();
for (Map.Entry<Key<?>, Binding<?>> e : src.getBindings().entrySet()) {
if (shouldCopy(e.getKey())) {
bindings.put(e.getKey(), e.getValue());
}
}
bindings.remove(Key.get(Injector.class));
bindings.remove(Key.get(java.util.logging.Logger.class));
return new AbstractModule() {
@SuppressWarnings("unchecked")
@Override
protected void configure() {
for (Map.Entry<Key<?>, Binding<?>> e : bindings.entrySet()) {
Key<Object> k = (Key<Object>) e.getKey();
Binding<Object> b = (Binding<Object>) e.getValue();
bind(k).toProvider(b.getProvider());
}
}
};
}
private static boolean shouldCopy(Key<?> key) {
Class<?> type = key.getTypeLiteral().getRawType();
if (type == LifecycleListener.class) {
return false;
}
if (type == StartPluginListener.class) {
return false;
}
if ("org.apache.sshd.server.Command".equals(type.getName())) {
return false;
}
return true;
}
}

View File

@@ -12,9 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.common;
package com.google.gerrit.server.plugins;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gerrit.lifecycle.LifecycleListener;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import com.google.inject.Module;
@@ -32,7 +35,6 @@ import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.Attributes;
@@ -40,56 +42,66 @@ import java.util.jar.JarFile;
import java.util.jar.Manifest;
@Singleton
public class PluginLoader {
public class PluginLoader implements LifecycleListener {
private static final Logger log = LoggerFactory.getLogger(PluginLoader.class);
private final File pluginsDir;
private Map<String, Plugin> pluginCache;
private final PluginGuiceEnvironment env;
private final Map<String, Plugin> running;
@Inject
public PluginLoader(SitePaths sitePaths) {
public PluginLoader(SitePaths sitePaths, PluginGuiceEnvironment pe) {
pluginsDir = sitePaths.plugins_dir;
env = pe;
running = Maps.newHashMap();
}
private synchronized void initialize() {
if (pluginCache != null) {
return;
@Override
public synchronized void start() {
log.info("Loading plugins from " + pluginsDir.getAbsolutePath());
for (Plugin p : scanPlugins()) {
if (running.containsKey(p.getName())) {
log.error("Skipping duplicate plugin " + p.getName());
continue;
}
try {
p.start(env);
} catch (Exception err) {
log.error("Cannot start plugin " + p.getName(), err);
continue;
}
running.put(p.getName(), p);
}
pluginCache = new HashMap<String, Plugin>();
loadPlugins();
}
public Plugin get(String pluginName) {
initialize();
return pluginCache.get(pluginName);
@Override
public synchronized void stop() {
for (Plugin p : running.values()) {
p.stop();
}
running.clear();
}
public Collection<Plugin> getPlugins() {
initialize();
return pluginCache.values();
}
private void loadPlugins() {
private Collection<Plugin> scanPlugins() {
Collection<File> pluginJars;
try {
pluginJars = getPluginFiles();
} catch (IOException e) {
log.error("Cannot scan Gerrit plugins directory looking for jar files", e);
return;
return Collections.emptyList();
}
List<Plugin> all = Lists.newArrayListWithCapacity(pluginJars.size());
for (File jarFile : pluginJars) {
Plugin plugin;
try {
plugin = loadPlugin(jarFile);
pluginCache.put(plugin.name, plugin);
all.add(loadPlugin(jarFile));
} catch (IOException e) {
log.error("Cannot access plugin jar " + jarFile, e);
} catch (ClassNotFoundException e) {
log.error("Cannot load plugin class module from " + jarFile, e);
}
}
return all;
}
@SuppressWarnings("unchecked")
@@ -97,7 +109,7 @@ public class PluginLoader {
ClassNotFoundException {
Manifest jarManifest = new JarFile(jarFile).getManifest();
ClassLoader parentLoader = PluginLoader.class.getClassLoader();
ClassLoader jarClassLoader =
ClassLoader pluginLoader =
new URLClassLoader(getPluginURLs(jarFile), parentLoader);
Attributes attrs = jarManifest.getMainAttributes();
@@ -111,14 +123,15 @@ public class PluginLoader {
throw new IOException("No Gerrit-SshModule attribute in manifest");
}
Class<?> moduleClass = Class.forName(moduleName, false, jarClassLoader);
Class<? extends Module> moduleClass =
(Class<? extends Module>) Class
.forName(moduleName, false, pluginLoader);
if (!Module.class.isAssignableFrom(moduleClass)) {
throw new ClassNotFoundException(String.format(
"Gerrit-SshModule %s is not a Guice Module",
moduleClass.getName()));
"Gerrit-SshModule %s is not a Guice Module", moduleClass.getName()));
}
return new Plugin(pluginName, (Class<? extends Module>) moduleClass);
return new Plugin(pluginName, pluginLoader, null, moduleClass);
}
private URL[] getPluginURLs(File jarFile) throws MalformedURLException {

View File

@@ -12,21 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.common;
package com.google.gerrit.server.plugins;
import com.google.inject.Module;
public class Plugin {
public final String name;
public final Class<? extends Module> sshModule;
public Plugin(String name, Class<? extends Module> sshModule) {
this.name = name;
this.sshModule = sshModule;
}
import com.google.gerrit.lifecycle.LifecycleModule;
public class PluginModule extends LifecycleModule {
@Override
public String toString() {
return "Plugin [" + name + "; SshModule=" + sshModule.getName() + "]";
protected void configure() {
bind(PluginGuiceEnvironment.class);
bind(PluginLoader.class);
bind(CopyConfigModule.class);
listener().to(PluginLoader.class);
}
}

View File

@@ -0,0 +1,29 @@
// 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 java.lang.annotation.RetentionPolicy.RUNTIME;
import com.google.inject.BindingAnnotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RUNTIME)
@BindingAnnotation
public @interface PluginName {
}

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.guice;
package com.google.gerrit.server.plugins;
/** Handle for registered information. */
public interface RegistrationHandle {

View File

@@ -0,0 +1,20 @@
// 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;
/** Broadcasts event indicating a plugin was loaded. */
public interface StartPluginListener {
public void onStartPlugin(Plugin plugin);
}

View File

@@ -15,7 +15,7 @@
package com.google.gerrit.sshd;
import com.google.common.collect.Maps;
import com.google.gerrit.server.guice.RegistrationHandle;
import com.google.gerrit.server.plugins.RegistrationHandle;
import com.google.inject.Binding;
import com.google.inject.Inject;
import com.google.inject.Injector;

View File

@@ -30,6 +30,7 @@ import com.google.gerrit.server.config.FactoryModule;
import com.google.gerrit.server.config.GerritRequestModule;
import com.google.gerrit.server.git.QueueProvider;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.plugins.StartPluginListener;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.ssh.SshInfo;
import com.google.gerrit.server.util.RequestScopePropagator;
@@ -44,6 +45,7 @@ import com.google.gerrit.sshd.commands.DefaultCommandModule;
import com.google.gerrit.sshd.commands.QueryShell;
import com.google.gerrit.util.cli.CmdLineParser;
import com.google.gerrit.util.cli.OptionHandlerUtil;
import com.google.inject.internal.UniqueAnnotations;
import com.google.inject.servlet.RequestScoped;
import org.apache.sshd.common.KeyPairProvider;
@@ -89,6 +91,9 @@ public class SshModule extends FactoryModule {
install(new LifecycleModule() {
@Override
protected void configure() {
bind(StartPluginListener.class)
.annotatedWith(UniqueAnnotations.create())
.to(SshPluginStarterCallback.class);
listener().to(SshLog.class);
listener().to(SshDaemon.class);
}

View File

@@ -0,0 +1,57 @@
// 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.gerrit.server.plugins.Plugin;
import com.google.gerrit.server.plugins.StartPluginListener;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.apache.sshd.server.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
@Singleton
class SshPluginStarterCallback implements StartPluginListener {
private static final Logger log = LoggerFactory
.getLogger(SshPluginStarterCallback.class);
private final DispatchCommandProvider root;
@Inject
SshPluginStarterCallback(
@CommandName(Commands.ROOT) DispatchCommandProvider root) {
this.root = root;
}
@Override
public void onStartPlugin(Plugin plugin) {
if (plugin.getSshInjector() != null) {
Key<Command> key = Commands.key(plugin.getName());
Provider<Command> cmd;
try {
cmd = plugin.getSshInjector().getProvider(key);
} catch (RuntimeException err) {
log.warn(String.format("Plugin %s does not define command",
plugin.getName()), err);
return;
}
plugin.add(root.register(Commands.named(plugin.getName()), cmd));
}
}
}

View File

@@ -1,57 +0,0 @@
// 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.commands;
import com.google.gerrit.common.Plugin;
import com.google.gerrit.common.PluginLoader;
import com.google.gerrit.sshd.CommandModule;
import com.google.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
public class MasterPluginsModule extends CommandModule {
private static final Logger log =
LoggerFactory.getLogger(MasterPluginsModule.class);
private PluginLoader pluginLoader;
@Inject
MasterPluginsModule(PluginLoader loader) {
pluginLoader = loader;
}
@Override
protected void configure() {
Collection<Plugin> plugins = pluginLoader.getPlugins();
for (Plugin p : plugins) {
if (PluginCommandModule.class.isAssignableFrom(p.sshModule)) {
@SuppressWarnings("unchecked")
Class<PluginCommandModule> c = (Class<PluginCommandModule>) p.sshModule;
try {
PluginCommandModule module = c.newInstance();
module.initSshModule(p.name);
install(module);
} catch (InstantiationException e) {
log.warn("Initialization of plugin module '" + p.name + "' failed");
} catch (IllegalAccessException e) {
log.warn("Initialization of plugin module '" + p.name + "' failed");
}
}
}
}
}

View File

@@ -14,6 +14,8 @@
package com.google.gerrit.sshd.commands;
import com.google.common.base.Preconditions;
import com.google.gerrit.server.plugins.PluginName;
import com.google.gerrit.sshd.CommandName;
import com.google.gerrit.sshd.Commands;
import com.google.gerrit.sshd.DispatchCommandProvider;
@@ -22,20 +24,24 @@ import com.google.inject.binder.LinkedBindingBuilder;
import org.apache.sshd.server.Command;
import javax.inject.Inject;
public abstract class PluginCommandModule extends AbstractModule {
private CommandName command;
public void initSshModule(String pluginName) {
command = Commands.named(pluginName);
@Inject
void setPluginName(@PluginName String name) {
this.command = Commands.named(name);
}
@Override
protected final void configure() {
Preconditions.checkState(command != null, "@PluginName must be provided");
bind(Commands.key(command)).toProvider(new DispatchCommandProvider(command));
configureCmds();
configureCommands();
}
protected abstract void configureCmds();
protected abstract void configureCommands();
protected LinkedBindingBuilder<Command> command(String subCmd) {
return bind(Commands.key(command, subCmd));

View File

@@ -37,13 +37,14 @@ import com.google.gerrit.server.git.ReceiveCommitsExecutorModule;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
import com.google.gerrit.server.mail.SmtpEmailSender;
import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
import com.google.gerrit.server.plugins.PluginModule;
import com.google.gerrit.server.schema.DataSourceProvider;
import com.google.gerrit.server.schema.DatabaseModule;
import com.google.gerrit.server.schema.SchemaModule;
import com.google.gerrit.server.schema.SchemaVersionCheck;
import com.google.gerrit.sshd.SshModule;
import com.google.gerrit.sshd.commands.MasterCommandModule;
import com.google.gerrit.sshd.commands.MasterPluginsModule;
import com.google.inject.AbstractModule;
import com.google.inject.CreationException;
import com.google.inject.Guice;
@@ -113,6 +114,10 @@ public class WebAppInitializer extends GuiceServletContextListener {
sshInjector = createSshInjector();
webInjector = createWebInjector();
PluginGuiceEnvironment env = sysInjector.getInstance(PluginGuiceEnvironment.class);
env.setCfgInjector(cfgInjector);
env.setSshInjector(sshInjector);
// Push the Provider<HttpServletRequest> down into the canonical
// URL provider. Its optional for that provider, but since we can
// supply one we should do so, in case the administrator has not
@@ -198,6 +203,7 @@ public class WebAppInitializer extends GuiceServletContextListener {
modules.add(new SmtpEmailSender.Module());
modules.add(new SignedTokenEmailTokenVerifier.Module());
modules.add(new PushReplication.Module());
modules.add(new PluginModule());
modules.add(new CanonicalWebUrlModule() {
@Override
protected Class<? extends Provider<String>> provider() {
@@ -212,7 +218,6 @@ public class WebAppInitializer extends GuiceServletContextListener {
final List<Module> modules = new ArrayList<Module>();
modules.add(new SshModule());
modules.add(new MasterCommandModule());
modules.add(cfgInjector.getInstance(MasterPluginsModule.class));
return sysInjector.createChildInjector(modules);
}