Get commentLink w/ association from project.config

Align its-base behaviour with Gerrit hierarchical configuration of
commentLink:
- project.config with inheritance
- gerrit.config as fallback

With regards to the association policy, on project.config is taken
from the plugin-specific section.

Example of gerrit.config association policy:
[plugin "its-jira"]
   association = MANDATORY

Change-Id: I7dadb6f0bd6b1952fdc18d8c24ead36cca33ae9d
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/its/ItsConfig.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/its/ItsConfig.java
index 1241b8a..17d31a1 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/base/its/ItsConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/its/ItsConfig.java
@@ -14,9 +14,14 @@
 
 package com.googlesource.gerrit.plugins.its.base.its;
 
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
 import com.google.gerrit.common.data.RefConfigSection;
 import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.api.projects.CommentLinkInfo;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.Project.NameKey;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.PluginConfig;
 import com.google.gerrit.server.config.PluginConfigFactory;
@@ -28,6 +33,7 @@
 import com.google.gerrit.server.events.Event;
 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.gerrit.server.project.RefPatternMatcher;
@@ -39,10 +45,14 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.regex.Pattern;
 
 
 public class ItsConfig {
+  private static final String PLUGIN = "plugin";
+
   private static final Logger log = LoggerFactory.getLogger(ItsConfig.class);
 
   private final String pluginName;
@@ -50,6 +60,18 @@
   private final PluginConfigFactory pluginCfgFactory;
   private final Config gerritConfig;
 
+  private static final ThreadLocal<Project.NameKey> currentProjectName =
+      new ThreadLocal<Project.NameKey>() {
+        @Override
+        protected Project.NameKey initialValue() {
+          return null;
+        }
+      };
+
+  public static void setCurrentProjectName(Project.NameKey projectName) {
+    currentProjectName.set(projectName);
+  }
+
   @Inject
   public ItsConfig(@PluginName String pluginName, ProjectCache projectCache,
       PluginConfigFactory pluginCfgFactory, @GerritServerConfig Config gerritConfig) {
@@ -148,7 +170,7 @@
   public String getCommentLinkName() {
     String ret;
 
-    ret = gerritConfig.getString(pluginName, null, "commentlink");
+    ret = getPluginConfigString("commentlink");
     if (ret == null) {
       ret = pluginName;
     }
@@ -166,9 +188,26 @@
    *    to match issue ids.
    */
   public Pattern getIssuePattern() {
+    String match =
+        FluentIterable
+            .from(getCommitLinkInfo(getCommentLinkName()))
+            .filter(new Predicate<CommentLinkInfo>() {
+              @Override
+              public boolean apply(CommentLinkInfo input) {
+                return input.match != null && !input.match.trim().isEmpty();
+              }
+            })
+            .transform(new Function<CommentLinkInfo, String>() {
+              @Override
+              public String apply(CommentLinkInfo input) {
+                return input.match;
+              }
+            })
+            .last()
+            .or(gerritConfig.getString("commentlink", getCommentLinkName(),
+                "match"));
     Pattern ret = null;
-    String match = gerritConfig.getString("commentlink",
-        getCommentLinkName(), "match");
+
     if (match != null) {
       ret = Pattern.compile(match);
     }
@@ -186,7 +225,7 @@
   public int getIssuePatternGroupIndex() {
     Pattern pattern = getIssuePattern();
     int groupCount = pattern.matcher("").groupCount();
-    int index = gerritConfig.getInt(pluginName, "commentlinkGroupIndex", 1);
+    int index = getPluginConfigInt("commentlinkGroupIndex", 1);
     if (index < 0 || index > groupCount) {
       index = (groupCount == 0 ? 0 : 1);
     }
@@ -195,14 +234,59 @@
 
   /**
    * Gets how necessary it is to associate commits with issues
+   *
    * @return policy on how necessary association with issues is
    */
   public ItsAssociationPolicy getItsAssociationPolicy() {
-    ItsAssociationPolicy legacyAssociatonPolicy =
-        gerritConfig.getEnum("commentlink", getCommentLinkName(),
+    ItsAssociationPolicy legacyItsAssociationPolicy =
+        gerritConfig.getEnum("commentLink", getCommentLinkName(),
             "association", ItsAssociationPolicy.OPTIONAL);
 
-    return gerritConfig.getEnum("plugin", pluginName, "association",
-        legacyAssociatonPolicy);
+    return getPluginConfigEnum("association", legacyItsAssociationPolicy);
+  }
+
+  private String getPluginConfigString(String key) {
+    return getCurrentPluginConfig().getString(key,
+        gerritConfig.getString(PLUGIN, pluginName, key));
+  }
+
+  private int getPluginConfigInt(String key, int defaultValue) {
+    return getCurrentPluginConfig().getInt(key,
+        gerritConfig.getInt(PLUGIN, pluginName, key, defaultValue));
+  }
+
+  private <T extends Enum<?>> T getPluginConfigEnum(String key, T defaultValue) {
+    return getCurrentPluginConfig().getEnum(key,
+        gerritConfig.getEnum(PLUGIN, pluginName, key, defaultValue));
+  }
+
+  private PluginConfig getCurrentPluginConfig() {
+    NameKey projectName = currentProjectName.get();
+    if (projectName != null) {
+      try {
+        return pluginCfgFactory.getFromProjectConfigWithInheritance(
+            projectName, pluginName);
+      } catch (NoSuchProjectException e) {
+        log.error("Cannot access " + projectName + " configuration for plugin "
+            + pluginName, e);
+      }
+    }
+    return new PluginConfig(pluginName, new Config());
+  }
+
+  private List<CommentLinkInfo> getCommitLinkInfo(final String commentLinkName) {
+    NameKey projectName = currentProjectName.get();
+    if (projectName != null) {
+      List<CommentLinkInfo> commentLinks =
+          projectCache.get(projectName).getCommentLinks();
+      return FluentIterable.from(commentLinks)
+          .filter(new Predicate<CommentLinkInfo>() {
+            @Override
+            public boolean apply(CommentLinkInfo input) {
+              return input.name.equals(commentLinkName);
+            }
+          }).toList();
+    }
+    return Collections.emptyList();
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/validation/ItsValidateComment.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/validation/ItsValidateComment.java
index 4ce687d..ff2b38a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/base/validation/ItsValidateComment.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/validation/ItsValidateComment.java
@@ -143,6 +143,8 @@
   @Override
   public List<CommitValidationMessage> onCommitReceived(
       CommitReceivedEvent receiveEvent) throws CommitValidationException {
+    ItsConfig.setCurrentProjectName(receiveEvent.getProjectNameKey());
+
     if (itsConfig.isEnabled(receiveEvent.getProjectNameKey(), receiveEvent.getRefName())) {
       return validCommit(receiveEvent.commit);
     } else {
diff --git a/src/main/resources/Documentation/config-common.md b/src/main/resources/Documentation/config-common.md
index bf36852..d53b0c6 100644
--- a/src/main/resources/Documentation/config-common.md
+++ b/src/main/resources/Documentation/config-common.md
@@ -68,6 +68,10 @@
 You are encouraged to move the association policy to the plugin section, the
 commentLink.association will be discontinued in the next major release.
 
+The association can be overridden at project level in the project.config
+using the same syntax used in the gerrit.config. Project's hierarchy will be respected
+when evaluating the links configuration and association policy.
+
 [enabling-its-integration]: #enabling-its-integration
 <a name="enabling-its-integration">Enabling ITS integration</a>
 ---------------------------------------------------------------