Support easy access to global plugin configuration in own config file

For some plugins the configuration is too complex to be stored in a
single 'plugin' subsection in 'gerrit.config'. If a plugin needs
subsections in its configuration then the plugin configuration can
be stored in an own configuration file next to 'gerrit.config'.

This change adds a method to PluginConfigFactory to easily access the
plugin configuration that is stored in such a plugin configuration
file.

The plugin configuration is loaded only once and is then cached. The
caching is important to avoid loading and parsing the configuration
file on every access, but similar to changes in 'gerrit.config',
changes to the plugin configuration file will only become effective
after a Gerrit restart.

Change-Id: Idbdfd2cf5bd985df3b524b5500e526ecb150cce8
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 8daacad..b529217d 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -465,9 +465,9 @@
 $ ssh -p 29418 review.example.com shell ps
 ----
 
-[[configuration]]
-Configuration
--------------
+[[simple-configuration]]
+Simple Configuration in `gerrit.config`
+---------------------------------------
 
 In Gerrit, global configuration is stored in the `gerrit.config` file.
 If a plugin needs global configuration, this configuration should be
@@ -477,12 +477,12 @@
 plugins that have a simple configuration that only consists of
 key-value pairs. With this approach it is not possible to have
 subsections in the plugin configuration. Plugins that require a complex
-configuration need to store their configuration in their own
-configuration file where they can make use of subsections. On the other
-hand storing the plugin configuration in a 'plugin' subsection in the
-`gerrit.config` file has the advantage that administrators have all
-configuration parameters in one file, instead of having one
-configuration file per plugin.
+configuration need to store their configuration in their
+link:#configuration[own configuration file] where they can make use of
+subsections. On the other hand storing the plugin configuration in a
+'plugin' subsection in the `gerrit.config` file has the advantage that
+administrators have all configuration parameters in one file, instead
+of having one configuration file per plugin.
 
 To avoid conflicts with other plugins, it is recommended that plugins
 only use the `plugin` subsection with their own name. For example the
@@ -509,6 +509,49 @@
                      .getString("language", "English");
 ----
 
+[[configuration]]
+Configuration in own config file
+--------------------------------
+
+Plugins can store their configuration in an own configuration file.
+This makes sense if the plugin configuration is rather complex and
+requires the usage of subsections. Plugins that have a simple
+key-value pair configuration can store their configuration in a
+link:#simple-configuration[`plugin` subsection of the `gerrit.config`
+file].
+
+The plugin configuration file must be named after the plugin and must
+be located in the `etc` folder of the review site. For example a
+configuration file for a `default-reviewer` plugin could look like
+this:
+
+.$site_path/etc/default-reviewer.config
+----
+[branch "refs/heads/master"]
+  reviewer = Project Owners
+  reviewer = john.doe@example.com
+[match "file:^.*\.txt"]
+  reviewer = My Info Developers
+----
+
+Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
+plugin can easily access its configuration:
+
+[source,java]
+----
+@Inject
+private com.google.gerrit.server.config.PluginConfigFactory cfg;
+
+[...]
+
+String[] reviewers = cfg.getGlobalPluginConfig("default-reviewer")
+                        .getStringList("branch", "refs/heads/master", "reviewer");
+----
+
+The plugin configuration is loaded only once and is then cached.
+Similar to changes in 'gerrit.config', changes to the plugin
+configuration file will only become effective after a Gerrit restart.
+
 [[project-specific-configuration]]
 Project Specific Configuration
 ------------------------------
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfigFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfigFactory.java
index 294d8a5..e222bcf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfigFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfigFactory.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.config;
 
+import com.google.common.collect.Maps;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectCache;
@@ -21,20 +22,36 @@
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
+import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
 
 @Singleton
 public class PluginConfigFactory {
+  private static final Logger log =
+      LoggerFactory.getLogger(PluginConfigFactory.class);
+
+  private final SitePaths site;
   private final Config cfg;
   private final ProjectCache projectCache;
   private final ProjectState.Factory projectStateFactory;
+  private final Map<String, Config> pluginConfigs;
 
   @Inject
-  PluginConfigFactory(@GerritServerConfig Config cfg,
+  PluginConfigFactory(SitePaths site, @GerritServerConfig Config cfg,
       ProjectCache projectCache, ProjectState.Factory projectStateFactory) {
+    this.site = site;
     this.cfg = cfg;
     this.projectCache = projectCache;
     this.projectStateFactory = projectStateFactory;
+    this.pluginConfigs = Maps.newHashMap();
   }
 
   /**
@@ -128,4 +145,38 @@
     return getFromProjectConfig(projectName, pluginName).withInheritance(
         projectStateFactory);
   }
+
+  /**
+   * Returns the configuration for the specified plugin that is stored in the
+   * plugin configuration file 'etc/<plugin-name>.config'.
+   *
+   * The plugin configuration is only loaded once and is then cached.
+   *
+   * @param pluginName the name of the plugin for which the configuration should
+   *        be returned
+   * @return the plugin configuration from the 'etc/<plugin-name>.config' file
+   */
+  public Config getGlobalPluginConfig(String pluginName) {
+    if (pluginConfigs.containsKey(pluginName)) {
+      return pluginConfigs.get(pluginName);
+    }
+
+    File pluginConfigFile = new File(site.etc_dir, pluginName + ".config");
+    FileBasedConfig cfg = new FileBasedConfig(pluginConfigFile, FS.DETECTED);
+    pluginConfigs.put(pluginName, cfg);
+    if (!cfg.getFile().exists()) {
+      log.info("No " + pluginConfigFile.getAbsolutePath() + "; assuming defaults");
+      return cfg;
+    }
+
+    try {
+      cfg.load();
+    } catch (IOException e) {
+      log.warn("Failed to load " + pluginConfigFile.getAbsolutePath(), e);
+    } catch (ConfigInvalidException e) {
+      log.warn("Failed to load " + pluginConfigFile.getAbsolutePath(), e);
+    }
+
+    return cfg;
+  }
 }