// Copyright (C) 2017 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.index.group;

import static com.google.gerrit.server.git.QueueProvider.QueueType.BATCH;

import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.events.GroupIndexedListener;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.index.Index;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.index.IndexExecutor;
import com.google.gerrit.server.index.IndexUtils;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
import java.util.concurrent.Future;
import org.eclipse.jgit.lib.Config;

public class GroupIndexerImpl implements GroupIndexer {
  public interface Factory {
    GroupIndexerImpl create(GroupIndexCollection indexes);

    GroupIndexerImpl create(@Nullable GroupIndex index);
  }

  private final GroupCache groupCache;
  private final DynamicSet<GroupIndexedListener> indexedListener;
  private final StalenessChecker stalenessChecker;
  private final ListeningExecutorService batchExecutor;
  private final boolean autoReindexIfStale;
  @Nullable private final GroupIndexCollection indexes;
  @Nullable private final GroupIndex index;

  @AssistedInject
  GroupIndexerImpl(
      GroupCache groupCache,
      DynamicSet<GroupIndexedListener> indexedListener,
      StalenessChecker stalenessChecker,
      @IndexExecutor(BATCH) ListeningExecutorService batchExecutor,
      @GerritServerConfig Config config,
      @Assisted GroupIndexCollection indexes) {
    this.groupCache = groupCache;
    this.indexedListener = indexedListener;
    this.stalenessChecker = stalenessChecker;
    this.batchExecutor = batchExecutor;
    this.autoReindexIfStale = autoReindexIfStale(config);
    this.indexes = indexes;
    this.index = null;
  }

  @AssistedInject
  GroupIndexerImpl(
      GroupCache groupCache,
      DynamicSet<GroupIndexedListener> indexedListener,
      StalenessChecker stalenessChecker,
      @IndexExecutor(BATCH) ListeningExecutorService batchExecutor,
      @GerritServerConfig Config config,
      @Assisted @Nullable GroupIndex index) {
    this.groupCache = groupCache;
    this.indexedListener = indexedListener;
    this.stalenessChecker = stalenessChecker;
    this.batchExecutor = batchExecutor;
    this.autoReindexIfStale = autoReindexIfStale(config);
    this.indexes = null;
    this.index = index;
  }

  @Override
  public void index(AccountGroup.UUID uuid) throws IOException {
    for (Index<AccountGroup.UUID, InternalGroup> i : getWriteIndexes()) {
      // Evict the cache to get an up-to-date value for sure.
      groupCache.evict(uuid);
      Optional<InternalGroup> internalGroup = groupCache.get(uuid);
      if (internalGroup.isPresent()) {
        i.replace(internalGroup.get());
      } else {
        i.delete(uuid);
      }
    }
    fireGroupIndexedEvent(uuid.get());
    autoReindexIfStale(uuid);
  }

  @Override
  public boolean reindexIfStale(AccountGroup.UUID uuid) throws IOException {
    if (stalenessChecker.isStale(uuid)) {
      index(uuid);
      return true;
    }
    return false;
  }

  private static boolean autoReindexIfStale(Config cfg) {
    return cfg.getBoolean("index", null, "autoReindexIfStale", true);
  }

  private void autoReindexIfStale(AccountGroup.UUID uuid) {
    if (autoReindexIfStale) {
      // Don't retry indefinitely; if this fails the group will be stale.
      @SuppressWarnings("unused")
      Future<?> possiblyIgnoredError = reindexIfStaleAsync(uuid);
    }
  }

  /**
   * Asynchronously check if a group is stale, and reindex if it is.
   *
   * <p>Always run on the batch executor, even if this indexer instance is configured to use a
   * different executor.
   *
   * @param uuid the unique identifier of the group.
   * @return future for reindexing the group; returns true if the group was stale.
   */
  @SuppressWarnings("deprecation")
  private com.google.common.util.concurrent.CheckedFuture<Boolean, IOException> reindexIfStaleAsync(
      AccountGroup.UUID uuid) {
    return Futures.makeChecked(
        Futures.nonCancellationPropagating(batchExecutor.submit(() -> reindexIfStale(uuid))),
        IndexUtils.MAPPER);
  }

  private void fireGroupIndexedEvent(String uuid) {
    for (GroupIndexedListener listener : indexedListener) {
      listener.onGroupIndexed(uuid);
    }
  }

  private Collection<GroupIndex> getWriteIndexes() {
    if (indexes != null) {
      return indexes.getWriteIndexes();
    }

    return index != null ? Collections.singleton(index) : ImmutableSet.of();
  }
}

@@ -660,238 +660,46 @@
 ort 
-static com.google.gerrit.server.git.QueueProvider.QueueType.BATCH;%0A%0Aimport com.google.common.collect.ImmutableSet;%0Aimport com.google.common.util.concurrent.Futures;%0Aimport com.google.common.util.concurrent.ListeningExecutorService
+com.google.common.collect.ImmutableSet
 ;%0Aim
@@ -1045,189 +1045,27 @@
 ver.
-config.GerritServerConfig;%0Aimport com.google.gerrit.server.group.InternalGroup;%0Aimport com.google.gerrit.server.index.IndexExecutor;%0Aimport com.google.gerrit.server.index.IndexUtils
+group.InternalGroup
 ;%0Aim
@@ -1285,80 +1285,8 @@
 nal;
-%0Aimport java.util.concurrent.Future;%0Aimport org.eclipse.jgit.lib.Config;
 %0A%0Apu
@@ -1650,108 +1650,8 @@
 er;%0A
-  private final ListeningExecutorService batchExecutor;%0A  private final boolean autoReindexIfStale;%0A
   @N
@@ -1915,117 +1915,8 @@
 er,%0A
-      @IndexExecutor(BATCH) ListeningExecutorService batchExecutor,%0A      @GerritServerConfig Config config,%0A
     
@@ -2087,106 +2087,8 @@
 er;%0A
-    this.batchExecutor = batchExecutor;%0A    this.autoReindexIfStale = autoReindexIfStale(config);%0A
     
@@ -2307,117 +2307,8 @@
 er,%0A
-      @IndexExecutor(BATCH) ListeningExecutorService batchExecutor,%0A      @GerritServerConfig Config config,%0A
     
@@ -2477,106 +2477,8 @@
 er;%0A
-    this.batchExecutor = batchExecutor;%0A    this.autoReindexIfStale = autoReindexIfStale(config);%0A
     
@@ -3013,38 +3013,8 @@
 ));%0A
-    autoReindexIfStale(uuid);%0A
   %7D%0A
@@ -3216,1090 +3216,8 @@
  %7D%0A%0A
-  private static boolean autoReindexIfStale(Config cfg) %7B%0A    return cfg.getBoolean(%22index%22, null, %22autoReindexIfStale%22, true);%0A  %7D%0A%0A  private void autoReindexIfStale(AccountGroup.UUID uuid) %7B%0A    if (autoReindexIfStale) %7B%0A      // Don't retry indefinitely; if this fails the group will be stale.%0A      @SuppressWarnings(%22unused%22)%0A      Future%3C?%3E possiblyIgnoredError = reindexIfStaleAsync(uuid);%0A    %7D%0A  %7D%0A%0A  /**%0A   * Asynchronously check if a group is stale, and reindex if it is.%0A   *%0A   * %3Cp%3EAlways run on the batch executor, even if this indexer instance is configured to use a%0A   * different executor.%0A   *%0A   * @param uuid the unique identifier of the group.%0A   * @return future for reindexing the group; returns true if the group was stale.%0A   */%0A  @SuppressWarnings(%22deprecation%22)%0A  private com.google.common.util.concurrent.CheckedFuture%3CBoolean, IOException%3E reindexIfStaleAsync(%0A      AccountGroup.UUID uuid) %7B%0A    return Futures.makeChecked(%0A        Futures.nonCancellationPropagating(batchExecutor.submit(() -%3E reindexIfStale(uuid))),%0A        IndexUtils.MAPPER);%0A  %7D%0A%0A
   pr

Remove reindex if stale after original index update