| // 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 static com.google.common.base.Preconditions.checkNotNull; |
| |
| 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; |
| |
| /** |
| * 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, 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} 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, 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 = checkNotNull(cd, "ChangeData"); |
| 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_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; |
| } |
| } |