// 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.collect.ImmutableList.toImmutableList; import com.google.common.collect.Streams; import com.google.common.flogger.FluentLogger; import com.google.gerrit.entities.Project; import com.google.gerrit.entities.SubmitRecord; import com.google.gerrit.entities.SubmitTypeRecord; import com.google.gerrit.exceptions.StorageException; import com.google.gerrit.extensions.api.changes.ChangeApi; import com.google.gerrit.metrics.Description; import com.google.gerrit.metrics.Description.Units; import com.google.gerrit.metrics.MetricMaker; import com.google.gerrit.metrics.Timer0; import com.google.gerrit.server.change.ChangeJson; import com.google.gerrit.server.index.OnlineReindexMode; import com.google.gerrit.server.logging.CallerFinder; import com.google.gerrit.server.plugincontext.PluginSetContext; import com.google.gerrit.server.query.change.ChangeData; import com.google.gerrit.server.rules.DefaultSubmitRule; import com.google.gerrit.server.rules.PrologSubmitRuleUtil; import com.google.gerrit.server.rules.SubmitRule; import com.google.inject.Inject; import com.google.inject.Singleton; import com.google.inject.assistedinject.Assisted; import java.util.List; import java.util.Optional; /** * 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 static final FluentLogger logger = FluentLogger.forEnclosingClass(); public interface Factory { /** Returns a new {@link SubmitRuleEvaluator} with the specified options */ SubmitRuleEvaluator create(SubmitRuleOptions options); } @Singleton private static class Metrics { final Timer0 submitRuleEvaluationLatency; final Timer0 submitTypeEvaluationLatency; @Inject Metrics(MetricMaker metricMaker) { submitRuleEvaluationLatency = metricMaker.newTimer( "change/submit_rule_evaluation", new Description("Latency for evaluating submit rules on a change.") .setCumulative() .setUnit(Units.MILLISECONDS)); submitTypeEvaluationLatency = metricMaker.newTimer( "change/submit_type_evaluation", new Description("Latency for evaluating the submit type on a change.") .setCumulative() .setUnit(Units.MILLISECONDS)); } } private final ProjectCache projectCache; private final PrologSubmitRuleUtil prologSubmitRuleUtil; private final PluginSetContext submitRules; private final Metrics metrics; private final SubmitRuleOptions opts; private final CallerFinder callerFinder; @Inject private SubmitRuleEvaluator( ProjectCache projectCache, PrologSubmitRuleUtil prologSubmitRuleUtil, PluginSetContext submitRules, Metrics metrics, @Assisted SubmitRuleOptions options) { this.projectCache = projectCache; this.prologSubmitRuleUtil = prologSubmitRuleUtil; this.submitRules = submitRules; this.metrics = metrics; this.opts = options; this.callerFinder = CallerFinder.builder() .addTarget(ChangeApi.class) .addTarget(ChangeJson.class) .addTarget(ChangeData.class) .addTarget(SubmitRequirementsEvaluatorImpl.class) .build(); } /** * Evaluate the submit rules. * * @return List of {@link SubmitRecord} objects returned from the evaluated rules, including any * errors. * @param cd ChangeData to evaluate */ public List evaluate(ChangeData cd) { logger.atFine().log( "Evaluate submit rules for change %d (caller: %s)", cd.change().getId().get(), callerFinder.findCallerLazy()); try (Timer0.Context ignored = metrics.submitRuleEvaluationLatency.start()) { if (cd.change() == null) { throw new StorageException("Change not found"); } ProjectState projectState = projectCache .get(cd.project()) .orElseThrow( () -> new IllegalStateException( "Unable to find project while evaluating submit rule", new NoSuchProjectException(cd.project()))); if (cd.change().isClosed() && (!opts.recomputeOnClosedChanges() || OnlineReindexMode.isActive())) { return cd.notes().getSubmitRecords().stream() .map( r -> { SubmitRecord record = r.deepCopy(); if (record.status == SubmitRecord.Status.OK) { // Submit records that were OK when they got merged are CLOSED now. record.status = SubmitRecord.Status.CLOSED; } return record; }) .collect(toImmutableList()); } // We evaluate all the plugin-defined evaluators, // and then we collect the results in one list. return Streams.stream(submitRules) // Skip evaluating the default submit rule if the project has prolog rules. // Note that in this case, the prolog submit rule will handle labels for us .filter( projectState.hasPrologRules() ? rule -> !(rule.get() instanceof DefaultSubmitRule) : rule -> true) .map( c -> c.call( s -> { Optional record = s.evaluate(cd); if (record.isPresent() && record.get().ruleName == null) { // Only back-fill the ruleName if it was not populated by the "submit // rule". record.get().ruleName = c.getPluginName() + "~" + s.getClass().getSimpleName(); } return record; })) .filter(Optional::isPresent) .map(Optional::get) .collect(toImmutableList()); } } /** * Evaluate the submit type rules to get the submit type. * * @return record from the evaluated rules. */ public SubmitTypeRecord getSubmitType(ChangeData cd) { try (Timer0.Context ignored = metrics.submitTypeEvaluationLatency.start()) { try { Project.NameKey name = cd.project(); Optional project = projectCache.get(name); if (!project.isPresent()) { throw new NoSuchProjectException(name); } } catch (NoSuchProjectException e) { throw new IllegalStateException("Unable to find project while evaluating submit rule", e); } return prologSubmitRuleUtil.getSubmitType(cd); } } }