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:
		
				
					committed by
					
						
						gerrit code review
					
				
			
			
				
	
			
			
			
						parent
						
							b4992582d6
						
					
				
				
					commit
					4c847cf912
				
			@@ -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());
 | 
			
		||||
 
 | 
			
		||||
@@ -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,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 + "]";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -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 {
 | 
			
		||||
@@ -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);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -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 {
 | 
			
		||||
}
 | 
			
		||||
@@ -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 {
 | 
			
		||||
@@ -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);
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 | 
			
		||||
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));
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user