All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gerrit.server.project.SubmitRequirementsEvaluatorImpl Maven / Gradle / Ivy

There is a newer version: 3.11.0-rc3
Show newest version
// Copyright (C) 2021 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.ImmutableMap.toImmutableMap;
import static com.google.gerrit.server.project.ProjectCache.illegalState;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.SubmitRequirement;
import com.google.gerrit.entities.SubmitRequirementExpression;
import com.google.gerrit.entities.SubmitRequirementExpressionResult;
import com.google.gerrit.entities.SubmitRequirementExpressionResult.PredicateResult;
import com.google.gerrit.entities.SubmitRequirementResult;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.SubmitRequirementChangeQueryBuilder;
import com.google.gerrit.server.util.ManualRequestContext;
import com.google.gerrit.server.util.OneOffRequestContext;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.Scopes;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;

/** Evaluates submit requirements for different change data. */
public class SubmitRequirementsEvaluatorImpl implements SubmitRequirementsEvaluator {
  private static final FluentLogger logger = FluentLogger.forEnclosingClass();

  private final Provider queryBuilder;
  private final ProjectCache projectCache;
  private final PluginSetContext globalSubmitRequirements;

  // Use a request context to execute predicates as an internal user with expanded visibility.
  // This is so that the evaluation does not depend on who is running the current request (e.g.
  // a "ownerin" predicate with group that is not visible to the person making this request).
  private final OneOffRequestContext requestContext;

  public static Module module() {
    return new AbstractModule() {
      @Override
      protected void configure() {
        bind(SubmitRequirementsEvaluator.class)
            .to(SubmitRequirementsEvaluatorImpl.class)
            .in(Scopes.SINGLETON);
      }
    };
  }

  @Inject
  private SubmitRequirementsEvaluatorImpl(
      Provider queryBuilder,
      ProjectCache projectCache,
      PluginSetContext globalSubmitRequirements,
      OneOffRequestContext requestContext) {
    this.queryBuilder = queryBuilder;
    this.projectCache = projectCache;
    this.globalSubmitRequirements = globalSubmitRequirements;
    this.requestContext = requestContext;
  }

  @Override
  public void validateExpression(SubmitRequirementExpression expression)
      throws QueryParseException {
    try (ManualRequestContext ignored = requestContext.open()) {
      @SuppressWarnings("unused")
      var unused = queryBuilder.get().parse(expression.expressionString());
    }
  }

  @Override
  public ImmutableMap evaluateAllRequirements(
      ChangeData cd) {
    try (ManualRequestContext ignored = requestContext.open()) {
      return getRequirements(cd);
    }
  }

  @Override
  public SubmitRequirementResult evaluateRequirement(SubmitRequirement sr, ChangeData cd) {
    try (ManualRequestContext ignored = requestContext.open()) {
      return evaluateRequirementInternal(sr, cd);
    }
  }

  /** Evaluate a {@link SubmitRequirementExpression} using change data. */
  @VisibleForTesting
  public SubmitRequirementExpressionResult evaluateExpression(
      SubmitRequirementExpression expression, ChangeData changeData) {
    try {
      Predicate predicate = queryBuilder.get().parse(expression.expressionString());
      PredicateResult predicateResult = evaluatePredicateTree(predicate, changeData);
      return SubmitRequirementExpressionResult.create(expression, predicateResult);
    } catch (QueryParseException | SubmitRequirementEvaluationException e) {
      logger.atWarning().withCause(e).log(
          "Failed to evaluate submit requirement expression: %s", expression.expressionString());
      return SubmitRequirementExpressionResult.error(expression, e.getMessage());
    }
  }

  private SubmitRequirementResult evaluateRequirementInternal(SubmitRequirement sr, ChangeData cd) {
    Optional applicabilityResult =
        sr.applicabilityExpression().isPresent()
            ? Optional.of(evaluateExpression(sr.applicabilityExpression().get(), cd))
            : Optional.empty();
    Optional submittabilityResult =
        Optional.of(SubmitRequirementExpressionResult.notEvaluated(sr.submittabilityExpression()));
    Optional overrideResult =
        sr.overrideExpression().isPresent()
            ? Optional.of(
                SubmitRequirementExpressionResult.notEvaluated(sr.overrideExpression().get()))
            : Optional.empty();
    if (!sr.applicabilityExpression().isPresent()
        || SubmitRequirementResult.assertPass(applicabilityResult)) {
      submittabilityResult = Optional.of(evaluateExpression(sr.submittabilityExpression(), cd));
      overrideResult =
          sr.overrideExpression().isPresent()
              ? Optional.of(evaluateExpression(sr.overrideExpression().get(), cd))
              : Optional.empty();
    }

    if (applicabilityResult.isPresent()) {
      logger.atFine().log(
          "Applicability expression result for SR name '%s':"
              + " passing atoms: %s, failing atoms: %s",
          sr.name(),
          applicabilityResult.get().passingAtoms(),
          applicabilityResult.get().failingAtoms());
    }
    if (submittabilityResult.isPresent()) {
      logger.atFine().log(
          "Submittability expression result for SR name '%s':"
              + " passing atoms: %s, failing atoms: %s",
          sr.name(),
          submittabilityResult.get().passingAtoms(),
          submittabilityResult.get().failingAtoms());
    }
    if (overrideResult.isPresent()) {
      logger.atFine().log(
          "Override expression result for SR name '%s':" + " passing atoms: %s, failing atoms: %s",
          sr.name(), overrideResult.get().passingAtoms(), overrideResult.get().failingAtoms());
    }

    return SubmitRequirementResult.builder()
        .legacy(Optional.of(false))
        .submitRequirement(sr)
        .patchSetCommitId(cd.currentPatchSet().commitId())
        .submittabilityExpressionResult(submittabilityResult)
        .applicabilityExpressionResult(applicabilityResult)
        .overrideExpressionResult(overrideResult)
        .build();
  }

  /**
   * Evaluate and return all {@link SubmitRequirement}s.
   *
   * 

This includes all globally bound {@link SubmitRequirement}s, as well as requirements stored * in this project's config and its parents. * *

The behaviour in case of the name match is controlled by {@link * SubmitRequirement#allowOverrideInChildProjects} of global {@link SubmitRequirement}. */ private ImmutableMap getRequirements(ChangeData cd) { try (TraceTimer timer = TraceContext.newTimer( "Get submit requirements", Metadata.builder().changeId(cd.change().getId().get()).build())) { ImmutableMap globalRequirements = getGlobalRequirements(); ProjectState state = projectCache.get(cd.project()).orElseThrow(illegalState(cd.project())); Map projectConfigRequirements = state.getSubmitRequirements(); ImmutableMap requirements = Stream.concat( globalRequirements.entrySet().stream(), projectConfigRequirements.entrySet().stream()) .collect( toImmutableMap( Map.Entry::getKey, Map.Entry::getValue, (globalSubmitRequirement, projectConfigRequirement) -> // Override with projectConfigRequirement if allowed by // globalSubmitRequirement configuration globalSubmitRequirement.allowOverrideInChildProjects() ? projectConfigRequirement : globalSubmitRequirement)); ImmutableMap.Builder results = ImmutableMap.builder(); for (SubmitRequirement requirement : requirements.values()) { results.put(requirement, evaluateRequirementInternal(requirement, cd)); } return results.build(); } } /** * Returns a map of all global {@link SubmitRequirement}s, keyed by their lower-case name. * *

The global {@link SubmitRequirement}s apply to all projects and can be bound by plugins. */ private ImmutableMap getGlobalRequirements() { return globalSubmitRequirements.stream() .collect( toImmutableMap( globalRequirement -> globalRequirement.name().toLowerCase(Locale.US), Function.identity())); } /** Evaluate the predicate recursively using change data. */ private PredicateResult evaluatePredicateTree( Predicate predicate, ChangeData changeData) { PredicateResult.Builder predicateResult = PredicateResult.builder() .predicateString(predicate.isLeaf() ? predicate.getPredicateString() : "") .status(predicate.asMatchable().match(changeData)); predicate .getChildren() .forEach( c -> predicateResult.addChildPredicateResult(evaluatePredicateTree(c, changeData))); return predicateResult.build(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy