summaryrefslogtreecommitdiffstats
path: root/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
diff options
context:
space:
mode:
Diffstat (limited to 'gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java')
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java249
1 files changed, 249 insertions, 0 deletions
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
new file mode 100644
index 0000000000..349567cbd8
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
@@ -0,0 +1,249 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+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;
+import com.google.gerrit.rules.PrologEnvironment;
+import com.google.gerrit.rules.StoredValues;
+import com.google.gerrit.server.query.change.ChangeData;
+
+import com.googlecode.prolog_cafe.compiler.CompileException;
+import com.googlecode.prolog_cafe.lang.ListTerm;
+import com.googlecode.prolog_cafe.lang.Prolog;
+import com.googlecode.prolog_cafe.lang.PrologException;
+import com.googlecode.prolog_cafe.lang.Term;
+import com.googlecode.prolog_cafe.lang.VariableTerm;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+/**
+ * Evaluates a submit-like Prolog rule found in the rules.pl file of the current
+ * project and filters the results through rules found in the parent projects,
+ * all the way up to All-Projects.
+ */
+public class SubmitRuleEvaluator {
+ private final ReviewDb db;
+ private final PatchSet patchSet;
+ private final ProjectControl projectControl;
+ private final ChangeControl changeControl;
+ private final Change change;
+ private final ChangeData cd;
+ private final boolean fastEvalLabels;
+ private final String userRuleLocatorName;
+ private final String userRuleWrapperName;
+ private final String filterRuleLocatorName;
+ private final String filterRuleWrapperName;
+ private final boolean skipFilters;
+ private final InputStream rulesInputStream;
+
+ private Term submitRule;
+ private String projectName;
+
+ /**
+ * @param userRuleLocatorName The name of the rule used to locate the
+ * user-supplied rule.
+ * @param userRuleWrapperName The name of the wrapper rule used to evaluate
+ * the user-supplied rule.
+ * @param filterRuleLocatorName The name of the rule used to locate the filter
+ * rule.
+ * @param filterRuleWrapperName The name of the rule used to evaluate the
+ * filter rule.
+ */
+ public SubmitRuleEvaluator(ReviewDb db, PatchSet patchSet,
+ ProjectControl projectControl,
+ ChangeControl changeControl, Change change, @Nullable ChangeData cd,
+ boolean fastEvalLabels,
+ String userRuleLocatorName, String userRuleWrapperName,
+ String filterRuleLocatorName, String filterRuleWrapperName) {
+ this(db, patchSet, projectControl, changeControl, change, cd,
+ fastEvalLabels, userRuleLocatorName, userRuleWrapperName,
+ filterRuleLocatorName, filterRuleWrapperName, false, null);
+ }
+
+ /**
+ * @param userRuleLocatorName The name of the rule used to locate the
+ * user-supplied rule.
+ * @param userRuleWrapperName The name of the wrapper rule used to evaluate
+ * the user-supplied rule.
+ * @param filterRuleLocatorName The name of the rule used to locate the filter
+ * rule.
+ * @param filterRuleWrapperName The name of the rule used to evaluate the
+ * filter rule.
+ * @param skipSubmitFilters if <code>true</code> submit filter will not be
+ * applied
+ * @param rules when non-null the rules will be read from this input stream
+ * instead of refs/meta/config:rules.pl file
+ */
+ public SubmitRuleEvaluator(ReviewDb db, PatchSet patchSet,
+ ProjectControl projectControl,
+ ChangeControl changeControl, Change change, @Nullable ChangeData cd,
+ boolean fastEvalLabels,
+ String userRuleLocatorName, String userRuleWrapperName,
+ String filterRuleLocatorName, String filterRuleWrapperName,
+ boolean skipSubmitFilters, InputStream rules) {
+ this.db = db;
+ this.patchSet = patchSet;
+ this.projectControl = projectControl;
+ this.changeControl = changeControl;
+ this.change = change;
+ this.cd = cd;
+ this.fastEvalLabels = fastEvalLabels;
+ this.userRuleLocatorName = userRuleLocatorName;
+ this.userRuleWrapperName = userRuleWrapperName;
+ this.filterRuleLocatorName = filterRuleLocatorName;
+ this.filterRuleWrapperName = filterRuleWrapperName;
+ this.skipFilters = skipSubmitFilters;
+ this.rulesInputStream = rules;
+ }
+
+ /**
+ * Evaluates the given rule and filters.
+ *
+ * Sets the {@link #submitRule} to the Term found by the
+ * {@link #userRuleLocatorName}. This can be used when reporting error(s) on
+ * unexpected return value of this method.
+ *
+ * @return List of {@link Term} objects returned from the evaluated rules.
+ * @throws RuleEvalException
+ */
+ public List<Term> evaluate() throws RuleEvalException {
+ PrologEnvironment env = getPrologEnvironment();
+ try {
+ submitRule = env.once("gerrit", userRuleLocatorName, new VariableTerm());
+ if (fastEvalLabels) {
+ env.once("gerrit", "assume_range_from_label");
+ }
+
+ List<Term> results = new ArrayList<Term>();
+ try {
+ for (Term[] template : env.all("gerrit", userRuleWrapperName,
+ submitRule, new VariableTerm())) {
+ results.add(template[1]);
+ }
+ } catch (PrologException err) {
+ throw new RuleEvalException("Exception calling " + submitRule
+ + " on change " + change.getId() + " of " + getProjectName(),
+ err);
+ } catch (RuntimeException err) {
+ throw new RuleEvalException("Exception calling " + submitRule
+ + " on change " + change.getId() + " of " + getProjectName(),
+ err);
+ }
+
+ Term resultsTerm = toListTerm(results);
+ if (!skipFilters) {
+ resultsTerm = runSubmitFilters(resultsTerm, env);
+ }
+ 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();
+ }
+ }
+
+ private PrologEnvironment getPrologEnvironment() throws RuleEvalException {
+ ProjectState projectState = projectControl.getProjectState();
+ PrologEnvironment env;
+ try {
+ if (rulesInputStream == null) {
+ env = projectState.newPrologEnvironment();
+ } else {
+ env = projectState.newPrologEnvironment("stdin", rulesInputStream);
+ }
+ } catch (CompileException err) {
+ throw new RuleEvalException("Cannot consult rules.pl for "
+ + getProjectName(), err);
+ }
+ env.set(StoredValues.REVIEW_DB, db);
+ env.set(StoredValues.CHANGE, change);
+ env.set(StoredValues.CHANGE_DATA, cd);
+ env.set(StoredValues.PATCH_SET, patchSet);
+ env.set(StoredValues.CHANGE_CONTROL, changeControl);
+ return env;
+ }
+
+ private Term runSubmitFilters(Term results, PrologEnvironment env) throws RuleEvalException {
+ ProjectState projectState = projectControl.getProjectState();
+ PrologEnvironment childEnv = env;
+ for (ProjectState parentState : projectState.parents()) {
+ PrologEnvironment parentEnv;
+ try {
+ parentEnv = parentState.newPrologEnvironment();
+ } catch (CompileException err) {
+ throw new RuleEvalException("Cannot consult rules.pl for "
+ + parentState.getProject().getName(), err);
+ }
+
+ parentEnv.copyStoredValues(childEnv);
+ Term filterRule =
+ parentEnv.once("gerrit", filterRuleLocatorName, new VariableTerm());
+ try {
+ if (fastEvalLabels) {
+ env.once("gerrit", "assume_range_from_label");
+ }
+
+ Term[] template =
+ parentEnv.once("gerrit", filterRuleWrapperName, filterRule,
+ results, new VariableTerm());
+ results = template[2];
+ } catch (PrologException err) {
+ throw new RuleEvalException("Exception calling " + filterRule
+ + " on change " + change.getId() + " of "
+ + parentState.getProject().getName(), err);
+ } catch (RuntimeException err) {
+ throw new RuleEvalException("Exception calling " + filterRule
+ + " on change " + change.getId() + " of "
+ + parentState.getProject().getName(), err);
+ }
+ childEnv = parentEnv;
+ }
+ return results;
+ }
+
+ private static Term toListTerm(List<Term> terms) {
+ Term list = Prolog.Nil;
+ for (int i = terms.size() - 1; i >= 0; i--) {
+ list = new ListTerm(terms.get(i), list);
+ }
+ return list;
+ }
+
+ public Term getSubmitRule() {
+ return submitRule;
+ }
+
+ private String getProjectName() {
+ if (projectName == null) {
+ projectName = projectControl.getProjectState().getProject().getName();
+ }
+ return projectName;
+ }
+}