Support enforcing issue tracker system integration for child projects

Change-Id: Ia1c62f96c0d06806aa7013c47a66fdde4d325a2d
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
diff --git a/its-base/src/main/java/com/googlesource/gerrit/plugins/hooks/its/InitIts.java b/its-base/src/main/java/com/googlesource/gerrit/plugins/hooks/its/InitIts.java
index 6e1f24a..18df41d 100644
--- a/its-base/src/main/java/com/googlesource/gerrit/plugins/hooks/its/InitIts.java
+++ b/its-base/src/main/java/com/googlesource/gerrit/plugins/hooks/its/InitIts.java
@@ -32,6 +32,10 @@
     TRUE, FALSE;
   }
 
+  public static enum ItsIntegration {
+    ENABLED, DISABLED, ENFORCED;
+  }
+
   private final String pluginName;
   private final String itsDisplayName;
   protected final ConsoleUI ui;
@@ -54,13 +58,32 @@
     Config cfg = allProjectsConfig.load();
     ui.message("\n");
     ui.header(itsDisplayName + " Integration");
-    boolean enabled =
-        ui.yesno(cfg.getBoolean("plugin", pluginName, "enabled", false),
-            "By default enabled for all projects");
-    if (enabled) {
-      cfg.setBoolean("plugin", pluginName, "enabled", enabled);
+
+    ItsIntegration itsintegration;
+    String enabled = cfg.getString("plugin", pluginName, "enabled");
+    if (ItsIntegration.ENFORCED.name().equalsIgnoreCase(enabled)) {
+      itsintegration = ItsIntegration.ENFORCED;
+    } else if (Boolean.parseBoolean(enabled)) {
+      itsintegration = ItsIntegration.ENABLED;
     } else {
-      cfg.unset("plugin", pluginName, "enabled");
+      itsintegration = ItsIntegration.DISABLED;
+    }
+    itsintegration =
+        ui.readEnum(itsintegration,
+            "Issue tracker integration for all projects?");
+    switch (itsintegration) {
+      case ENFORCED:
+        cfg.setString("plugin", pluginName, "enabled", "enforced");
+        break;
+      case ENABLED:
+        cfg.setBoolean("plugin", pluginName, "enabled", true);
+        break;
+      case DISABLED:
+        cfg.unset("plugin", pluginName, "enabled");
+        break;
+      default:
+        throw new IOException("Unsupported value for issue track integration: "
+            + itsintegration.name());
     }
     allProjectsConfig.save(pluginName, "Initialize " + itsDisplayName + " Integration");
   }
diff --git a/its-base/src/main/java/com/googlesource/gerrit/plugins/hooks/its/ItsConfig.java b/its-base/src/main/java/com/googlesource/gerrit/plugins/hooks/its/ItsConfig.java
index 2aea591..2a5ba9f 100644
--- a/its-base/src/main/java/com/googlesource/gerrit/plugins/hooks/its/ItsConfig.java
+++ b/its-base/src/main/java/com/googlesource/gerrit/plugins/hooks/its/ItsConfig.java
@@ -16,6 +16,7 @@
 
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.config.PluginConfig;
 import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.gerrit.server.events.ChangeAbandonedEvent;
 import com.google.gerrit.server.events.ChangeEvent;
@@ -24,7 +25,8 @@
 import com.google.gerrit.server.events.CommentAddedEvent;
 import com.google.gerrit.server.events.PatchSetCreatedEvent;
 import com.google.gerrit.server.events.RefUpdatedEvent;
-import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
 import com.google.inject.Inject;
 
 import org.slf4j.Logger;
@@ -34,11 +36,14 @@
   private static final Logger log = LoggerFactory.getLogger(ItsConfig.class);
 
   private final String pluginName;
+  private final ProjectCache projectCache;
   private final PluginConfigFactory pluginCfgFactory;
 
   @Inject
-  public ItsConfig(@PluginName String pluginName, PluginConfigFactory pluginCfgFactory) {
+  public ItsConfig(@PluginName String pluginName, ProjectCache projectCache,
+      PluginConfigFactory pluginCfgFactory) {
     this.pluginName = pluginName;
+    this.projectCache = projectCache;
     this.pluginCfgFactory = pluginCfgFactory;
   }
 
@@ -62,13 +67,22 @@
   }
 
   public boolean isEnabled(String project) {
-    try {
-      return pluginCfgFactory.getFromProjectConfigWithInheritance(
-          new Project.NameKey(project), pluginName).getBoolean("enabled", false);
-    } catch (NoSuchProjectException e) {
+    ProjectState projectState = projectCache.get(new Project.NameKey(project));
+    if (projectState == null) {
       log.error("Failed to check if " + pluginName + " is enabled for project "
-          + project, e);
+          + project + ": Project " + project + " not found");
       return false;
     }
+
+    for (ProjectState parentState : projectState.treeInOrder()) {
+      PluginConfig parentCfg =
+          pluginCfgFactory.getFromProjectConfig(parentState, pluginName);
+      if ("enforced".equals(parentCfg.getString("enabled"))) {
+        return true;
+      }
+    }
+
+    return pluginCfgFactory.getFromProjectConfigWithInheritance(
+        projectState, pluginName).getBoolean("enabled", false);
   }
 }
diff --git a/its-base/src/main/resources/Documentation/config.md b/its-base/src/main/resources/Documentation/config.md
index fd238b1..f0de506 100644
--- a/its-base/src/main/resources/Documentation/config.md
+++ b/its-base/src/main/resources/Documentation/config.md
@@ -50,6 +50,10 @@
 issue tracker systems on a server. E.g. a project can choose if it
 wants to enable integration with Jira or with Bugzilla.
 
+If child projects must not be allowed to disable the issue tracker
+system integration a project can enforce the issue tracker system
+integration for all child projects by setting
+`plugin.<its-name>.enabled` to `enforced`.
 
 
 [[config-rule-base]]
diff --git a/its-base/src/test/java/com/googlesource/gerrit/plugins/hooks/validation/ItsValidateCommentTest.java b/its-base/src/test/java/com/googlesource/gerrit/plugins/hooks/validation/ItsValidateCommentTest.java
index d0119a9..beb4a9a 100644
--- a/its-base/src/test/java/com/googlesource/gerrit/plugins/hooks/validation/ItsValidateCommentTest.java
+++ b/its-base/src/test/java/com/googlesource/gerrit/plugins/hooks/validation/ItsValidateCommentTest.java
@@ -512,7 +512,7 @@
       itsFacade = createMock(ItsFacade.class);
       bind(ItsFacade.class).toInstance(itsFacade);
 
-      bind(ItsConfig.class).toInstance(new ItsConfig(null, null) {
+      bind(ItsConfig.class).toInstance(new ItsConfig(null, null, null) {
         @Override
         public boolean isEnabled(String project) {
           return true;
diff --git a/its-base/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ActionControllerTest.java b/its-base/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ActionControllerTest.java
index 428375d..7266d81 100644
--- a/its-base/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ActionControllerTest.java
+++ b/its-base/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ActionControllerTest.java
@@ -198,7 +198,7 @@
       actionExecutor = createMock(ActionExecutor.class);
       bind(ActionExecutor.class).toInstance(actionExecutor);
 
-      bind(ItsConfig.class).toInstance(new ItsConfig(null, null) {
+      bind(ItsConfig.class).toInstance(new ItsConfig(null, null, null) {
         @Override
         public boolean isEnabled(ChangeEvent event) {
           return true;