Add per-project prolog submit rule files

When loading the prolog environment, now checks refs/meta/config
branch for a file called rules.pl. If it exists, consult the
file. Expects a predicate called submit_rule. If no file is found,
uses the default_submit predicate in common_rules.pl.

Change-Id: Ia76d0dc5aba67cf3c68c56589fe013c63157bca5
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
index 96eacfe..6f022d3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -72,6 +72,7 @@
   private Map<AccountGroup.UUID, GroupReference> groupsByUUID;
   private Map<String, AccessSection> accessSections;
   private List<ValidationError> validationErrors;
+  private String prologRules;
 
   public static ProjectConfig read(MetaDataUpdate update) throws IOException,
       ConfigInvalidException {
@@ -149,6 +150,17 @@
   }
 
   /**
+   * @return the project's Prolog based rules.pl script,
+   *    if present in the branch. Null if there are no rules.
+   */
+  public String getPrologRules() {
+    if (prologRules.equals("")) {
+      return null;
+    }
+    return prologRules;
+  }
+
+  /**
    * Check all GroupReferences use current group name, repairing stale ones.
    *
    * @param groupCache cache to use when looking up group information by UUID.
@@ -188,6 +200,7 @@
   protected void onLoad() throws IOException, ConfigInvalidException {
     Map<String, GroupReference> groupsByName = readGroupList();
 
+    prologRules = readUTF8("rules.pl");
     Config rc = readConfig(PROJECT_CONFIG);
     project = new Project(projectName);
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
index 7a3b848..32794a8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
@@ -28,10 +28,10 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
+import com.googlecode.prolog_cafe.compiler.CompileException;
 import com.googlecode.prolog_cafe.lang.IntegerTerm;
 import com.googlecode.prolog_cafe.lang.PrologException;
 import com.googlecode.prolog_cafe.lang.StructureTerm;
-import com.googlecode.prolog_cafe.lang.SymbolTerm;
 import com.googlecode.prolog_cafe.lang.Term;
 import com.googlecode.prolog_cafe.lang.VariableTerm;
 
@@ -252,25 +252,36 @@
       return result;
     }
 
-    PrologEnvironment env = getProjectControl().getProjectState().newPrologEnvironment();
+    PrologEnvironment env;
+    try {
+      env = getProjectControl().getProjectState().newPrologEnvironment();
+    } catch (CompileException err) {
+      log.error("cannot consult rules.pl", err);
+      return new CanSubmitResult("Error reading submit rule");
+    }
+
     env.set(StoredValues.REVIEW_DB, db);
     env.set(StoredValues.CHANGE, change);
     env.set(StoredValues.PATCH_SET_ID, patchSetId);
     env.set(StoredValues.CHANGE_CONTROL, this);
 
-    StructureTerm submitRule = SymbolTerm.makeSymbol(
-        "com.google.gerrit.rules.common", "default_submit", 1);
+    Term submitRule = env.once("com.google.gerrit.rules.common", "locate_submit_rule",
+        new VariableTerm());
+    if (submitRule == null) {
+      log.error("Error in locate_submit_rule: no submit_rule found");
+      return new CanSubmitResult("Error in finding submit rule");
+    }
 
     List<Term> results = new ArrayList<Term>();
     try {
       for (Term[] template : env.all(
-              "com.google.gerrit.rules.common", "can_submit",
-              submitRule,
-              new VariableTerm())) {
-          results.add(template[1]);
-        }
+          "com.google.gerrit.rules.common", "can_submit",
+          submitRule,
+          new VariableTerm())) {
+        results.add(template[1]);
+      }
     } catch (PrologException err) {
-      log.error("PrologException calling "+submitRule, err);
+      log.error("PrologException calling " + submitRule, err);
       return new CanSubmitResult("Error in submit rule");
     }
 
@@ -319,7 +330,7 @@
         continue;
 
       } else if ("reject".equals(status.name())) {
-        return new CanSubmitResult("Submit blocked by "+ label);
+        return new CanSubmitResult("Submit blocked by " + label);
 
       } else if ("need".equals(status.name())) {
         if (status.isStructure() && status.arg(0).isInteger()) {
@@ -340,4 +351,4 @@
 
     return CanSubmitResult.OK;
   }
-}
+}
\ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
index 422230e..fe5c140 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
@@ -29,10 +29,17 @@
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
+import com.googlecode.prolog_cafe.compiler.CompileException;
+import com.googlecode.prolog_cafe.lang.JavaObjectTerm;
+import com.googlecode.prolog_cafe.lang.Prolog;
+import com.googlecode.prolog_cafe.lang.SymbolTerm;
+
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 
 import java.io.IOException;
+import java.io.PushbackReader;
+import java.io.StringReader;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -121,9 +128,24 @@
   }
 
   /** @return Construct a new PrologEnvironment for the calling thread. */
-  public PrologEnvironment newPrologEnvironment() {
+  public PrologEnvironment newPrologEnvironment() throws CompileException {
     // TODO Replace this with a per-project ClassLoader to isolate rules.
-    return envFactory.create(getClass().getClassLoader());
+    PrologEnvironment env = envFactory.create(getClass().getClassLoader());
+
+    //consult rules.pl at refs/meta/config branch for custom submit rules
+    String rules = getConfig().getPrologRules();
+    if (rules != null) {
+      PushbackReader in =
+          new PushbackReader(new StringReader(rules), Prolog.PUSHBACK_SIZE);
+      JavaObjectTerm streamObject = new JavaObjectTerm(in);
+      if (!env.execute(Prolog.BUILTIN, "consult_stream",
+          SymbolTerm.makeSymbol("rules.pl"), streamObject)) {
+        throw new CompileException("Cannot consult rules.pl " +
+            getProject().getName() + " " + getConfig().getRevision());
+      }
+    }
+
+    return env;
   }
 
   public Project getProject() {
diff --git a/gerrit-server/src/main/prolog/common_rules.pl b/gerrit-server/src/main/prolog/common_rules.pl
index c3eeee3..d083f87 100644
--- a/gerrit-server/src/main/prolog/common_rules.pl
+++ b/gerrit-server/src/main/prolog/common_rules.pl
@@ -143,7 +143,7 @@
 %%
 %% can_submit/2:
 %%
-%%   Execute the SubmitRule for each solution until one where all of the
+%%   Executes the SubmitRule for each solution until one where all of the
 %%   states has the format label(_, ok(_)) is found, then cut away any
 %%   remaining choice points leaving this as the last solution.
 %%
@@ -164,6 +164,30 @@
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %%
+%% locate_submit_rule/1:
+%%
+%%   Finds a submit_rule depending on what rules are available.
+%%   If none are available, use default_submit/1.
+%%
+:- public locate_submit_rule/1.
+%%
+
+locate_submit_rule(RuleName) :-
+  clause(user:submit_rule(_), _),
+  !,
+  RuleName = user:submit_rule
+  .
+locate_submit_rule(RuleName) :-
+  '$compiled_predicate'(user, submit_rule, 1),
+  !,
+  RuleName = user:submit_rule
+  .
+locate_submit_rule(RuleName) :-
+  RuleName = 'com.google.gerrit.rules.common':default_submit.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
 %% default_submit/1:
 %%
 :- public default_submit/1.