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

com.google.gerrit.server.notedb.AllUsersAsyncUpdate Maven / Gradle / Ivy

The newest version!
// Copyright (C) 2019 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.notedb;

import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.CHANGE_MODIFICATION;

import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.git.RefUpdateUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.FanOutExecutor;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.update.context.RefUpdateContext;
import com.google.inject.Inject;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.transport.PushCertificate;

/**
 * Performs an update on {@code All-Users} asynchronously if required. No-op in case no updates were
 * scheduled for asynchronous execution.
 */
public class AllUsersAsyncUpdate {
  private static final FluentLogger logger = FluentLogger.forEnclosingClass();

  private final ExecutorService executor;
  private final AllUsersName allUsersName;
  private final GitRepositoryManager repoManager;
  private final GitReferenceUpdated gitReferenceUpdated;
  private final ListMultimap draftUpdates;

  private PersonIdent serverIdent;

  @Inject
  AllUsersAsyncUpdate(
      @FanOutExecutor ExecutorService executor,
      AllUsersName allUsersName,
      GitRepositoryManager repoManager,
      GitReferenceUpdated gitReferenceUpdated) {
    this.executor = executor;
    this.allUsersName = allUsersName;
    this.repoManager = repoManager;
    this.gitReferenceUpdated = gitReferenceUpdated;
    this.draftUpdates = MultimapBuilder.hashKeys().arrayListValues().build();
  }

  void setDraftUpdates(ListMultimap draftUpdates) {
    checkState(isEmpty(), "attempted to set draft comment updates for async execution twice");
    boolean allPublishOnly =
        draftUpdates.values().stream().allMatch(ChangeDraftNotesUpdate::canRunAsync);
    checkState(allPublishOnly, "not all updates can be run asynchronously");
    // Add deep copies to avoid any threading issues.
    for (Map.Entry entry : draftUpdates.entries()) {
      this.draftUpdates.put(entry.getKey(), entry.getValue().copy());
    }
    if (draftUpdates.size() > 0) {
      // Save the PersonIdent for later so that we get consistent time stamps in the commit and ref
      // log.
      serverIdent = Iterables.get(draftUpdates.entries(), 0).getValue().serverIdent;
    }
  }

  /** Returns true if no operations should be performed on the repo. */
  boolean isEmpty() {
    return draftUpdates.isEmpty();
  }

  /** Executes repository update asynchronously. No-op in case no updates were scheduled. */
  void execute(
      PersonIdent refLogIdent,
      String refLogMessage,
      PushCertificate pushCert,
      CurrentUser currentUser) {
    if (isEmpty()) {
      return;
    }

    @SuppressWarnings("unused")
    Future possiblyIgnoredError =
        executor.submit(
            () -> {
              try (RefUpdateContext ctx = RefUpdateContext.open(CHANGE_MODIFICATION)) {
                try (OpenRepo allUsersRepo = OpenRepo.open(repoManager, allUsersName)) {
                  allUsersRepo.addUpdatesNoLimits(draftUpdates);
                  allUsersRepo.flush();
                  BatchRefUpdate bru = allUsersRepo.repo.getRefDatabase().newBatchUpdate();
                  bru.setPushCertificate(pushCert);
                  if (refLogMessage != null) {
                    bru.setRefLogMessage(refLogMessage, false);
                  } else {
                    bru.setRefLogMessage(
                        firstNonNull(NoteDbUtil.guessRestApiHandler(), "Update NoteDb refs async"),
                        false);
                  }
                  bru.setRefLogIdent(refLogIdent != null ? refLogIdent : serverIdent);
                  bru.setAtomic(true);
                  allUsersRepo.cmds.addTo(bru);
                  bru.setAllowNonFastForwards(true);
                  RefUpdateUtil.executeChecked(bru, allUsersRepo.rw);
                  gitReferenceUpdated.fire(allUsersName, bru, getAccountState(currentUser));
                } catch (IOException e) {
                  logger.atSevere().withCause(e).log(
                      "Failed to delete draft comments asynchronously after publishing them");
                }
              }
            });
  }

  @Nullable
  private static AccountState getAccountState(CurrentUser user) {
    return user.isIdentifiedUser() ? user.asIdentifiedUser().state() : null;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy