Gracefully fail when submit_rule/1 has no solutions

If a submit_rule is coded with no solution for a change, e.g.:

  submit_rule(submit()) :- fail.

the server used to crash with 500s from a number of REST APIs,
caused by a ClassCastException inside of SubmitRuleEvaluator.

An empty result list is not a ListTerm, it is a SymbolTerm and using
the NIL instance '[]'. This cannot be cast to a ListTerm for return
to the caller.

Instead return List<Term> and handle the conversion, ensuring an
empty list is returned to callers when there are no results.

Change-Id: Iea1211afb2f3970ba98ea1eff14df07452cf5ccd
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java
index 170f8e8..db18f0d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java
@@ -129,10 +129,9 @@
     return out;
   }
 
-  @SuppressWarnings("unchecked")
   private static List<Term> eval(SubmitRuleEvaluator evaluator)
       throws RuleEvalException {
-    return evaluator.evaluate().toJava();
+    return evaluator.evaluate();
   }
 
   static class Record {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java
index f58fe75..e7e1f32 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java
@@ -30,12 +30,12 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 
-import com.googlecode.prolog_cafe.lang.ListTerm;
 import com.googlecode.prolog_cafe.lang.Term;
 
 import org.kohsuke.args4j.Option;
 
 import java.io.ByteArrayInputStream;
+import java.util.List;
 
 public class TestSubmitType implements RestModifyView<RevisionResource, Input> {
   private final ReviewDb db;
@@ -76,7 +76,7 @@
           ? new ByteArrayInputStream(input.rule.getBytes(Charsets.UTF_8))
           : null);
 
-    ListTerm results;
+    List<Term> results;
     try {
       results = evaluator.evaluate();
     } catch (RuleEvalException e) {
@@ -84,12 +84,12 @@
           "rule failed with exception: %s",
           e.getMessage()));
     }
-    if (results.isNil()) {
+    if (results.isEmpty()) {
       throw new BadRequestException(String.format(
           "rule %s has no solution",
           evaluator.getSubmitRule()));
     }
-    Term type = results.car();
+    Term type = results.get(0);
     if (!type.isSymbol()) {
       throw new BadRequestException(String.format(
           "rule %s produced invalid result: %s",
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 48b0731..74b6232 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
@@ -36,6 +36,7 @@
 import com.googlecode.prolog_cafe.lang.ListTerm;
 import com.googlecode.prolog_cafe.lang.Prolog;
 import com.googlecode.prolog_cafe.lang.StructureTerm;
+import com.googlecode.prolog_cafe.lang.SymbolTerm;
 import com.googlecode.prolog_cafe.lang.Term;
 
 import org.slf4j.Logger;
@@ -355,7 +356,7 @@
           fastEvalLabels,
           "locate_submit_rule", "can_submit",
           "locate_submit_filter", "filter_submit_results");
-      results = evaluator.evaluate().toJava();
+      results = evaluator.evaluate();
     } catch (RuleEvalException e) {
       return logRuleError(e.getMessage(), e);
     }
@@ -492,7 +493,7 @@
           err);
     }
 
-    List<String> results;
+    List<Term> results;
     SubmitRuleEvaluator evaluator;
     try {
       evaluator = new SubmitRuleEvaluator(db, patchSet,
@@ -500,7 +501,7 @@
           false,
           "locate_submit_type", "get_submit_type",
           "locate_submit_type_filter", "filter_submit_type_results");
-      results = evaluator.evaluate().toJava();
+      results = evaluator.evaluate();
     } catch (RuleEvalException e) {
       return logTypeRuleError(e.getMessage(), e);
     }
@@ -513,10 +514,15 @@
       return typeRuleError("Project submit rule has no solution");
     }
 
-    // Take only the first result and convert it to SubmitTypeRecord
-    // This logic will need to change once we support multiple submit types
-    // in the UI
-    String typeName = results.get(0);
+    Term typeTerm = results.get(0);
+    if (!typeTerm.isSymbol()) {
+      log.error("Submit rule '" + evaluator.getSubmitRule() + "' for change "
+          + change.getId() + " of " + getProject().getName()
+          + " did not return a symbol.");
+      return typeRuleError("Project submit rule has invalid solution");
+    }
+
+    String typeName = ((SymbolTerm)typeTerm).name();
     try {
       return SubmitTypeRecord.OK(
           Project.SubmitType.valueOf(typeName.toUpperCase()));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
index bcea2c9..349567c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.project;
 
+import com.google.common.collect.Lists;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -30,6 +31,7 @@
 
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 import javax.annotation.Nullable;
@@ -124,8 +126,7 @@
    * @return List of {@link Term} objects returned from the evaluated rules.
    * @throws RuleEvalException
    */
-  public ListTerm evaluate() throws RuleEvalException {
-    List<Term> results = new ArrayList<Term>();
+  public List<Term> evaluate() throws RuleEvalException {
     PrologEnvironment env = getPrologEnvironment();
     try {
       submitRule = env.once("gerrit", userRuleLocatorName, new VariableTerm());
@@ -133,6 +134,7 @@
         env.once("gerrit", "assume_range_from_label");
       }
 
+      List<Term> results = new ArrayList<Term>();
       try {
         for (Term[] template : env.all("gerrit", userRuleWrapperName,
             submitRule, new VariableTerm())) {
@@ -152,7 +154,16 @@
       if (!skipFilters) {
         resultsTerm = runSubmitFilters(resultsTerm, env);
       }
-      return (ListTerm) resultsTerm;
+      if (resultsTerm.isList()) {
+        List<Term> r = Lists.newArrayList();
+        for (Term t = resultsTerm; t.isList();) {
+          ListTerm l = (ListTerm) t;
+          r.add(l.car().dereference());
+          t = l.cdr().dereference();
+        }
+        return r;
+      }
+      return Collections.emptyList();
     } finally {
       env.close();
     }