Merge changes I7afb6192,I7bc54c84,I8e60aa0f
* changes: Automatically load/unload/reload plugins Dynamically load plugins in new private injectors Allow SSH commands to be registered dynamically
This commit is contained in:
@@ -46,11 +46,12 @@ import com.google.gerrit.server.git.ReceiveCommitsExecutorModule;
|
|||||||
import com.google.gerrit.server.git.WorkQueue;
|
import com.google.gerrit.server.git.WorkQueue;
|
||||||
import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
|
import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
|
||||||
import com.google.gerrit.server.mail.SmtpEmailSender;
|
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.schema.SchemaVersionCheck;
|
||||||
import com.google.gerrit.server.ssh.NoSshModule;
|
import com.google.gerrit.server.ssh.NoSshModule;
|
||||||
import com.google.gerrit.sshd.SshModule;
|
import com.google.gerrit.sshd.SshModule;
|
||||||
import com.google.gerrit.sshd.commands.MasterCommandModule;
|
import com.google.gerrit.sshd.commands.MasterCommandModule;
|
||||||
import com.google.gerrit.sshd.commands.MasterPluginsModule;
|
|
||||||
import com.google.gerrit.sshd.commands.SlaveCommandModule;
|
import com.google.gerrit.sshd.commands.SlaveCommandModule;
|
||||||
import com.google.inject.Injector;
|
import com.google.inject.Injector;
|
||||||
import com.google.inject.Module;
|
import com.google.inject.Module;
|
||||||
@@ -141,6 +142,8 @@ public class Daemon extends SiteProgram {
|
|||||||
dbInjector = createDbInjector(MULTI_USER);
|
dbInjector = createDbInjector(MULTI_USER);
|
||||||
cfgInjector = createCfgInjector();
|
cfgInjector = createCfgInjector();
|
||||||
sysInjector = createSysInjector();
|
sysInjector = createSysInjector();
|
||||||
|
sysInjector.getInstance(PluginGuiceEnvironment.class)
|
||||||
|
.setCfgInjector(cfgInjector);
|
||||||
manager.add(dbInjector, cfgInjector, sysInjector);
|
manager.add(dbInjector, cfgInjector, sysInjector);
|
||||||
|
|
||||||
if (sshd) {
|
if (sshd) {
|
||||||
@@ -209,6 +212,7 @@ public class Daemon extends SiteProgram {
|
|||||||
modules.add(new SmtpEmailSender.Module());
|
modules.add(new SmtpEmailSender.Module());
|
||||||
modules.add(new SignedTokenEmailTokenVerifier.Module());
|
modules.add(new SignedTokenEmailTokenVerifier.Module());
|
||||||
modules.add(new PushReplication.Module());
|
modules.add(new PushReplication.Module());
|
||||||
|
modules.add(new PluginModule());
|
||||||
if (httpd) {
|
if (httpd) {
|
||||||
modules.add(new CanonicalWebUrlModule() {
|
modules.add(new CanonicalWebUrlModule() {
|
||||||
@Override
|
@Override
|
||||||
@@ -232,6 +236,8 @@ public class Daemon extends SiteProgram {
|
|||||||
|
|
||||||
private void initSshd() {
|
private void initSshd() {
|
||||||
sshInjector = createSshInjector();
|
sshInjector = createSshInjector();
|
||||||
|
sysInjector.getInstance(PluginGuiceEnvironment.class)
|
||||||
|
.setSshInjector(sshInjector);
|
||||||
manager.add(sshInjector);
|
manager.add(sshInjector);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,7 +249,6 @@ public class Daemon extends SiteProgram {
|
|||||||
modules.add(new SlaveCommandModule());
|
modules.add(new SlaveCommandModule());
|
||||||
} else {
|
} else {
|
||||||
modules.add(new MasterCommandModule());
|
modules.add(new MasterCommandModule());
|
||||||
modules.add(cfgInjector.getInstance(MasterPluginsModule.class));
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
modules.add(new NoSshModule());
|
modules.add(new NoSshModule());
|
||||||
|
@@ -1,146 +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.common;
|
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import com.google.gerrit.server.config.SitePaths;
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import com.google.inject.Module;
|
|
||||||
import com.google.inject.Singleton;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileFilter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URL;
|
|
||||||
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;
|
|
||||||
import java.util.jar.JarFile;
|
|
||||||
import java.util.jar.Manifest;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class PluginLoader {
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(PluginLoader.class);
|
|
||||||
|
|
||||||
private final File pluginsDir;
|
|
||||||
private Map<String, Plugin> pluginCache;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public PluginLoader(SitePaths sitePaths) {
|
|
||||||
pluginsDir = sitePaths.plugins_dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void initialize() {
|
|
||||||
if (pluginCache != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pluginCache = new HashMap<String, Plugin>();
|
|
||||||
loadPlugins();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Plugin get(String pluginName) {
|
|
||||||
initialize();
|
|
||||||
return pluginCache.get(pluginName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Collection<Plugin> getPlugins() {
|
|
||||||
initialize();
|
|
||||||
return pluginCache.values();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadPlugins() {
|
|
||||||
Collection<File> pluginJars;
|
|
||||||
try {
|
|
||||||
pluginJars = getPluginFiles();
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("Cannot scan Gerrit plugins directory looking for jar files", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (File jarFile : pluginJars) {
|
|
||||||
Plugin plugin;
|
|
||||||
try {
|
|
||||||
plugin = loadPlugin(jarFile);
|
|
||||||
pluginCache.put(plugin.name, plugin);
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("Cannot access plugin jar " + jarFile, e);
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
log.error("Cannot load plugin class module from " + jarFile, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private Plugin loadPlugin(File jarFile) throws IOException,
|
|
||||||
ClassNotFoundException {
|
|
||||||
Manifest jarManifest = new JarFile(jarFile).getManifest();
|
|
||||||
ClassLoader parentLoader = PluginLoader.class.getClassLoader();
|
|
||||||
ClassLoader jarClassLoader =
|
|
||||||
new URLClassLoader(getPluginURLs(jarFile), parentLoader);
|
|
||||||
|
|
||||||
Attributes attrs = jarManifest.getMainAttributes();
|
|
||||||
String pluginName = attrs.getValue("Gerrit-Plugin");
|
|
||||||
if (Strings.isNullOrEmpty(pluginName)) {
|
|
||||||
throw new IOException("No Gerrit-Plugin attribute in manifest");
|
|
||||||
}
|
|
||||||
|
|
||||||
String moduleName = attrs.getValue("Gerrit-SshModule");
|
|
||||||
if (Strings.isNullOrEmpty(moduleName)) {
|
|
||||||
throw new IOException("No Gerrit-SshModule attribute in manifest");
|
|
||||||
}
|
|
||||||
|
|
||||||
Class<?> moduleClass = Class.forName(moduleName, false, jarClassLoader);
|
|
||||||
if (!Module.class.isAssignableFrom(moduleClass)) {
|
|
||||||
throw new ClassNotFoundException(String.format(
|
|
||||||
"Gerrit-SshModule %s is not a Guice Module",
|
|
||||||
moduleClass.getName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Plugin(pluginName, (Class<? extends Module>) moduleClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
private URL[] getPluginURLs(File jarFile) throws MalformedURLException {
|
|
||||||
return new URL[] {jarFile.toURI().toURL()};
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<File> getPluginFiles() throws IOException {
|
|
||||||
if (pluginsDir == null || !pluginsDir.exists()) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
File[] plugins = pluginsDir.listFiles(new FileFilter() {
|
|
||||||
@Override
|
|
||||||
public boolean accept(File pathname) {
|
|
||||||
return pathname.isFile() && pathname.getName().endsWith(".jar");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (plugins == null) {
|
|
||||||
log.error("Cannot list " + pluginsDir.getAbsolutePath());
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Arrays.asList(plugins);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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() {
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,128 @@
|
|||||||
|
// 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 org.eclipse.jgit.storage.file.FileSnapshot;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
public class Plugin {
|
||||||
|
private final String name;
|
||||||
|
private final FileSnapshot snapshot;
|
||||||
|
private Class<? extends Module> sysModule;
|
||||||
|
private Class<? extends Module> sshModule;
|
||||||
|
|
||||||
|
private Injector sysInjector;
|
||||||
|
private Injector sshInjector;
|
||||||
|
private LifecycleManager manager;
|
||||||
|
|
||||||
|
public Plugin(String name,
|
||||||
|
FileSnapshot snapshot,
|
||||||
|
@Nullable Class<? extends Module> sysModule,
|
||||||
|
@Nullable Class<? extends Module> sshModule) {
|
||||||
|
this.name = name;
|
||||||
|
this.snapshot = snapshot;
|
||||||
|
this.sysModule = sysModule;
|
||||||
|
this.sshModule = sshModule;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isModified(File jar) {
|
||||||
|
return snapshot.lastModified() != jar.lastModified();
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
manager = null;
|
||||||
|
sysInjector = null;
|
||||||
|
sshInjector = 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 + "]";
|
||||||
|
}
|
||||||
|
}
|
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,205 @@
|
|||||||
|
// 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.Maps;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import com.google.gerrit.lifecycle.LifecycleListener;
|
||||||
|
import com.google.gerrit.server.config.ConfigUtil;
|
||||||
|
import com.google.gerrit.server.config.GerritServerConfig;
|
||||||
|
import com.google.gerrit.server.config.SitePaths;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Module;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.lib.Config;
|
||||||
|
import org.eclipse.jgit.storage.file.FileSnapshot;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileFilter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.jar.Attributes;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
|
import java.util.jar.Manifest;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class PluginLoader implements LifecycleListener {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(PluginLoader.class);
|
||||||
|
|
||||||
|
private final File pluginsDir;
|
||||||
|
private final PluginGuiceEnvironment env;
|
||||||
|
private final Map<String, Plugin> running;
|
||||||
|
private final Map<String, FileSnapshot> broken;
|
||||||
|
private final PluginScannerThread scanner;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public PluginLoader(SitePaths sitePaths,
|
||||||
|
PluginGuiceEnvironment pe,
|
||||||
|
@GerritServerConfig Config cfg) {
|
||||||
|
pluginsDir = sitePaths.plugins_dir;
|
||||||
|
env = pe;
|
||||||
|
running = Maps.newHashMap();
|
||||||
|
broken = Maps.newHashMap();
|
||||||
|
scanner = new PluginScannerThread(
|
||||||
|
this,
|
||||||
|
ConfigUtil.getTimeUnit(cfg,
|
||||||
|
"plugins", null, "checkFrequency",
|
||||||
|
TimeUnit.MINUTES.toMillis(1), TimeUnit.MILLISECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void start() {
|
||||||
|
log.info("Loading plugins from " + pluginsDir.getAbsolutePath());
|
||||||
|
rescan();
|
||||||
|
scanner.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
scanner.end();
|
||||||
|
synchronized (this) {
|
||||||
|
for (Plugin p : running.values()) {
|
||||||
|
p.stop();
|
||||||
|
}
|
||||||
|
running.clear();
|
||||||
|
broken.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void rescan() {
|
||||||
|
List<File> jars = scanJarsInPluginsDirectory();
|
||||||
|
|
||||||
|
stopRemovedPlugins(jars);
|
||||||
|
|
||||||
|
for (File jar : jars) {
|
||||||
|
String name = nameOf(jar);
|
||||||
|
FileSnapshot brokenTime = broken.get(name);
|
||||||
|
if (brokenTime != null && !brokenTime.isModified(jar)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Plugin active = running.get(name);
|
||||||
|
if (active != null && !active.isModified(jar)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (active != null) {
|
||||||
|
log.warn(String.format(
|
||||||
|
"Detected %s was replaced/overwritten."
|
||||||
|
+ " This is not a safe way to update a plugin.",
|
||||||
|
jar.getAbsolutePath()));
|
||||||
|
log.info(String.format("Reloading plugin %s", name));
|
||||||
|
active.stop();
|
||||||
|
running.remove(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileSnapshot snapshot = FileSnapshot.save(jar);
|
||||||
|
Plugin next;
|
||||||
|
try {
|
||||||
|
next = loadPlugin(name, snapshot, jar);
|
||||||
|
next.start(env);
|
||||||
|
} catch (Throwable err) {
|
||||||
|
log.warn(String.format("Cannot load plugin %s", name), err);
|
||||||
|
broken.put(name, snapshot);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
broken.remove(name);
|
||||||
|
running.put(name, next);
|
||||||
|
|
||||||
|
if (active == null) {
|
||||||
|
log.info(String.format("Loaded plugin %s", name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopRemovedPlugins(List<File> jars) {
|
||||||
|
Set<String> unload = Sets.newHashSet(running.keySet());
|
||||||
|
for (File jar : jars) {
|
||||||
|
unload.remove(nameOf(jar));
|
||||||
|
}
|
||||||
|
for (String name : unload){
|
||||||
|
log.info(String.format("Unloading plugin %s", name));
|
||||||
|
running.remove(name).stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String nameOf(File jar) {
|
||||||
|
String name = jar.getName();
|
||||||
|
int ext = name.lastIndexOf('.');
|
||||||
|
return 0 < ext ? name.substring(0, ext) : name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Plugin loadPlugin(String name, FileSnapshot snapshot, File jarFile)
|
||||||
|
throws IOException, ClassNotFoundException {
|
||||||
|
Manifest manifest = new JarFile(jarFile).getManifest();
|
||||||
|
|
||||||
|
Attributes main = manifest.getMainAttributes();
|
||||||
|
String sysName = main.getValue("Gerrit-Module");
|
||||||
|
String sshName = main.getValue("Gerrit-SshModule");
|
||||||
|
|
||||||
|
URL[] urls = {jarFile.toURI().toURL()};
|
||||||
|
ClassLoader parentLoader = PluginLoader.class.getClassLoader();
|
||||||
|
ClassLoader pluginLoader = new URLClassLoader(urls, parentLoader);
|
||||||
|
|
||||||
|
Class<? extends Module> sysModule = load(sysName, pluginLoader);
|
||||||
|
Class<? extends Module> sshModule = load(sshName, pluginLoader);
|
||||||
|
return new Plugin(name, snapshot, sysModule, sshModule);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Class<? extends Module> load(String name, ClassLoader pluginLoader)
|
||||||
|
throws ClassNotFoundException {
|
||||||
|
if (Strings.isNullOrEmpty(name)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Class<? extends Module> clazz =
|
||||||
|
(Class<? extends Module>) Class.forName(name, false, pluginLoader);
|
||||||
|
if (!Module.class.isAssignableFrom(clazz)) {
|
||||||
|
throw new ClassCastException(String.format(
|
||||||
|
"Class %s does not implement %s",
|
||||||
|
name, Module.class.getName()));
|
||||||
|
}
|
||||||
|
return clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<File> scanJarsInPluginsDirectory() {
|
||||||
|
if (pluginsDir == null || !pluginsDir.exists()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
File[] matches = pluginsDir.listFiles(new FileFilter() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(File pathname) {
|
||||||
|
return pathname.getName().endsWith(".jar") && pathname.isFile();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (matches == null) {
|
||||||
|
log.error("Cannot list " + pluginsDir.getAbsolutePath());
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return Arrays.asList(matches);
|
||||||
|
}
|
||||||
|
}
|
@@ -12,21 +12,16 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package com.google.gerrit.common;
|
package com.google.gerrit.server.plugins;
|
||||||
|
|
||||||
import com.google.inject.Module;
|
import com.google.gerrit.lifecycle.LifecycleModule;
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
public class PluginModule extends LifecycleModule {
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
protected void configure() {
|
||||||
return "Plugin [" + name + "; SshModule=" + sshModule.getName() + "]";
|
bind(PluginGuiceEnvironment.class);
|
||||||
|
bind(PluginLoader.class);
|
||||||
|
bind(CopyConfigModule.class);
|
||||||
|
listener().to(PluginLoader.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -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 {
|
||||||
|
}
|
@@ -0,0 +1,52 @@
|
|||||||
|
// 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 java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
class PluginScannerThread extends Thread {
|
||||||
|
private final CountDownLatch done = new CountDownLatch(1);
|
||||||
|
private final PluginLoader loader;
|
||||||
|
private final long checkFrequencyMillis;
|
||||||
|
|
||||||
|
PluginScannerThread(PluginLoader loader, long checkFrequencyMillis) {
|
||||||
|
this.loader = loader;
|
||||||
|
this.checkFrequencyMillis = checkFrequencyMillis;
|
||||||
|
setDaemon(true);
|
||||||
|
setName("PluginScanner");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
for (;;) {
|
||||||
|
try {
|
||||||
|
if (done.await(checkFrequencyMillis, TimeUnit.MILLISECONDS)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
loader.rescan();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void end() {
|
||||||
|
done.countDown();
|
||||||
|
try {
|
||||||
|
join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,21 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
/** Handle for registered information. */
|
||||||
|
public interface RegistrationHandle {
|
||||||
|
/** Delete this registration. */
|
||||||
|
public void remove();
|
||||||
|
}
|
@@ -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);
|
||||||
|
}
|
@@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
package com.google.gerrit.sshd;
|
package com.google.gerrit.sshd;
|
||||||
|
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.gerrit.server.plugins.RegistrationHandle;
|
||||||
import com.google.inject.Binding;
|
import com.google.inject.Binding;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Injector;
|
import com.google.inject.Injector;
|
||||||
@@ -23,11 +25,8 @@ import com.google.inject.TypeLiteral;
|
|||||||
import org.apache.sshd.server.Command;
|
import org.apache.sshd.server.Command;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.TreeMap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates DispatchCommand using commands registered by {@link CommandModule}.
|
* Creates DispatchCommand using commands registered by {@link CommandModule}.
|
||||||
@@ -42,7 +41,7 @@ public class DispatchCommandProvider implements Provider<DispatchCommand> {
|
|||||||
private final String dispatcherName;
|
private final String dispatcherName;
|
||||||
private final CommandName parent;
|
private final CommandName parent;
|
||||||
|
|
||||||
private volatile Map<String, Provider<Command>> map;
|
private volatile ConcurrentMap<String, Provider<Command>> map;
|
||||||
|
|
||||||
public DispatchCommandProvider(final CommandName cn) {
|
public DispatchCommandProvider(final CommandName cn) {
|
||||||
this(Commands.nameOf(cn), cn);
|
this(Commands.nameOf(cn), cn);
|
||||||
@@ -59,7 +58,21 @@ public class DispatchCommandProvider implements Provider<DispatchCommand> {
|
|||||||
return factory.create(dispatcherName, getMap());
|
return factory.create(dispatcherName, getMap());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Provider<Command>> getMap() {
|
public RegistrationHandle register(final CommandName name,
|
||||||
|
final Provider<Command> cmd) {
|
||||||
|
final ConcurrentMap<String, Provider<Command>> m = getMap();
|
||||||
|
if (m.putIfAbsent(name.value(), cmd) != null) {
|
||||||
|
throw new IllegalArgumentException(name.value() + " exists");
|
||||||
|
}
|
||||||
|
return new RegistrationHandle() {
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
m.remove(name.value(), cmd);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConcurrentMap<String, Provider<Command>> getMap() {
|
||||||
if (map == null) {
|
if (map == null) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (map == null) {
|
if (map == null) {
|
||||||
@@ -71,10 +84,8 @@ public class DispatchCommandProvider implements Provider<DispatchCommand> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private Map<String, Provider<Command>> createMap() {
|
private ConcurrentMap<String, Provider<Command>> createMap() {
|
||||||
final Map<String, Provider<Command>> m =
|
ConcurrentMap<String, Provider<Command>> m = Maps.newConcurrentMap();
|
||||||
new TreeMap<String, Provider<Command>>();
|
|
||||||
|
|
||||||
for (final Binding<?> b : allCommands()) {
|
for (final Binding<?> b : allCommands()) {
|
||||||
final Annotation annotation = b.getKey().getAnnotation();
|
final Annotation annotation = b.getKey().getAnnotation();
|
||||||
if (annotation instanceof CommandName) {
|
if (annotation instanceof CommandName) {
|
||||||
@@ -84,9 +95,7 @@ public class DispatchCommandProvider implements Provider<DispatchCommand> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return m;
|
||||||
return Collections.unmodifiableMap(
|
|
||||||
new LinkedHashMap<String, Provider<Command>>(m));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final TypeLiteral<Command> type =
|
private static final TypeLiteral<Command> type =
|
||||||
|
@@ -31,6 +31,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.StartPluginListener;
|
||||||
import com.google.gerrit.server.project.ProjectControl;
|
import com.google.gerrit.server.project.ProjectControl;
|
||||||
import com.google.gerrit.server.ssh.SshInfo;
|
import com.google.gerrit.server.ssh.SshInfo;
|
||||||
import com.google.gerrit.server.util.RequestScopePropagator;
|
import com.google.gerrit.server.util.RequestScopePropagator;
|
||||||
@@ -46,6 +47,7 @@ import com.google.gerrit.sshd.commands.DefaultCommandModule;
|
|||||||
import com.google.gerrit.sshd.commands.QueryShell;
|
import com.google.gerrit.sshd.commands.QueryShell;
|
||||||
import com.google.gerrit.util.cli.CmdLineParser;
|
import com.google.gerrit.util.cli.CmdLineParser;
|
||||||
import com.google.gerrit.util.cli.OptionHandlerUtil;
|
import com.google.gerrit.util.cli.OptionHandlerUtil;
|
||||||
|
import com.google.inject.internal.UniqueAnnotations;
|
||||||
import com.google.inject.servlet.RequestScoped;
|
import com.google.inject.servlet.RequestScoped;
|
||||||
|
|
||||||
import org.apache.sshd.common.KeyPairProvider;
|
import org.apache.sshd.common.KeyPairProvider;
|
||||||
@@ -91,6 +93,9 @@ public class SshModule extends FactoryModule {
|
|||||||
install(new LifecycleModule() {
|
install(new LifecycleModule() {
|
||||||
@Override
|
@Override
|
||||||
protected void configure() {
|
protected void configure() {
|
||||||
|
bind(StartPluginListener.class)
|
||||||
|
.annotatedWith(UniqueAnnotations.create())
|
||||||
|
.to(SshPluginStarterCallback.class);
|
||||||
listener().to(SshLog.class);
|
listener().to(SshLog.class);
|
||||||
listener().to(SshDaemon.class);
|
listener().to(SshDaemon.class);
|
||||||
}
|
}
|
||||||
|
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
package com.google.gerrit.sshd.commands;
|
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.CommandName;
|
||||||
import com.google.gerrit.sshd.Commands;
|
import com.google.gerrit.sshd.Commands;
|
||||||
import com.google.gerrit.sshd.DispatchCommandProvider;
|
import com.google.gerrit.sshd.DispatchCommandProvider;
|
||||||
@@ -22,20 +24,24 @@ import com.google.inject.binder.LinkedBindingBuilder;
|
|||||||
|
|
||||||
import org.apache.sshd.server.Command;
|
import org.apache.sshd.server.Command;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
public abstract class PluginCommandModule extends AbstractModule {
|
public abstract class PluginCommandModule extends AbstractModule {
|
||||||
private CommandName command;
|
private CommandName command;
|
||||||
|
|
||||||
public void initSshModule(String pluginName) {
|
@Inject
|
||||||
command = Commands.named(pluginName);
|
void setPluginName(@PluginName String name) {
|
||||||
|
this.command = Commands.named(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected final void configure() {
|
protected final void configure() {
|
||||||
|
Preconditions.checkState(command != null, "@PluginName must be provided");
|
||||||
bind(Commands.key(command)).toProvider(new DispatchCommandProvider(command));
|
bind(Commands.key(command)).toProvider(new DispatchCommandProvider(command));
|
||||||
configureCmds();
|
configureCommands();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void configureCmds();
|
protected abstract void configureCommands();
|
||||||
|
|
||||||
protected LinkedBindingBuilder<Command> command(String subCmd) {
|
protected LinkedBindingBuilder<Command> command(String subCmd) {
|
||||||
return bind(Commands.key(command, subCmd));
|
return bind(Commands.key(command, subCmd));
|
||||||
|
@@ -37,13 +37,14 @@ import com.google.gerrit.server.git.ReceiveCommitsExecutorModule;
|
|||||||
import com.google.gerrit.server.git.WorkQueue;
|
import com.google.gerrit.server.git.WorkQueue;
|
||||||
import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
|
import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
|
||||||
import com.google.gerrit.server.mail.SmtpEmailSender;
|
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.DataSourceProvider;
|
||||||
import com.google.gerrit.server.schema.DatabaseModule;
|
import com.google.gerrit.server.schema.DatabaseModule;
|
||||||
import com.google.gerrit.server.schema.SchemaModule;
|
import com.google.gerrit.server.schema.SchemaModule;
|
||||||
import com.google.gerrit.server.schema.SchemaVersionCheck;
|
import com.google.gerrit.server.schema.SchemaVersionCheck;
|
||||||
import com.google.gerrit.sshd.SshModule;
|
import com.google.gerrit.sshd.SshModule;
|
||||||
import com.google.gerrit.sshd.commands.MasterCommandModule;
|
import com.google.gerrit.sshd.commands.MasterCommandModule;
|
||||||
import com.google.gerrit.sshd.commands.MasterPluginsModule;
|
|
||||||
import com.google.inject.AbstractModule;
|
import com.google.inject.AbstractModule;
|
||||||
import com.google.inject.CreationException;
|
import com.google.inject.CreationException;
|
||||||
import com.google.inject.Guice;
|
import com.google.inject.Guice;
|
||||||
@@ -113,6 +114,10 @@ public class WebAppInitializer extends GuiceServletContextListener {
|
|||||||
sshInjector = createSshInjector();
|
sshInjector = createSshInjector();
|
||||||
webInjector = createWebInjector();
|
webInjector = createWebInjector();
|
||||||
|
|
||||||
|
PluginGuiceEnvironment env = sysInjector.getInstance(PluginGuiceEnvironment.class);
|
||||||
|
env.setCfgInjector(cfgInjector);
|
||||||
|
env.setSshInjector(sshInjector);
|
||||||
|
|
||||||
// Push the Provider<HttpServletRequest> down into the canonical
|
// Push the Provider<HttpServletRequest> down into the canonical
|
||||||
// URL provider. Its optional for that provider, but since we can
|
// URL provider. Its optional for that provider, but since we can
|
||||||
// supply one we should do so, in case the administrator has not
|
// 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 SmtpEmailSender.Module());
|
||||||
modules.add(new SignedTokenEmailTokenVerifier.Module());
|
modules.add(new SignedTokenEmailTokenVerifier.Module());
|
||||||
modules.add(new PushReplication.Module());
|
modules.add(new PushReplication.Module());
|
||||||
|
modules.add(new PluginModule());
|
||||||
modules.add(new CanonicalWebUrlModule() {
|
modules.add(new CanonicalWebUrlModule() {
|
||||||
@Override
|
@Override
|
||||||
protected Class<? extends Provider<String>> provider() {
|
protected Class<? extends Provider<String>> provider() {
|
||||||
@@ -212,7 +218,6 @@ public class WebAppInitializer extends GuiceServletContextListener {
|
|||||||
final List<Module> modules = new ArrayList<Module>();
|
final List<Module> modules = new ArrayList<Module>();
|
||||||
modules.add(new SshModule());
|
modules.add(new SshModule());
|
||||||
modules.add(new MasterCommandModule());
|
modules.add(new MasterCommandModule());
|
||||||
modules.add(cfgInjector.getInstance(MasterPluginsModule.class));
|
|
||||||
return sysInjector.createChildInjector(modules);
|
return sysInjector.createChildInjector(modules);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user