Add resource closing to PrologEnvironment

Adds close() to PrologEnvironment that runs all the necessary routines
to close any resources when the environment is no longer needed.
When computing a resource to store that needs to be closed later, add
a cleanup routine using addToCleanup(). Adds the Repository
stored value as an example. Refactors ChangeControl to have the
environments clean up after themselves.

Change-Id: I5d109be40cb9212068624820999d522e71ef6fd6
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/PrologEnvironment.java b/gerrit-server/src/main/java/com/google/gerrit/rules/PrologEnvironment.java
index 744feb3..5d86954 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/rules/PrologEnvironment.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/PrologEnvironment.java
@@ -22,8 +22,14 @@
 import com.googlecode.prolog_cafe.lang.Prolog;
 import com.googlecode.prolog_cafe.lang.PrologMachineCopy;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import java.util.EnumSet;
 import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -34,6 +40,10 @@
  * A single copy of the Prolog interpreter, for the current thread.
  */
 public class PrologEnvironment extends BufferingPrologControl {
+
+  private static final Logger log =
+    LoggerFactory.getLogger(PrologEnvironment.class);
+
   static final int MAX_ARITY = 8;
 
   public static interface Factory {
@@ -48,6 +58,7 @@
 
   private final Injector injector;
   private final Map<StoredValue<Object>, Object> storedValues;
+  private List<Runnable> cleanup;
 
   @Inject
   PrologEnvironment(Injector i, @Assisted PrologMachineCopy src) {
@@ -56,6 +67,7 @@
     setMaxArity(MAX_ARITY);
     setEnabled(EnumSet.allOf(Prolog.Feature.class), false);
     storedValues = new HashMap<StoredValue<Object>, Object>();
+    cleanup = new LinkedList<Runnable>();
   }
 
   /** Get the global Guice Injector that configured the environment. */
@@ -87,8 +99,42 @@
 
   /**
    * Copy the stored values from another interpreter to this one.
+   * Also gets the cleanup from the child interpreter
    */
   public void copyStoredValues(PrologEnvironment child) {
     storedValues.putAll(child.storedValues);
+    setCleanup(child.cleanup);
+  }
+
+  /**
+   * Assign the environment a cleanup list (in order to use a centralized list)
+   * If this enivronment's list is non-empty, append its cleanup tasks to the
+   * assigning list.
+   */
+  public void setCleanup(List<Runnable> newCleanupList) {
+    newCleanupList.addAll(cleanup);
+    cleanup = newCleanupList;
+  }
+
+  /**
+   * Adds cleanup task to run when close() is called
+   * @param task is run when close() is called
+   */
+  public void addToCleanup(Runnable task) {
+    cleanup.add(task);
+  }
+
+  /**
+   * Release resources stored in interpreter's hash manager.
+   */
+  public void close() {
+    for (final Iterator<Runnable> i = cleanup.iterator(); i.hasNext();) {
+      try {
+        i.next().run();
+      } catch (Throwable err) {
+        log.error("Failed to execute cleanup for PrologEnvironment", err);
+      }
+      i.remove();
+    }
   }
 }
\ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java b/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java
index 895c70b..cdcbb41 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.reviewdb.PatchSetInfo;
 import com.google.gerrit.reviewdb.Project;
 import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.patch.PatchList;
 import com.google.gerrit.server.patch.PatchListCache;
 import com.google.gerrit.server.patch.PatchListKey;
@@ -32,7 +33,9 @@
 import com.googlecode.prolog_cafe.lang.Prolog;
 import com.googlecode.prolog_cafe.lang.SystemException;
 
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
 
 public final class StoredValues {
   public static final StoredValue<ReviewDb> REVIEW_DB = create(ReviewDb.class);
@@ -75,6 +78,30 @@
     }
   };
 
+  public static final StoredValue<Repository> REPOSITORY = new StoredValue<Repository>() {
+    @Override
+    public Repository createValue(Prolog engine) {
+      PrologEnvironment env = (PrologEnvironment) engine.control;
+      GitRepositoryManager gitMgr =
+        env.getInjector().getInstance(GitRepositoryManager.class);
+      Change change = StoredValues.CHANGE.get(engine);
+      Project.NameKey projectKey = change.getProject();
+      final Repository repo;
+      try {
+        repo = gitMgr.openRepository(projectKey);
+      } catch (RepositoryNotFoundException e) {
+        throw new SystemException(e.getMessage());
+      }
+      env.addToCleanup(new Runnable() {
+        @Override
+        public void run() {
+          repo.close();
+        }
+      });
+      return repo;
+    }
+  };
+
   private StoredValues() {
   }
 }
\ No newline at end of file
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 2c55462..6d44511 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
@@ -46,6 +46,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
 
@@ -243,8 +244,11 @@
       return ruleError("Patch set " + patchSetId + " is not current");
     }
 
+    List<Term> results = new ArrayList<Term>();
+    Term submitRule;
     ProjectState projectState = getProjectControl().getProjectState();
     PrologEnvironment env;
+
     try {
       env = projectState.newPrologEnvironment();
     } catch (CompileException err) {
@@ -252,76 +256,80 @@
           + getProject().getName(), err);
     }
 
-    env.set(StoredValues.REVIEW_DB, db);
-    env.set(StoredValues.CHANGE, change);
-    env.set(StoredValues.PATCH_SET_ID, patchSetId);
-    env.set(StoredValues.CHANGE_CONTROL, this);
-
-    Term submitRule = env.once(
-      "gerrit", "locate_submit_rule",
-      new VariableTerm());
-    if (submitRule == null) {
-      return logRuleError("No user:submit_rule found for "
-          + getProject().getName());
-    }
-
-    List<Term> results = new ArrayList<Term>();
     try {
-      for (Term[] template : env.all(
-          "gerrit", "can_submit",
-          submitRule,
-          new VariableTerm())) {
-        results.add(template[1]);
-      }
-    } catch (PrologException err) {
-      return logRuleError("Exception calling " + submitRule + " on change "
-          + change.getId() + " of " + getProject().getName(), err);
-    } catch (RuntimeException err) {
-      return logRuleError("Exception calling " + submitRule + " on change "
-          + change.getId() + " of " + getProject().getName(), err);
-    }
+      env.set(StoredValues.REVIEW_DB, db);
+      env.set(StoredValues.CHANGE, change);
+      env.set(StoredValues.PATCH_SET_ID, patchSetId);
+      env.set(StoredValues.CHANGE_CONTROL, this);
 
-    ProjectState parentState = projectState.getParentState();
-    PrologEnvironment childEnv = env;
-    Set<Project.NameKey> projectsSeen = new HashSet<Project.NameKey>();
-    projectsSeen.add(getProject().getNameKey());
-
-    while (parentState != null) {
-      if (!projectsSeen.add(parentState.getProject().getNameKey())) {
-        //parent has been seen before, stop walk up inheritance tree
-        break;
+      submitRule = env.once(
+        "gerrit", "locate_submit_rule",
+        new VariableTerm());
+      if (submitRule == null) {
+        return logRuleError("No user:submit_rule found for "
+            + getProject().getName());
       }
-      PrologEnvironment parentEnv;
+
       try {
-        parentEnv = parentState.newPrologEnvironment();
-      } catch (CompileException err) {
-        return logRuleError("Cannot consult rules.pl for "
-            + parentState.getProject().getName(), err);
-      }
-      parentEnv.copyStoredValues(childEnv);
-      Term filterRule =
-          parentEnv.once("gerrit", "locate_submit_filter", new VariableTerm());
-      if (filterRule != null) {
-        try {
-          Term resultsTerm = toListTerm(results);
-          results.clear();
-          Term[] template = parentEnv.once(
-              "gerrit", "filter_submit_results",
-              filterRule,
-              resultsTerm,
-              new VariableTerm());
-          results.addAll(((ListTerm) template[2]).toJava());
-        } catch (PrologException err) {
-          return logRuleError("Exception calling " + filterRule + " on change "
-              + change.getId() + " of " + parentState.getProject().getName(), err);
-        } catch (RuntimeException err) {
-          return logRuleError("Exception calling " + filterRule + " on change "
-              + change.getId() + " of " + parentState.getProject().getName(), err);
+        for (Term[] template : env.all(
+            "gerrit", "can_submit",
+            submitRule,
+            new VariableTerm())) {
+          results.add(template[1]);
         }
+      } catch (PrologException err) {
+        return logRuleError("Exception calling " + submitRule + " on change "
+            + change.getId() + " of " + getProject().getName(), err);
+      } catch (RuntimeException err) {
+        return logRuleError("Exception calling " + submitRule + " on change "
+            + change.getId() + " of " + getProject().getName(), err);
       }
 
-      parentState = parentState.getParentState();
-      childEnv = parentEnv;
+      ProjectState parentState = projectState.getParentState();
+      PrologEnvironment childEnv = env;
+      Set<Project.NameKey> projectsSeen = new HashSet<Project.NameKey>();
+      projectsSeen.add(getProject().getNameKey());
+
+      while (parentState != null) {
+        if (!projectsSeen.add(parentState.getProject().getNameKey())) {
+          //parent has been seen before, stop walk up inheritance tree
+          break;
+        }
+        PrologEnvironment parentEnv;
+        try {
+          parentEnv = parentState.newPrologEnvironment();
+        } catch (CompileException err) {
+          return logRuleError("Cannot consult rules.pl for "
+              + parentState.getProject().getName(), err);
+        }
+
+        parentEnv.copyStoredValues(childEnv);
+        Term filterRule =
+            parentEnv.once("gerrit", "locate_submit_filter", new VariableTerm());
+        if (filterRule != null) {
+          try {
+            Term resultsTerm = toListTerm(results);
+            results.clear();
+            Term[] template = parentEnv.once(
+                "gerrit", "filter_submit_results",
+                filterRule,
+                resultsTerm,
+                new VariableTerm());
+            results.addAll(((ListTerm) template[2]).toJava());
+          } catch (PrologException err) {
+            return logRuleError("Exception calling " + filterRule + " on change "
+                + change.getId() + " of " + parentState.getProject().getName(), err);
+          } catch (RuntimeException err) {
+            return logRuleError("Exception calling " + filterRule + " on change "
+                + change.getId() + " of " + parentState.getProject().getName(), err);
+          }
+        }
+
+        parentState = parentState.getParentState();
+        childEnv = parentEnv;
+      }
+    } finally {
+      env.close();
     }
 
     if (results.isEmpty()) {