Merge "Added commit-message property for templates"
diff --git a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBase.java b/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBase.java
index 685a639..cfa70ac 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBase.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBase.java
@@ -19,6 +19,7 @@
 import java.util.Collection;
 
 import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.server.config.SitePath;
 import com.google.inject.Inject;
 
@@ -35,18 +36,23 @@
   private static final Logger log = LoggerFactory.getLogger(RuleBase.class);
 
   /**
-   * File (relative to site) to load rules from
+   * File beginning (relative to site) to load rules from
    */
-  private static final String ITS_CONFIG_FILE = "etc" + File.separatorChar +
-      "its" + File.separator + "actions.config";
+  private static final String ITS_CONFIG_FILE_START = "etc" +
+      File.separatorChar + "its" + File.separator + "actions";
 
   /**
-   * The section for rules within {@link #ITS_CONFIG_FILE}
+   * File end to load rules from
+   */
+  private static final String ITS_CONFIG_FILE_END = ".config";
+
+  /**
+   * The section for rules within rulebases
    */
   private static final String RULE_SECTION = "rule";
 
   /**
-   * The key for actions within {@link #ITS_CONFIG_FILE}
+   * The key for actions within rulebases
    */
   private static final String ACTION_KEY = "action";
 
@@ -54,6 +60,7 @@
   private final Rule.Factory ruleFactory;
   private final Condition.Factory conditionFactory;
   private final ActionRequest.Factory actionRequestFactory;
+  private final String pluginName;
 
   private Collection<Rule> rules;
 
@@ -64,11 +71,13 @@
   @Inject
   public RuleBase(@SitePath File sitePath, Rule.Factory ruleFactory,
       Condition.Factory conditionFactory,
-      ActionRequest.Factory actionRequestFactory) {
+      ActionRequest.Factory actionRequestFactory,
+      @PluginName String pluginName) {
     this.sitePath = sitePath;
     this.ruleFactory = ruleFactory;
     this.conditionFactory = conditionFactory;
     this.actionRequestFactory = actionRequestFactory;
+    this.pluginName = pluginName;
     reloadRules();
   }
 
@@ -109,8 +118,6 @@
         }
         rules.add(rule);
       }
-    } else {
-      log.warn("ITS actions configuration file (" + ruleFile + ") does not exist.");
     }
   }
 
@@ -138,8 +145,26 @@
     }
 
     // Add global rules
-    File globalRuleFile = new File(sitePath, ITS_CONFIG_FILE);
+    File globalRuleFile = new File(sitePath, ITS_CONFIG_FILE_START +
+        ITS_CONFIG_FILE_END);
     addRulesFromFile(globalRuleFile);
+
+    // Add its-specific rules
+    File itsSpecificRuleFile = new File(sitePath, ITS_CONFIG_FILE_START + "-" +
+        pluginName + ITS_CONFIG_FILE_END);
+    addRulesFromFile(itsSpecificRuleFile);
+
+    if (!globalRuleFile.exists() && !itsSpecificRuleFile.exists()) {
+      try {
+        log.warn("Neither global rule file "
+            + globalRuleFile.getCanonicalPath() + " nor Its specific rule file"
+            + itsSpecificRuleFile.getCanonicalPath() + " exist. Please configure "
+            + "rules.");
+      } catch (IOException e) {
+        log.warn("Neither global rule file nor Its specific rule files exist. "
+            + "Please configure rules.");
+      }
+    }
   }
 
   /**
diff --git a/src/main/resources/Documentation/config-common.md b/src/main/resources/Documentation/config-common.md
index 6609d8f..4ad3c97 100644
--- a/src/main/resources/Documentation/config-common.md
+++ b/src/main/resources/Documentation/config-common.md
@@ -113,12 +113,36 @@
 ‘John Doe’ voted ‘+2’ for ‘Code-Review’ on a change”) should trigger
 which action on the ITS (e.g.: “Set issue's status to ‘Resolved’”) is
 configured through a [rule base][rule-base] in
-`etc/its/action.config`.
+`etc/its/actions.config`.
 
 [rule-base]: config-rulebase-common.html
 
 
 
+[multiple-its]: #multiple-its
+<a name="mutiple-its">Multiple ITS</a>
+--------------------------------------
+
+Although not a common setup the @PLUGIN@ plugin supports connecting
+Gerrit to multiple issue tracking systems.
+
+For example users may want to reference issues from two independent
+issue tracking systems (i.e. a Bugzilla and a Jira instance).  In
+this configuration you can simply install both its plugins and
+configure them as described.
+
+In situations where users want to reference issues from multiple
+instances of the same issue tracking system (i.e. two independent
+Bugzilla instances) they can simply create two its-bugzilla plugin
+files with different names (i.e. its-bugzilla-external.jar and
+its-bugzilla-internal.jar).  Gerrit will give each plugin the same
+name as the file name (minus the extension).  You can view the names
+by going to the Gerrit UI under menu Plugins -> Installed.  Now you
+just need to use the appropriate name to configure each plugin.
+
+
+
+
 [legacy-configuration]: #legacy-configuration
 <a name="legacy-configuration">Legacy configuration</a>
 -------------------------------------------------------
@@ -181,4 +205,4 @@
 
 [Back to @PLUGIN@ documentation index][index]
 
-[index]: index.html
\ No newline at end of file
+[index]: index.html
diff --git a/src/main/resources/Documentation/config-rulebase-common.md b/src/main/resources/Documentation/config-rulebase-common.md
index bc8d351..60e1185 100644
--- a/src/main/resources/Documentation/config-rulebase-common.md
+++ b/src/main/resources/Documentation/config-rulebase-common.md
@@ -18,13 +18,16 @@
 ‘Resolved’”) on the ITS.
 
 Actions on the ITS and conditions for the action to take place are
-configured through the rule base in `etc/its/actions.config` in the
-site directory. The rule base is a git config file, and may contain an
-arbitrary number of rules. Each rule can have an arbitrary number of
-conditions and actions. A rule fires all associated actions, once all
-of its conditions are met.
+configured through the rule bases in `etc/its/actions.config` (for global rules
+to be picked up by all configured ITS plugins) and
+`etc/its/actions-@PLUGIN@.config` (for rules to be picked up only by @PLUGIN@)
+in the site directory. A rule base is a git config file, and may contain an
+arbitrary number of rules. Each rule can have an arbitrary number of conditions
+and actions. A rule fires all associated actions, once all of its conditions are
+met.
 
-A simple `etc/its/actions.config` may look like
+A simple `etc/its/actions.config` (or
+`etc/its/actions-@PLUGIN@.config`) may look like
 
 ```
 [rule "rule1"]
@@ -44,7 +47,7 @@
 Goodness! Someone gave a negative code review in Gerrit on an
 associated change.” to each such issue.
 
-The order of rules in `etc/its/action.config` need not be
+The order of rules in `etc/its/actions.config` need not be
 respected. So in the above example, do not rely on `rule1` being
 evaluated before `rule2`.
 
@@ -594,4 +597,4 @@
 
 [Back to @PLUGIN@ documentation index][index]
 
-[index]: index.html
\ No newline at end of file
+[index]: index.html
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBaseTest.java b/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBaseTest.java
index b8d20d9..ffadc1d 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBaseTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/RuleBaseTest.java
@@ -27,10 +27,12 @@
 import org.eclipse.jgit.util.FileUtils;
 
 import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.server.config.FactoryModule;
 import com.google.gerrit.server.config.SitePath;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
+
 import com.googlesource.gerrit.plugins.hooks.testutil.LoggingMockingTestCase;
 
 public class RuleBaseTest extends LoggingMockingTestCase {
@@ -43,12 +45,18 @@
 
   private boolean cleanupSitePath;
 
+  private enum RuleBaseKind {
+      GLOBAL,
+      ITS,
+      FAULTY
+  }
+
   public void testWarnNonExistingRuleBase() {
     replayMocks();
 
     createRuleBase();
 
-    assertLogMessageContains("does not exist");
+    assertLogMessageContains("Neither global");
   }
 
   public void testEmptyRuleBase() throws IOException {
@@ -219,20 +227,20 @@
   }
 
   public void testWarnExistingFaultyNameRuleBaseFile() throws IOException {
-    injectRuleBase("", true);
+    injectRuleBase("", RuleBaseKind.FAULTY);
 
     replayMocks();
 
     createRuleBase();
 
     assertLogMessageContains("Please migrate"); // Migration warning for old name
-    assertLogMessageContains("does not exist"); // For global rule file at new name
+    assertLogMessageContains("Neither global"); // For rule file at at usual places
   }
 
   public void testSimpleFaultyNameRuleBase() throws IOException {
     injectRuleBase("[rule \"rule1\"]\n" +
         "\tconditionA = value1\n" +
-        "\taction = action1", true);
+        "\taction = action1", RuleBaseKind.FAULTY);
 
     Rule rule1 = createMock(Rule.class);
     expect(ruleFactory.create("rule1")).andReturn(rule1);
@@ -250,15 +258,39 @@
     createRuleBase();
 
     assertLogMessageContains("Please migrate"); // Migration warning for old name
-    assertLogMessageContains("does not exist"); // For global rule file at new name
+    assertLogMessageContains("Neither global"); // For rule file at at usual places
   }
 
-  public void testGlobalRuleBaseFileAndFaultyNameFileAreLoaded() throws IOException {
+  public void testSimpleItsRuleBase() throws IOException {
     injectRuleBase("[rule \"rule1\"]\n" +
-        "\taction = action1", false);
+        "\tconditionA = value1\n" +
+        "\taction = action1", RuleBaseKind.ITS);
+
+    Rule rule1 = createMock(Rule.class);
+    expect(ruleFactory.create("rule1")).andReturn(rule1);
+
+    Condition condition1 = createMock(Condition.class);
+    expect(conditionFactory.create("conditionA", "value1")).andReturn(condition1);
+    rule1.addCondition(condition1);
+
+    ActionRequest actionRequest1 = createMock(ActionRequest.class);
+    expect(actionRequestFactory.create("action1")).andReturn(actionRequest1);
+    rule1.addActionRequest(actionRequest1);
+
+    replayMocks();
+
+    createRuleBase();
+  }
+
+  public void testAllRuleBaseFilesAreLoaded() throws IOException {
+    injectRuleBase("[rule \"rule1\"]\n" +
+        "\taction = action1", RuleBaseKind.FAULTY);
 
     injectRuleBase("[rule \"rule2\"]\n" +
-        "\taction = action2", true);
+        "\taction = action2", RuleBaseKind.GLOBAL);
+
+    injectRuleBase("[rule \"rule3\"]\n" +
+        "\taction = action3", RuleBaseKind.ITS);
 
     Collection<Property> properties = Collections.emptySet();
 
@@ -281,18 +313,30 @@
     rule2.addActionRequest(actionRequest2);
 
     List<ActionRequest> rule2Match = Lists.newArrayListWithCapacity(1);
-    rule1Match.add(actionRequest2);
+    rule2Match.add(actionRequest2);
     expect(rule2.actionRequestsFor(properties)).andReturn(rule2Match);
 
+    Rule rule3 = createMock(Rule.class);
+    expect(ruleFactory.create("rule3")).andReturn(rule3);
+
+    ActionRequest actionRequest3 = createMock(ActionRequest.class);
+    expect(actionRequestFactory.create("action3")).andReturn(actionRequest3);
+    rule3.addActionRequest(actionRequest3);
+
+    List<ActionRequest> rule3Match = Lists.newArrayListWithCapacity(1);
+    rule3Match.add(actionRequest3);
+    expect(rule3.actionRequestsFor(properties)).andReturn(rule3Match);
+
     replayMocks();
 
     RuleBase ruleBase = createRuleBase();
 
     Collection<ActionRequest> actual = ruleBase.actionRequestsFor(properties);
 
-    List<ActionRequest> expected = Lists.newArrayListWithCapacity(2);
+    List<ActionRequest> expected = Lists.newArrayListWithCapacity(3);
     expected.add(actionRequest1);
     expected.add(actionRequest2);
+    expected.add(actionRequest3);
 
     assertEquals("Matched actionRequests do not match", expected, actual);
 
@@ -304,12 +348,27 @@
   }
 
   private void injectRuleBase(String rules) throws IOException {
-    injectRuleBase(rules, false);
+    injectRuleBase(rules, RuleBaseKind.GLOBAL);
   }
 
-  private void injectRuleBase(String rules, Boolean faultyName) throws IOException {
+  private void injectRuleBase(String rules, RuleBaseKind ruleBaseKind) throws IOException {
+    String baseName = "";
+    switch (ruleBaseKind) {
+      case GLOBAL:
+        baseName = "actions";
+        break;
+      case ITS:
+        baseName = "actions-ItsTestName";
+        break;
+      case FAULTY:
+        baseName = "action";
+        break;
+      default:
+        fail("Unknown ruleBaseKind");
+    }
     File ruleBaseFile = new File(sitePath, "etc" + File.separatorChar + "its" +
-        File.separator + "action" + (faultyName ? "" : "s") + ".config");
+        File.separator + baseName + ".config");
+
     File ruleBaseParentFile = ruleBaseFile.getParentFile();
     if (!ruleBaseParentFile.exists()) {
       assertTrue("Failed to create parent (" + ruleBaseParentFile + ") for " +
@@ -346,6 +405,9 @@
     @Override
     protected void configure() {
 
+      bind(String.class).annotatedWith(PluginName.class)
+          .toInstance("ItsTestName");
+
       sitePath = randomTargetFile();
       assertFalse("sitePath already (" + sitePath + ") already exists",
           sitePath.exists());