Line data Source code
1 : // Copyright (C) 2014 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.index.change; 16 : 17 : import static com.google.common.util.concurrent.MoreExecutors.directExecutor; 18 : import static com.google.gerrit.server.query.change.ChangeData.asChanges; 19 : 20 : import com.google.common.flogger.FluentLogger; 21 : import com.google.common.util.concurrent.FutureCallback; 22 : import com.google.common.util.concurrent.Futures; 23 : import com.google.common.util.concurrent.ListeningExecutorService; 24 : import com.google.gerrit.entities.Account; 25 : import com.google.gerrit.entities.BranchNameKey; 26 : import com.google.gerrit.entities.Change; 27 : import com.google.gerrit.entities.Project; 28 : import com.google.gerrit.entities.RefNames; 29 : import com.google.gerrit.extensions.events.GitBatchRefUpdateListener; 30 : import com.google.gerrit.server.change.MergeabilityComputationBehavior; 31 : import com.google.gerrit.server.config.AllUsersName; 32 : import com.google.gerrit.server.config.GerritServerConfig; 33 : import com.google.gerrit.server.git.QueueProvider.QueueType; 34 : import com.google.gerrit.server.index.IndexExecutor; 35 : import com.google.gerrit.server.index.account.AccountIndexer; 36 : import com.google.gerrit.server.query.change.InternalChangeQuery; 37 : import com.google.gerrit.server.util.ManualRequestContext; 38 : import com.google.gerrit.server.util.OneOffRequestContext; 39 : import com.google.gerrit.server.util.RequestContext; 40 : import com.google.inject.Inject; 41 : import com.google.inject.Provider; 42 : import java.util.List; 43 : import java.util.concurrent.Callable; 44 : import java.util.concurrent.Future; 45 : import org.eclipse.jgit.lib.Config; 46 : 47 : /** 48 : * Listener for ref update events that reindexes entities in case the updated Git reference was used 49 : * to compute contents of an index document. 50 : * 51 : * <p>Reindexes any open changes that has a destination branch that was updated to ensure that 52 : * 'mergeable' is still current. 53 : * 54 : * <p>Will reindex accounts when the account's NoteDb ref changes. 55 : */ 56 : public class ReindexAfterRefUpdate implements GitBatchRefUpdateListener { 57 151 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 58 : 59 : private final OneOffRequestContext requestContext; 60 : private final Provider<InternalChangeQuery> queryProvider; 61 : private final ChangeIndexer.Factory indexerFactory; 62 : private final ChangeIndexCollection indexes; 63 : private final AllUsersName allUsersName; 64 : private final Provider<AccountIndexer> indexer; 65 : private final ListeningExecutorService executor; 66 : private final boolean enabled; 67 : 68 : @Inject 69 : ReindexAfterRefUpdate( 70 : @GerritServerConfig Config cfg, 71 : OneOffRequestContext requestContext, 72 : Provider<InternalChangeQuery> queryProvider, 73 : ChangeIndexer.Factory indexerFactory, 74 : ChangeIndexCollection indexes, 75 : AllUsersName allUsersName, 76 : Provider<AccountIndexer> indexer, 77 151 : @IndexExecutor(QueueType.BATCH) ListeningExecutorService executor) { 78 151 : this.requestContext = requestContext; 79 151 : this.queryProvider = queryProvider; 80 151 : this.indexerFactory = indexerFactory; 81 151 : this.indexes = indexes; 82 151 : this.allUsersName = allUsersName; 83 151 : this.indexer = indexer; 84 151 : this.executor = executor; 85 151 : this.enabled = MergeabilityComputationBehavior.fromConfig(cfg).includeInIndex(); 86 151 : } 87 : 88 : @Override 89 : public void onGitBatchRefUpdate(GitBatchRefUpdateListener.Event event) { 90 151 : if (allUsersName.get().equals(event.getProjectName())) { 91 151 : for (UpdatedRef ref : event.getUpdatedRefs()) { 92 151 : if (!RefNames.REFS_CONFIG.equals(ref.getRefName())) { 93 151 : if (ref.getRefName().startsWith(RefNames.REFS_STARRED_CHANGES)) { 94 10 : break; 95 : } 96 151 : Account.Id accountId = Account.Id.fromRef(ref.getRefName()); 97 151 : if (accountId != null) { 98 151 : indexer.get().index(accountId); 99 : } 100 : } 101 151 : } 102 : // The update is in All-Users and not on refs/meta/config. So it's not a change. Return early. 103 151 : return; 104 : } 105 : 106 145 : for (UpdatedRef ref : event.getUpdatedRefs()) { 107 145 : if (!enabled 108 14 : || ref.getRefName().startsWith(RefNames.REFS_CHANGES) 109 14 : || ref.getRefName().startsWith(RefNames.REFS_DRAFT_COMMENTS) 110 14 : || ref.getRefName().startsWith(RefNames.REFS_USERS)) { 111 0 : continue; 112 : } 113 14 : Futures.addCallback( 114 14 : executor.submit(new GetChanges(event.getProjectName(), ref)), 115 14 : new FutureCallback<List<Change>>() { 116 : @Override 117 : public void onSuccess(List<Change> changes) { 118 14 : for (Change c : changes) { 119 : @SuppressWarnings("unused") 120 11 : Future<?> possiblyIgnoredError = 121 11 : indexerFactory.create(executor, indexes).indexAsync(c.getProject(), c.getId()); 122 11 : } 123 14 : } 124 : 125 : @Override 126 : public void onFailure(Throwable ignored) { 127 : // Logged by {@link GetChanges#call()}. 128 0 : } 129 : }, 130 14 : directExecutor()); 131 14 : } 132 145 : } 133 : 134 : private abstract class Task<V> implements Callable<V> { 135 : protected UpdatedRef updatedRef; 136 : 137 14 : protected Task(UpdatedRef updatedRef) { 138 14 : this.updatedRef = updatedRef; 139 14 : } 140 : 141 : @Override 142 : public final V call() throws Exception { 143 14 : try (ManualRequestContext ctx = requestContext.open()) { 144 14 : return impl(ctx); 145 0 : } catch (Exception e) { 146 0 : logger.atSevere().withCause(e).log("Failed to reindex changes after %s", updatedRef); 147 0 : throw e; 148 : } 149 : } 150 : 151 : protected abstract V impl(RequestContext ctx) throws Exception; 152 : 153 : protected abstract void remove(); 154 : } 155 : 156 : private class GetChanges extends Task<List<Change>> { 157 : protected String projectName; 158 : 159 14 : private GetChanges(String projectName, UpdatedRef updatedRef) { 160 14 : super(updatedRef); 161 14 : this.projectName = projectName; 162 14 : } 163 : 164 : @Override 165 : protected List<Change> impl(RequestContext ctx) { 166 14 : String ref = updatedRef.getRefName(); 167 14 : Project.NameKey project = Project.nameKey(projectName); 168 14 : if (ref.equals(RefNames.REFS_CONFIG)) { 169 14 : return asChanges(queryProvider.get().byProjectOpen(project)); 170 : } 171 14 : return asChanges(queryProvider.get().byBranchNew(BranchNameKey.create(project, ref))); 172 : } 173 : 174 : @Override 175 : public String toString() { 176 0 : return "Get changes to reindex caused by " 177 0 : + updatedRef.getRefName() 178 : + " update of project " 179 : + projectName; 180 : } 181 : 182 : @Override 183 0 : protected void remove() {} 184 : } 185 : }