ChangeControl: Extract Prolog submit rule evaluation to its own method
The code in ChangeControl.canSubmit() for finding and evaluating Prolog submit rules and converting the results into a list of SubmitRecord objects is extracted to a class of its own in preparation of future changes where rules with other names but the same input/output will be used for evaluating other things. Change-Id: Ie0d00433dac7c8ec41e3494fcaba0e432453bc06 Signed-off-by: Sasa Zivkov <sasa.zivkov@sap.com>
This commit is contained in:
committed by
Gerrit Code Review
parent
dad5620441
commit
ee59a26b39
@@ -23,8 +23,6 @@ import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.reviewdb.client.Project.SubmitType;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.rules.PrologEnvironment;
|
||||
import com.google.gerrit.rules.StoredValues;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.query.change.ChangeData;
|
||||
@@ -33,23 +31,18 @@ import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.util.Providers;
|
||||
|
||||
import com.googlecode.prolog_cafe.compiler.CompileException;
|
||||
import com.googlecode.prolog_cafe.lang.IntegerTerm;
|
||||
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.StructureTerm;
|
||||
import com.googlecode.prolog_cafe.lang.Term;
|
||||
import com.googlecode.prolog_cafe.lang.VariableTerm;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@@ -335,103 +328,18 @@ public class ChangeControl {
|
||||
return logRuleError("Cannot read patch set " + patchSet.getId(), err);
|
||||
}
|
||||
|
||||
List<Term> results = new ArrayList<Term>();
|
||||
Term submitRule;
|
||||
ProjectState projectState = getProjectControl().getProjectState();
|
||||
PrologEnvironment env;
|
||||
|
||||
List<Term> results;
|
||||
SubmitRuleEvaluator evaluator;
|
||||
try {
|
||||
env = projectState.newPrologEnvironment();
|
||||
} catch (CompileException err) {
|
||||
return logRuleError("Cannot consult rules.pl for "
|
||||
+ getProject().getName(), err);
|
||||
}
|
||||
|
||||
try {
|
||||
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, this);
|
||||
|
||||
submitRule = env.once(
|
||||
"gerrit", "locate_submit_rule",
|
||||
new VariableTerm());
|
||||
if (submitRule == null) {
|
||||
return logRuleError("No user:submit_rule found for "
|
||||
+ getProject().getName());
|
||||
}
|
||||
|
||||
if (fastEvalLabels) {
|
||||
env.once("gerrit", "assume_range_from_label");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 {
|
||||
if (fastEvalLabels) {
|
||||
env.once("gerrit", "assume_range_from_label");
|
||||
}
|
||||
|
||||
Term resultsTerm = toListTerm(results);
|
||||
results.clear();
|
||||
Term[] template = parentEnv.once(
|
||||
"gerrit", "filter_submit_results",
|
||||
filterRule,
|
||||
resultsTerm,
|
||||
new VariableTerm());
|
||||
@SuppressWarnings("unchecked")
|
||||
final List<? extends Term> termList = ((ListTerm) template[2]).toJava();
|
||||
results.addAll(termList);
|
||||
} 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();
|
||||
evaluator = new SubmitRuleEvaluator(db, patchSet,
|
||||
getProjectControl(),
|
||||
this, change, cd,
|
||||
fastEvalLabels,
|
||||
"locate_submit_rule", "can_submit",
|
||||
"locate_submit_filter", "filter_submit_results");
|
||||
results = evaluator.evaluate();
|
||||
} catch (RuleEvalException e) {
|
||||
return logRuleError(e.getMessage(), e);
|
||||
}
|
||||
|
||||
if (results.isEmpty()) {
|
||||
@@ -439,12 +347,13 @@ public class ChangeControl {
|
||||
// at least one result informing the caller of the labels that are
|
||||
// required for this change to be submittable. Each label will indicate
|
||||
// whether or not that is actually possible given the permissions.
|
||||
log.error("Submit rule " + submitRule + " for change " + change.getId()
|
||||
+ " of " + getProject().getName() + " has no solution.");
|
||||
log.error("Submit rule '" + evaluator.getSubmitRule() + "' for change "
|
||||
+ change.getId() + " of " + getProject().getName()
|
||||
+ " has no solution.");
|
||||
return ruleError("Project submit rule has no solution");
|
||||
}
|
||||
|
||||
return resultsToSubmitRecord(submitRule, results);
|
||||
return resultsToSubmitRecord(evaluator.getSubmitRule(), results);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class RuleEvalException extends Exception {
|
||||
public RuleEvalException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
RuleEvalException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
// 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.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
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.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
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 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.
|
||||
*/
|
||||
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 = 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
List<Term> evaluate() throws RuleEvalException {
|
||||
List<Term> results = new ArrayList<Term>();
|
||||
ProjectState projectState = projectControl.getProjectState();
|
||||
PrologEnvironment env;
|
||||
|
||||
try {
|
||||
env = projectState.newPrologEnvironment();
|
||||
} catch (CompileException err) {
|
||||
throw new RuleEvalException("Cannot consult rules.pl for "
|
||||
+ getProjectName(), err);
|
||||
}
|
||||
|
||||
try {
|
||||
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);
|
||||
|
||||
submitRule = env.once("gerrit", userRuleLocatorName, new VariableTerm());
|
||||
if (submitRule == null) {
|
||||
throw new RuleEvalException(userRuleLocatorName + " returned null");
|
||||
}
|
||||
|
||||
if (fastEvalLabels) {
|
||||
env.once("gerrit", "assume_range_from_label");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
ProjectState parentState = projectState.getParentState();
|
||||
PrologEnvironment childEnv = env;
|
||||
Set<Project.NameKey> projectsSeen = new HashSet<Project.NameKey>();
|
||||
projectsSeen.add(projectState.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) {
|
||||
throw new RuleEvalException("Cannot consult rules.pl for "
|
||||
+ parentState.getProject().getName(), err);
|
||||
}
|
||||
|
||||
parentEnv.copyStoredValues(childEnv);
|
||||
Term filterRule =
|
||||
parentEnv.once("gerrit", filterRuleLocatorName, new VariableTerm());
|
||||
if (filterRule != null) {
|
||||
try {
|
||||
if (fastEvalLabels) {
|
||||
env.once("gerrit", "assume_range_from_label");
|
||||
}
|
||||
|
||||
Term resultsTerm = toListTerm(results);
|
||||
results.clear();
|
||||
Term[] template =
|
||||
parentEnv.once("gerrit", filterRuleWrapperName, filterRule,
|
||||
resultsTerm, new VariableTerm());
|
||||
@SuppressWarnings("unchecked")
|
||||
final List<? extends Term> termList =
|
||||
((ListTerm) template[2]).toJava();
|
||||
results.addAll(termList);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
parentState = parentState.getParentState();
|
||||
childEnv = parentEnv;
|
||||
}
|
||||
} finally {
|
||||
env.close();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Term getSubmitRule() {
|
||||
return submitRule;
|
||||
}
|
||||
|
||||
private String getProjectName() {
|
||||
if (projectName == null) {
|
||||
projectName = projectControl.getProjectState().getProject().getName();
|
||||
}
|
||||
return projectName;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user