Line data Source code
1 : // Copyright (C) 2012 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.project; 16 : 17 : import static com.google.common.collect.ImmutableList.toImmutableList; 18 : 19 : import com.google.common.collect.Streams; 20 : import com.google.common.flogger.FluentLogger; 21 : import com.google.gerrit.entities.Change; 22 : import com.google.gerrit.entities.Project; 23 : import com.google.gerrit.entities.SubmitRecord; 24 : import com.google.gerrit.entities.SubmitTypeRecord; 25 : import com.google.gerrit.exceptions.StorageException; 26 : import com.google.gerrit.extensions.api.changes.ChangeApi; 27 : import com.google.gerrit.metrics.Description; 28 : import com.google.gerrit.metrics.Description.Units; 29 : import com.google.gerrit.metrics.MetricMaker; 30 : import com.google.gerrit.metrics.Timer0; 31 : import com.google.gerrit.server.change.ChangeJson; 32 : import com.google.gerrit.server.index.OnlineReindexMode; 33 : import com.google.gerrit.server.logging.CallerFinder; 34 : import com.google.gerrit.server.plugincontext.PluginSetContext; 35 : import com.google.gerrit.server.query.change.ChangeData; 36 : import com.google.gerrit.server.rules.DefaultSubmitRule; 37 : import com.google.gerrit.server.rules.PrologRule; 38 : import com.google.gerrit.server.rules.SubmitRule; 39 : import com.google.inject.Inject; 40 : import com.google.inject.assistedinject.Assisted; 41 : import java.util.List; 42 : import java.util.Optional; 43 : 44 : /** 45 : * Evaluates a submit-like Prolog rule found in the rules.pl file of the current project and filters 46 : * the results through rules found in the parent projects, all the way up to All-Projects. 47 : */ 48 : public class SubmitRuleEvaluator { 49 103 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 50 : 51 : private final ProjectCache projectCache; 52 : private final PrologRule prologRule; 53 : private final PluginSetContext<SubmitRule> submitRules; 54 : private final Timer0 submitRuleEvaluationLatency; 55 : private final Timer0 submitTypeEvaluationLatency; 56 : private final SubmitRuleOptions opts; 57 : private final CallerFinder callerFinder; 58 : 59 : public interface Factory { 60 : /** Returns a new {@link SubmitRuleEvaluator} with the specified options */ 61 : SubmitRuleEvaluator create(SubmitRuleOptions options); 62 : } 63 : 64 : @Inject 65 : private SubmitRuleEvaluator( 66 : ProjectCache projectCache, 67 : PrologRule prologRule, 68 : PluginSetContext<SubmitRule> submitRules, 69 : MetricMaker metricMaker, 70 103 : @Assisted SubmitRuleOptions options) { 71 103 : this.projectCache = projectCache; 72 103 : this.prologRule = prologRule; 73 103 : this.submitRules = submitRules; 74 103 : this.submitRuleEvaluationLatency = 75 103 : metricMaker.newTimer( 76 : "change/submit_rule_evaluation", 77 : new Description("Latency for evaluating submit rules on a change.") 78 103 : .setCumulative() 79 103 : .setUnit(Units.MILLISECONDS)); 80 103 : this.submitTypeEvaluationLatency = 81 103 : metricMaker.newTimer( 82 : "change/submit_type_evaluation", 83 : new Description("Latency for evaluating the submit type on a change.") 84 103 : .setCumulative() 85 103 : .setUnit(Units.MILLISECONDS)); 86 : 87 103 : this.opts = options; 88 : 89 103 : this.callerFinder = 90 103 : CallerFinder.builder() 91 103 : .addTarget(ChangeApi.class) 92 103 : .addTarget(ChangeJson.class) 93 103 : .addTarget(ChangeData.class) 94 103 : .addTarget(SubmitRequirementsEvaluatorImpl.class) 95 103 : .build(); 96 103 : } 97 : 98 : /** 99 : * Evaluate the submit rules. 100 : * 101 : * @return List of {@link SubmitRecord} objects returned from the evaluated rules, including any 102 : * errors. 103 : * @param cd ChangeData to evaluate 104 : */ 105 : public List<SubmitRecord> evaluate(ChangeData cd) { 106 103 : logger.atFine().log( 107 : "Evaluate submit rules for change %d (caller: %s)", 108 103 : cd.change().getId().get(), callerFinder.findCallerLazy()); 109 103 : try (Timer0.Context ignored = submitRuleEvaluationLatency.start()) { 110 : Change change; 111 : ProjectState projectState; 112 : try { 113 103 : change = cd.change(); 114 103 : if (change == null) { 115 0 : throw new StorageException("Change not found"); 116 : } 117 : 118 103 : Project.NameKey name = cd.project(); 119 103 : Optional<ProjectState> projectStateOptional = projectCache.get(name); 120 103 : if (!projectStateOptional.isPresent()) { 121 0 : throw new NoSuchProjectException(name); 122 : } 123 103 : projectState = projectStateOptional.get(); 124 0 : } catch (NoSuchProjectException e) { 125 0 : throw new IllegalStateException("Unable to find project while evaluating submit rule", e); 126 103 : } 127 : 128 103 : if (change.isClosed() && (!opts.recomputeOnClosedChanges() || OnlineReindexMode.isActive())) { 129 57 : return cd.notes().getSubmitRecords().stream() 130 57 : .map( 131 : r -> { 132 54 : SubmitRecord record = r.deepCopy(); 133 54 : if (record.status == SubmitRecord.Status.OK) { 134 : // Submit records that were OK when they got merged are CLOSED now. 135 47 : record.status = SubmitRecord.Status.CLOSED; 136 : } 137 54 : return record; 138 : }) 139 57 : .collect(toImmutableList()); 140 : } 141 : 142 : // We evaluate all the plugin-defined evaluators, 143 : // and then we collect the results in one list. 144 103 : return Streams.stream(submitRules) 145 : // Skip evaluating the default submit rule if the project has prolog rules. 146 : // Note that in this case, the prolog submit rule will handle labels for us 147 103 : .filter( 148 103 : projectState.hasPrologRules() 149 5 : ? rule -> !(rule.get() instanceof DefaultSubmitRule) 150 103 : : rule -> true) 151 103 : .map( 152 : c -> 153 103 : c.call( 154 : s -> { 155 103 : Optional<SubmitRecord> record = s.evaluate(cd); 156 103 : if (record.isPresent() && record.get().ruleName == null) { 157 : // Only back-fill the ruleName if it was not populated by the "submit 158 : // rule". 159 103 : record.get().ruleName = 160 103 : c.getPluginName() + "~" + s.getClass().getSimpleName(); 161 : } 162 103 : return record; 163 : })) 164 103 : .filter(Optional::isPresent) 165 103 : .map(Optional::get) 166 103 : .collect(toImmutableList()); 167 57 : } 168 : } 169 : 170 : /** 171 : * Evaluate the submit type rules to get the submit type. 172 : * 173 : * @return record from the evaluated rules. 174 : */ 175 : public SubmitTypeRecord getSubmitType(ChangeData cd) { 176 103 : try (Timer0.Context ignored = submitTypeEvaluationLatency.start()) { 177 : try { 178 103 : Project.NameKey name = cd.project(); 179 103 : Optional<ProjectState> project = projectCache.get(name); 180 103 : if (!project.isPresent()) { 181 0 : throw new NoSuchProjectException(name); 182 : } 183 0 : } catch (NoSuchProjectException e) { 184 0 : throw new IllegalStateException("Unable to find project while evaluating submit rule", e); 185 103 : } 186 : 187 103 : return prologRule.getSubmitType(cd); 188 : } 189 : } 190 : }