public Response apply()

in java/com/google/gerrit/server/restapi/change/PostReview.java [281:455]


  public Response<ReviewResult> apply(RevisionResource revision, ReviewInput input, Instant ts)
      throws RestApiException, UpdateException, IOException, PermissionBackendException,
          ConfigInvalidException, PatchListNotAvailableException {
    // Respect timestamp, but truncate at change created-on time.
    ts = Ordering.natural().max(ts, revision.getChange().getCreatedOn());
    if (revision.getEdit().isPresent()) {
      throw new ResourceConflictException("cannot post review on edit");
    }
    ProjectState projectState =
        projectCache.get(revision.getProject()).orElseThrow(illegalState(revision.getProject()));
    LabelTypes labelTypes = projectState.getLabelTypes(revision.getNotes());

    logger.atFine().log("strict label checking is %s", (strictLabels ? "enabled" : "disabled"));

    metrics.draftHandling.increment(input.drafts == null ? "N/A" : input.drafts.name());
    input.drafts = firstNonNull(input.drafts, DraftHandling.KEEP);
    logger.atFine().log("draft handling = %s", input.drafts);

    if (input.onBehalfOf != null) {
      revision = onBehalfOf(revision, labelTypes, input);
    }
    if (input.labels != null) {
      checkLabels(revision, labelTypes, input.labels);
    }
    if (input.comments != null) {
      input.comments = cleanUpComments(input.comments);
      checkComments(revision, input.comments);
    }
    if (input.draftIdsToPublish != null) {
      checkDraftIds(revision, input.draftIdsToPublish, input.drafts);
    }
    if (input.robotComments != null) {
      input.robotComments = cleanUpComments(input.robotComments);
      checkRobotComments(revision, input.robotComments);
    }

    if (input.notify == null) {
      input.notify = defaultNotify(revision.getChange(), input);
    }
    logger.atFine().log("notify handling = %s", input.notify);

    Map<String, ReviewerResult> reviewerJsonResults = null;
    List<ReviewerModification> reviewerResults = Lists.newArrayList();
    boolean hasError = false;
    boolean confirm = false;
    if (input.reviewers != null) {
      reviewerJsonResults = Maps.newHashMap();
      for (ReviewerInput reviewerInput : input.reviewers) {
        ReviewerModification result =
            reviewerModifier.prepare(revision.getNotes(), revision.getUser(), reviewerInput, true);
        reviewerJsonResults.put(reviewerInput.reviewer, result.result);
        if (result.result.error != null) {
          logger.atFine().log(
              "Adding %s as reviewer failed: %s", reviewerInput.reviewer, result.result.error);
          hasError = true;
          continue;
        }
        if (result.result.confirm != null) {
          logger.atFine().log(
              "Adding %s as reviewer requires confirmation", reviewerInput.reviewer);
          confirm = true;
          continue;
        }
        logger.atFine().log("Adding %s as reviewer was prepared", reviewerInput.reviewer);
        reviewerResults.add(result);
      }
    }

    ReviewResult output = new ReviewResult();
    output.reviewers = reviewerJsonResults;
    if (hasError || confirm) {
      output.error = ERROR_ADDING_REVIEWER;
      return Response.withStatusCode(SC_BAD_REQUEST, output);
    }
    output.labels = input.labels;

    try (BatchUpdate bu =
        updateFactory.create(revision.getChange().getProject(), revision.getUser(), ts)) {
      Account account = revision.getUser().asIdentifiedUser().getAccount();
      boolean ccOrReviewer = false;
      if (input.labels != null && !input.labels.isEmpty()) {
        ccOrReviewer = input.labels.values().stream().anyMatch(v -> v != 0);
        if (ccOrReviewer) {
          logger.atFine().log("calling user is cc/reviewer on the change due to voting on a label");
        }
      }

      if (!ccOrReviewer) {
        // Check if user was already CCed or reviewing prior to this review.
        ReviewerSet currentReviewers =
            approvalsUtil.getReviewers(revision.getChangeResource().getNotes());
        ccOrReviewer = currentReviewers.all().contains(account.id());
        if (ccOrReviewer) {
          logger.atFine().log("calling user is already cc/reviewer on the change");
        }
      }

      // Apply reviewer changes first. Revision emails should be sent to the
      // updated set of reviewers. Also keep track of whether the user added
      // themselves as a reviewer or to the CC list.
      logger.atFine().log("adding reviewer additions");
      for (ReviewerModification reviewerResult : reviewerResults) {
        reviewerResult.op.suppressEmail(); // Send a single batch email below.
        reviewerResult.op.suppressEvent(); // Send events below, if possible as batch.
        bu.addOp(revision.getChange().getId(), reviewerResult.op);
        if (!ccOrReviewer && reviewerResult.reviewers.contains(account)) {
          logger.atFine().log("calling user is explicitly added as reviewer or CC");
          ccOrReviewer = true;
        }
      }

      if (!ccOrReviewer) {
        // User posting this review isn't currently in the reviewer or CC list,
        // isn't being explicitly added, and isn't voting on any label.
        // Automatically CC them on this change so they receive replies.
        logger.atFine().log("CCing calling user");
        ReviewerModification selfAddition =
            reviewerModifier.ccCurrentUser(revision.getUser(), revision);
        selfAddition.op.suppressEmail();
        selfAddition.op.suppressEvent();
        bu.addOp(revision.getChange().getId(), selfAddition.op);
      }

      // Add WorkInProgressOp if requested.
      if ((input.ready || input.workInProgress)
          && didWorkInProgressChange(revision.getChange().isWorkInProgress(), input)) {
        if (input.ready && input.workInProgress) {
          output.error = ERROR_WIP_READY_MUTUALLY_EXCLUSIVE;
          return Response.withStatusCode(SC_BAD_REQUEST, output);
        }

        revision
            .getChangeResource()
            .permissions()
            .check(ChangePermission.TOGGLE_WORK_IN_PROGRESS_STATE);

        if (input.ready) {
          output.ready = true;
        }

        logger.atFine().log("setting work-in-progress to %s", input.workInProgress);
        WorkInProgressOp wipOp =
            workInProgressOpFactory.create(input.workInProgress, new WorkInProgressOp.Input());
        wipOp.suppressEmail();
        bu.addOp(revision.getChange().getId(), wipOp);
      }

      // Add the review op.
      logger.atFine().log("posting review");
      bu.addOp(
          revision.getChange().getId(), new Op(projectState, revision.getPatchSet().id(), input));

      // Notify based on ReviewInput, ignoring the notify settings from any ReviewerInputs.
      NotifyResolver.Result notify = notifyResolver.resolve(input.notify, input.notifyDetails);
      bu.setNotify(notify);

      // Adjust the attention set based on the input
      replyAttentionSetUpdates.updateAttentionSet(
          bu, revision.getNotes(), input, revision.getUser());
      bu.execute();

      // Re-read change to take into account results of the update.
      ChangeData cd = changeDataFactory.create(revision.getProject(), revision.getChange().getId());
      for (ReviewerModification reviewerResult : reviewerResults) {
        reviewerResult.gatherResults(cd);
      }

      // Sending emails and events from ReviewersOps was suppressed so we can send a single batch
      // email/event here.
      batchEmailReviewers(revision.getUser(), revision.getChange(), reviewerResults, notify);
      batchReviewerEvents(revision.getUser(), cd, revision.getPatchSet(), reviewerResults, ts);
    }

    return Response.ok(output);
  }