Refactor PostReviewers to be more readable
This change factors the BatchUpdateOp out of PostReviewers into
PostReviewersOp and refactors the rest of the code in PostReviewers to
become more readable and easier to understand.
Change-Id: I8a4e5d65da61750ad72ecc61e9e382d5984c3044
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
index dcde8d1..6dd4570 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
@@ -168,5 +168,6 @@
factory(ChangeResource.Factory.class);
factory(DeleteReviewerOp.Factory.class);
factory(DeleteReviewerByEmailOp.Factory.class);
+ factory(PostReviewersOp.Factory.class);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
index 5d0b789..81ab39e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
@@ -308,7 +308,7 @@
bu.addOp(
revision.getChange().getId(),
- new Op(revision.getPatchSet().getId(), input, accountsToNotify, reviewerResults));
+ new Op(revision.getPatchSet().getId(), input, accountsToNotify));
bu.execute();
for (PostReviewers.Addition reviewerResult : reviewerResults) {
@@ -324,22 +324,27 @@
private void emailReviewers(
Change change,
List<PostReviewers.Addition> reviewerAdditions,
- NotifyHandling notify,
+ @Nullable NotifyHandling notify,
ListMultimap<RecipientType, Account.Id> accountsToNotify) {
List<Account.Id> to = new ArrayList<>();
List<Account.Id> cc = new ArrayList<>();
List<Address> toByEmail = new ArrayList<>();
List<Address> ccByEmail = new ArrayList<>();
for (PostReviewers.Addition addition : reviewerAdditions) {
- if (addition.op.state == ReviewerState.REVIEWER) {
- to.addAll(addition.op.reviewers.keySet());
- toByEmail.addAll(addition.op.reviewersByEmail);
- } else if (addition.op.state == ReviewerState.CC) {
- cc.addAll(addition.op.reviewers.keySet());
- ccByEmail.addAll(addition.op.reviewersByEmail);
+ if (addition.state == ReviewerState.REVIEWER) {
+ to.addAll(addition.reviewers.keySet());
+ toByEmail.addAll(addition.reviewersByEmail);
+ } else if (addition.state == ReviewerState.CC) {
+ cc.addAll(addition.reviewers.keySet());
+ ccByEmail.addAll(addition.reviewersByEmail);
}
}
- postReviewers.emailReviewers(change, to, cc, toByEmail, ccByEmail, notify, accountsToNotify);
+ if (reviewerAdditions.size() > 0) {
+ reviewerAdditions
+ .get(0)
+ .op
+ .emailReviewers(change, to, cc, toByEmail, ccByEmail, notify, accountsToNotify);
+ }
}
private RevisionResource onBehalfOf(RevisionResource rev, ReviewInput in)
@@ -744,7 +749,6 @@
private final PatchSet.Id psId;
private final ReviewInput in;
private final ListMultimap<RecipientType, Account.Id> accountsToNotify;
- private final List<PostReviewers.Addition> reviewerResults;
private IdentifiedUser user;
private ChangeNotes notes;
@@ -758,12 +762,10 @@
private Op(
PatchSet.Id psId,
ReviewInput in,
- ListMultimap<RecipientType, Account.Id> accountsToNotify,
- List<PostReviewers.Addition> reviewerResults) {
+ ListMultimap<RecipientType, Account.Id> accountsToNotify) {
this.psId = psId;
this.in = in;
this.accountsToNotify = checkNotNull(accountsToNotify);
- this.reviewerResults = reviewerResults;
}
@Override
@@ -1067,16 +1069,6 @@
if (ctx.getAccountId().equals(ctx.getChange().getOwner())) {
return true;
}
- for (PostReviewers.Addition addition : reviewerResults) {
- if (addition.op.addedReviewers == null) {
- continue;
- }
- for (PatchSetApproval psa : addition.op.addedReviewers) {
- if (psa.getAccountId().equals(ctx.getAccountId())) {
- return true;
- }
- }
- }
ChangeData cd = changeDataFactory.create(db.get(), ctx.getControl());
ReviewerSet reviewers = cd.reviewers();
if (reviewers.byState(REVIEWER).contains(ctx.getAccountId())) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
index f3d8ab1..b7fe986 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
@@ -14,7 +14,6 @@
package com.google.gerrit.server.change;
-import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.gerrit.extensions.client.ReviewerState.CC;
import static com.google.gerrit.extensions.client.ReviewerState.REVIEWER;
@@ -41,34 +40,24 @@
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.AnonymousUser;
-import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.PatchSetUtil;
-import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.account.AccountsCollection;
import com.google.gerrit.server.account.GroupMembers;
import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.extensions.events.ReviewerAdded;
import com.google.gerrit.server.group.GroupsCollection;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.mail.Address;
-import com.google.gerrit.server.mail.send.AddReviewerSender;
import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
import com.google.gerrit.server.notedb.NotesMigration;
-import com.google.gerrit.server.notedb.ReviewerStateInternal;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.update.BatchUpdate;
-import com.google.gerrit.server.update.BatchUpdateOp;
-import com.google.gerrit.server.update.ChangeContext;
-import com.google.gerrit.server.update.Context;
import com.google.gerrit.server.update.UpdateException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -76,86 +65,67 @@
import com.google.inject.Singleton;
import java.io.IOException;
import java.text.MessageFormat;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.jgit.lib.Config;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
@Singleton
public class PostReviewers implements RestModifyView<ChangeResource, AddReviewerInput> {
- private static final Logger log = LoggerFactory.getLogger(PostReviewers.class);
public static final int DEFAULT_MAX_REVIEWERS_WITHOUT_CHECK = 10;
public static final int DEFAULT_MAX_REVIEWERS = 20;
private final AccountsCollection accounts;
private final ReviewerResource.Factory reviewerFactory;
- private final ApprovalsUtil approvalsUtil;
- private final PatchSetUtil psUtil;
- private final AddReviewerSender.Factory addReviewerSenderFactory;
+
private final GroupsCollection groupsCollection;
private final GroupMembers.Factory groupMembersFactory;
private final AccountLoader.Factory accountLoaderFactory;
private final Provider<ReviewDb> dbProvider;
private final BatchUpdate.Factory batchUpdateFactory;
- private final Provider<IdentifiedUser> user;
private final IdentifiedUser.GenericFactory identifiedUserFactory;
private final Config cfg;
private final ReviewerJson json;
- private final ReviewerAdded reviewerAdded;
private final NotesMigration migration;
- private final AccountCache accountCache;
private final NotifyUtil notifyUtil;
private final ProjectCache projectCache;
private final Provider<AnonymousUser> anonymousProvider;
+ private final PostReviewersOp.Factory postReviewersOpFactory;
@Inject
PostReviewers(
AccountsCollection accounts,
ReviewerResource.Factory reviewerFactory,
- ApprovalsUtil approvalsUtil,
- PatchSetUtil psUtil,
- AddReviewerSender.Factory addReviewerSenderFactory,
GroupsCollection groupsCollection,
GroupMembers.Factory groupMembersFactory,
AccountLoader.Factory accountLoaderFactory,
Provider<ReviewDb> db,
BatchUpdate.Factory batchUpdateFactory,
- Provider<IdentifiedUser> user,
IdentifiedUser.GenericFactory identifiedUserFactory,
@GerritServerConfig Config cfg,
ReviewerJson json,
- ReviewerAdded reviewerAdded,
NotesMigration migration,
- AccountCache accountCache,
NotifyUtil notifyUtil,
ProjectCache projectCache,
- Provider<AnonymousUser> anonymousProvider) {
+ Provider<AnonymousUser> anonymousProvider,
+ PostReviewersOp.Factory postReviewersOpFactory) {
this.accounts = accounts;
this.reviewerFactory = reviewerFactory;
- this.approvalsUtil = approvalsUtil;
- this.psUtil = psUtil;
- this.addReviewerSenderFactory = addReviewerSenderFactory;
this.groupsCollection = groupsCollection;
this.groupMembersFactory = groupMembersFactory;
this.accountLoaderFactory = accountLoaderFactory;
this.dbProvider = db;
this.batchUpdateFactory = batchUpdateFactory;
- this.user = user;
this.identifiedUserFactory = identifiedUserFactory;
this.cfg = cfg;
this.json = json;
- this.reviewerAdded = reviewerAdded;
this.migration = migration;
- this.accountCache = accountCache;
this.notifyUtil = notifyUtil;
this.projectCache = projectCache;
this.anonymousProvider = anonymousProvider;
+ this.postReviewersOpFactory = postReviewersOpFactory;
}
@Override
@@ -180,44 +150,22 @@
return addition.result;
}
- // TODO(hiesel) Refactor this as it starts to become unreadable
public Addition prepareApplication(
ChangeResource rsrc, AddReviewerInput input, boolean allowGroup)
throws OrmException, RestApiException, IOException {
- Account.Id accountId;
- try {
- accountId = accounts.parse(input.reviewer).getAccountId();
- } catch (UnprocessableEntityException e) {
- boolean enableReviewerByEmail =
- projectCache.checkedGet(rsrc.getProject()).isEnableReviewerByEmail();
- if (allowGroup) {
- try {
- return putGroup(rsrc, input);
- } catch (UnprocessableEntityException e2) {
- if (!enableReviewerByEmail) {
- throw new UnprocessableEntityException(
- MessageFormat.format(
- ChangeMessages.get().reviewerNotFoundUserOrGroup, input.reviewer));
- }
- }
- }
- if (!enableReviewerByEmail) {
- throw new UnprocessableEntityException(
- MessageFormat.format(ChangeMessages.get().reviewerNotFoundUser, input.reviewer));
- }
- return putAccountByEmail(
- input.reviewer,
- rsrc,
- input.state(),
- input.notify,
- notifyUtil.resolveAccounts(input.notifyDetails));
+ boolean allowByEmail = projectCache.checkedGet(rsrc.getProject()).isEnableReviewerByEmail();
+
+ Addition byAccountId = addByAccountId(rsrc, input, allowGroup, allowByEmail);
+ if (byAccountId != null) {
+ return byAccountId;
}
- return putAccount(
- input.reviewer,
- reviewerFactory.create(rsrc, accountId),
- input.state(),
- input.notify,
- notifyUtil.resolveAccounts(input.notifyDetails));
+
+ Addition wholeGroup = addWholeGroup(rsrc, input, allowGroup, allowByEmail);
+ if (wholeGroup != null) {
+ return wholeGroup;
+ }
+
+ return addByEmail(rsrc, input);
}
Addition ccCurrentUser(CurrentUser user, RevisionResource revision) {
@@ -231,6 +179,60 @@
ImmutableListMultimap.of());
}
+ @Nullable
+ private Addition addByAccountId(
+ ChangeResource rsrc, AddReviewerInput input, boolean allowGroup, boolean allowByEmail)
+ throws OrmException, RestApiException {
+ Account.Id accountId = null;
+ try {
+ accountId = accounts.parse(input.reviewer).getAccountId();
+ } catch (UnprocessableEntityException e) {
+ if (!allowGroup && !allowByEmail) {
+ throw new UnprocessableEntityException(
+ MessageFormat.format(ChangeMessages.get().reviewerNotFoundUser, input.reviewer));
+ }
+ }
+ if (accountId != null) {
+ return putAccount(
+ input.reviewer,
+ reviewerFactory.create(rsrc, accountId),
+ input.state(),
+ input.notify,
+ notifyUtil.resolveAccounts(input.notifyDetails));
+ }
+ return null;
+ }
+
+ @Nullable
+ private Addition addWholeGroup(
+ ChangeResource rsrc, AddReviewerInput input, boolean allowGroup, boolean allowByEmail)
+ throws OrmException, RestApiException, IOException {
+ if (!allowGroup) {
+ return null;
+ }
+
+ try {
+ return putGroup(rsrc, input);
+ } catch (UnprocessableEntityException e) {
+ if (!allowByEmail) {
+ throw new UnprocessableEntityException(
+ MessageFormat.format(ChangeMessages.get().reviewerNotFoundUserOrGroup, input.reviewer));
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ private Addition addByEmail(ChangeResource rsrc, AddReviewerInput input)
+ throws OrmException, RestApiException {
+ return putAccountByEmail(
+ input.reviewer,
+ rsrc,
+ input.state(),
+ input.notify,
+ notifyUtil.resolveAccounts(input.notifyDetails));
+ }
+
private Addition putAccount(
String reviewer,
ReviewerResource rsrc,
@@ -366,10 +368,10 @@
public class Addition {
final AddReviewerResult result;
- final Op op;
-
- private final Map<Account.Id, ChangeControl> reviewers;
- private final Collection<Address> reviewersByEmail;
+ final PostReviewersOp op;
+ final Map<Account.Id, ChangeControl> reviewers;
+ final Collection<Address> reviewersByEmail;
+ final ReviewerState state;
protected Addition(String reviewer) {
this(reviewer, null, null, null, REVIEWER, null, ImmutableListMultimap.of());
@@ -381,24 +383,28 @@
@Nullable Map<Account.Id, ChangeControl> reviewers,
@Nullable Collection<Address> reviewersByEmail,
ReviewerState state,
- NotifyHandling notify,
+ @Nullable NotifyHandling notify,
ListMultimap<RecipientType, Account.Id> accountsToNotify) {
result = new AddReviewerResult(reviewer);
this.reviewers = reviewers == null ? ImmutableMap.of() : reviewers;
this.reviewersByEmail = reviewersByEmail == null ? ImmutableList.of() : reviewersByEmail;
+ this.state = state;
if (reviewers == null && reviewersByEmail == null) {
op = null;
return;
}
- op = new Op(rsrc, this.reviewers, this.reviewersByEmail, state, notify, accountsToNotify);
+ op =
+ postReviewersOpFactory.create(
+ rsrc, this.reviewers, this.reviewersByEmail, state, notify, accountsToNotify);
}
void gatherResults() throws OrmException {
// Generate result details and fill AccountLoader. This occurs outside
// the Op because the accounts are in a different table.
- if (migration.readChanges() && op.state == CC) {
- result.ccs = Lists.newArrayListWithCapacity(op.addedCCs.size());
- for (Account.Id accountId : op.addedCCs) {
+ PostReviewersOp.Result opResult = op.getResult();
+ if (migration.readChanges() && state == CC) {
+ result.ccs = Lists.newArrayListWithCapacity(opResult.addedCCs().size());
+ for (Account.Id accountId : opResult.addedCCs()) {
result.ccs.add(json.format(new ReviewerInfo(accountId.get()), reviewers.get(accountId)));
}
accountLoaderFactory.create(true).fill(result.ccs);
@@ -406,8 +412,8 @@
result.ccs.add(new AccountInfo(a.getName(), a.getEmail()));
}
} else {
- result.reviewers = Lists.newArrayListWithCapacity(op.addedReviewers.size());
- for (PatchSetApproval psa : op.addedReviewers) {
+ result.reviewers = Lists.newArrayListWithCapacity(opResult.addedReviewers().size());
+ for (PatchSetApproval psa : opResult.addedReviewers()) {
// New reviewers have value 0, don't bother normalizing.
result.reviewers.add(
json.format(
@@ -423,139 +429,6 @@
}
}
- public class Op implements BatchUpdateOp {
- final Map<Account.Id, ChangeControl> reviewers;
- final Collection<Address> reviewersByEmail;
- final ReviewerState state;
- final NotifyHandling notify;
- final ListMultimap<RecipientType, Account.Id> accountsToNotify;
- List<PatchSetApproval> addedReviewers = new ArrayList<>();
- Collection<Account.Id> addedCCs = new ArrayList<>();
- Collection<Address> addedCCsByEmail = new ArrayList<>();
-
- private final ChangeResource rsrc;
- private PatchSet patchSet;
-
- Op(
- ChangeResource rsrc,
- Map<Account.Id, ChangeControl> reviewers,
- Collection<Address> reviewersByEmail,
- ReviewerState state,
- NotifyHandling notify,
- ListMultimap<RecipientType, Account.Id> accountsToNotify) {
- this.rsrc = rsrc;
- this.reviewers = reviewers;
- this.reviewersByEmail = reviewersByEmail;
- this.state = state;
- this.notify = notify;
- this.accountsToNotify = checkNotNull(accountsToNotify);
- }
-
- @Override
- public boolean updateChange(ChangeContext ctx)
- throws RestApiException, OrmException, IOException {
- if (!reviewers.isEmpty()) {
- if (migration.readChanges() && state == CC) {
- addedCCs =
- approvalsUtil.addCcs(
- ctx.getNotes(),
- ctx.getUpdate(ctx.getChange().currentPatchSetId()),
- reviewers.keySet());
- if (addedCCs.isEmpty()) {
- return false;
- }
- } else {
- addedReviewers =
- approvalsUtil.addReviewers(
- ctx.getDb(),
- ctx.getNotes(),
- ctx.getUpdate(ctx.getChange().currentPatchSetId()),
- rsrc.getControl().getLabelTypes(),
- rsrc.getChange(),
- reviewers.keySet());
- if (addedReviewers.isEmpty()) {
- return false;
- }
- }
- }
-
- for (Address a : reviewersByEmail) {
- ctx.getUpdate(ctx.getChange().currentPatchSetId())
- .putReviewerByEmail(a, ReviewerStateInternal.fromReviewerState(state));
- }
-
- patchSet = psUtil.current(dbProvider.get(), rsrc.getNotes());
- return true;
- }
-
- @Override
- public void postUpdate(Context ctx) throws Exception {
- emailReviewers(
- rsrc.getChange(),
- Lists.transform(addedReviewers, r -> r.getAccountId()),
- addedCCs == null ? ImmutableList.of() : addedCCs,
- reviewersByEmail,
- addedCCsByEmail,
- notify,
- accountsToNotify);
- if (!addedReviewers.isEmpty()) {
- List<Account> reviewers =
- Lists.transform(
- addedReviewers, psa -> accountCache.get(psa.getAccountId()).getAccount());
- reviewerAdded.fire(rsrc.getChange(), patchSet, reviewers, ctx.getAccount(), ctx.getWhen());
- }
- }
- }
-
- public void emailReviewers(
- Change change,
- Collection<Account.Id> added,
- Collection<Account.Id> copied,
- Collection<Address> addedByEmail,
- Collection<Address> copiedByEmail,
- NotifyHandling notify,
- ListMultimap<RecipientType, Account.Id> accountsToNotify) {
- if (added.isEmpty() && copied.isEmpty() && addedByEmail.isEmpty() && copiedByEmail.isEmpty()) {
- return;
- }
-
- // Email the reviewers
- //
- // The user knows they added themselves, don't bother emailing them.
- List<Account.Id> toMail = Lists.newArrayListWithCapacity(added.size());
- Account.Id userId = user.get().getAccountId();
- for (Account.Id id : added) {
- if (!id.equals(userId)) {
- toMail.add(id);
- }
- }
- List<Account.Id> toCopy = Lists.newArrayListWithCapacity(copied.size());
- for (Account.Id id : copied) {
- if (!id.equals(userId)) {
- toCopy.add(id);
- }
- }
- if (toMail.isEmpty() && toCopy.isEmpty() && addedByEmail.isEmpty() && copiedByEmail.isEmpty()) {
- return;
- }
-
- try {
- AddReviewerSender cm = addReviewerSenderFactory.create(change.getProject(), change.getId());
- if (notify != null) {
- cm.setNotify(notify);
- }
- cm.setAccountsToNotify(accountsToNotify);
- cm.setFrom(userId);
- cm.addReviewers(toMail);
- cm.addReviewersByEmail(addedByEmail);
- cm.addExtraCC(toCopy);
- cm.addExtraCCByEmail(copiedByEmail);
- cm.send();
- } catch (Exception err) {
- log.error("Cannot send email to new reviewers of change " + change.getId(), err);
- }
- }
-
public static boolean isLegalReviewerGroup(AccountGroup.UUID groupUUID) {
return !SystemGroupBackend.isSystemGroup(groupUUID);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewersOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewersOp.java
new file mode 100644
index 0000000..948800d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewersOp.java
@@ -0,0 +1,262 @@
+// 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.change;
+
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.gerrit.extensions.client.ReviewerState.CC;
+import static java.util.stream.Collectors.toList;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.api.changes.NotifyHandling;
+import com.google.gerrit.extensions.api.changes.RecipientType;
+import com.google.gerrit.extensions.client.ReviewerState;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Account.Id;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalsUtil;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.PatchSetUtil;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.extensions.events.ReviewerAdded;
+import com.google.gerrit.server.mail.Address;
+import com.google.gerrit.server.mail.send.AddReviewerSender;
+import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.gerrit.server.notedb.ReviewerStateInternal;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.update.BatchUpdateOp;
+import com.google.gerrit.server.update.ChangeContext;
+import com.google.gerrit.server.update.Context;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.assistedinject.Assisted;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class PostReviewersOp implements BatchUpdateOp {
+ private static final Logger log = LoggerFactory.getLogger(PostReviewersOp.class);
+
+ public interface Factory {
+ PostReviewersOp create(
+ ChangeResource rsrc,
+ Map<Account.Id, ChangeControl> reviewers,
+ Collection<Address> reviewersByEmail,
+ ReviewerState state,
+ @Nullable NotifyHandling notify,
+ ListMultimap<RecipientType, Account.Id> accountsToNotify);
+ }
+
+ @AutoValue
+ public abstract static class Result {
+ public abstract ImmutableList<PatchSetApproval> addedReviewers();
+
+ public abstract ImmutableList<Account.Id> addedCCs();
+
+ static Builder builder() {
+ return new AutoValue_PostReviewersOp_Result.Builder();
+ }
+
+ @AutoValue.Builder
+ abstract static class Builder {
+ abstract Builder setAddedReviewers(ImmutableList<PatchSetApproval> addedReviewers);
+
+ abstract Builder setAddedCCs(ImmutableList<Account.Id> addedCCs);
+
+ abstract Result build();
+ }
+ }
+
+ private final ApprovalsUtil approvalsUtil;
+ private final PatchSetUtil psUtil;
+ private final ReviewerAdded reviewerAdded;
+ private final AccountCache accountCache;
+ private final AddReviewerSender.Factory addReviewerSenderFactory;
+ private final NotesMigration migration;
+ private final Provider<IdentifiedUser> user;
+ private final Provider<ReviewDb> dbProvider;
+ private final ChangeResource rsrc;
+ private final Map<Id, ChangeControl> reviewers;
+ private final Collection<Address> reviewersByEmail;
+ private final ReviewerState state;
+ private final NotifyHandling notify;
+ private final ListMultimap<RecipientType, Id> accountsToNotify;
+
+ private List<PatchSetApproval> addedReviewers = new ArrayList<>();
+ private Collection<Account.Id> addedCCs = new ArrayList<>();
+ private Collection<Address> addedCCsByEmail = new ArrayList<>();
+ private PatchSet patchSet;
+ private Result opResult;
+
+ @Inject
+ PostReviewersOp(
+ ApprovalsUtil approvalsUtil,
+ PatchSetUtil psUtil,
+ ReviewerAdded reviewerAdded,
+ AccountCache accountCache,
+ AddReviewerSender.Factory addReviewerSenderFactory,
+ NotesMigration migration,
+ Provider<IdentifiedUser> user,
+ Provider<ReviewDb> dbProvider,
+ @Assisted ChangeResource rsrc,
+ @Assisted Map<Account.Id, ChangeControl> reviewers,
+ @Assisted Collection<Address> reviewersByEmail,
+ @Assisted ReviewerState state,
+ @Assisted @Nullable NotifyHandling notify,
+ @Assisted ListMultimap<RecipientType, Account.Id> accountsToNotify) {
+ this.approvalsUtil = approvalsUtil;
+ this.psUtil = psUtil;
+ this.reviewerAdded = reviewerAdded;
+ this.accountCache = accountCache;
+ this.addReviewerSenderFactory = addReviewerSenderFactory;
+ this.migration = migration;
+ this.user = user;
+ this.dbProvider = dbProvider;
+
+ this.rsrc = rsrc;
+ this.reviewers = reviewers;
+ this.reviewersByEmail = reviewersByEmail;
+ this.state = state;
+ this.notify = notify;
+ this.accountsToNotify = accountsToNotify;
+ }
+
+ @Override
+ public boolean updateChange(ChangeContext ctx)
+ throws RestApiException, OrmException, IOException {
+ if (!reviewers.isEmpty()) {
+ if (migration.readChanges() && state == CC) {
+ addedCCs =
+ approvalsUtil.addCcs(
+ ctx.getNotes(),
+ ctx.getUpdate(ctx.getChange().currentPatchSetId()),
+ reviewers.keySet());
+ if (addedCCs.isEmpty()) {
+ return false;
+ }
+ } else {
+ addedReviewers =
+ approvalsUtil.addReviewers(
+ ctx.getDb(),
+ ctx.getNotes(),
+ ctx.getUpdate(ctx.getChange().currentPatchSetId()),
+ rsrc.getControl().getLabelTypes(),
+ rsrc.getChange(),
+ reviewers.keySet());
+ if (addedReviewers.isEmpty()) {
+ return false;
+ }
+ }
+ }
+
+ for (Address a : reviewersByEmail) {
+ ctx.getUpdate(ctx.getChange().currentPatchSetId())
+ .putReviewerByEmail(a, ReviewerStateInternal.fromReviewerState(state));
+ }
+
+ patchSet = psUtil.current(dbProvider.get(), rsrc.getNotes());
+ return true;
+ }
+
+ @Override
+ public void postUpdate(Context ctx) throws Exception {
+ opResult =
+ Result.builder()
+ .setAddedReviewers(ImmutableList.copyOf(addedReviewers))
+ .setAddedCCs(ImmutableList.copyOf(addedCCs))
+ .build();
+ emailReviewers(
+ rsrc.getChange(),
+ Lists.transform(addedReviewers, r -> r.getAccountId()),
+ addedCCs == null ? ImmutableList.of() : addedCCs,
+ reviewersByEmail,
+ addedCCsByEmail,
+ notify,
+ accountsToNotify);
+ if (!addedReviewers.isEmpty()) {
+ List<Account> reviewers =
+ addedReviewers
+ .stream()
+ .map(r -> accountCache.get(r.getAccountId()).getAccount())
+ .collect(toList());
+ reviewerAdded.fire(rsrc.getChange(), patchSet, reviewers, ctx.getAccount(), ctx.getWhen());
+ }
+ }
+
+ public void emailReviewers(
+ Change change,
+ Collection<Account.Id> added,
+ Collection<Account.Id> copied,
+ Collection<Address> addedByEmail,
+ Collection<Address> copiedByEmail,
+ NotifyHandling notify,
+ ListMultimap<RecipientType, Account.Id> accountsToNotify) {
+ if (added.isEmpty() && copied.isEmpty() && addedByEmail.isEmpty() && copiedByEmail.isEmpty()) {
+ return;
+ }
+
+ // Email the reviewers
+ //
+ // The user knows they added themselves, don't bother emailing them.
+ List<Account.Id> toMail = Lists.newArrayListWithCapacity(added.size());
+ Account.Id userId = user.get().getAccountId();
+ for (Account.Id id : added) {
+ if (!id.equals(userId)) {
+ toMail.add(id);
+ }
+ }
+ List<Account.Id> toCopy = Lists.newArrayListWithCapacity(copied.size());
+ for (Account.Id id : copied) {
+ if (!id.equals(userId)) {
+ toCopy.add(id);
+ }
+ }
+ if (toMail.isEmpty() && toCopy.isEmpty() && addedByEmail.isEmpty() && copiedByEmail.isEmpty()) {
+ return;
+ }
+
+ try {
+ AddReviewerSender cm = addReviewerSenderFactory.create(change.getProject(), change.getId());
+ cm.setNotify(MoreObjects.firstNonNull(notify, NotifyHandling.ALL));
+ cm.setAccountsToNotify(accountsToNotify);
+ cm.setFrom(userId);
+ cm.addReviewers(toMail);
+ cm.addReviewersByEmail(addedByEmail);
+ cm.addExtraCC(toCopy);
+ cm.addExtraCCByEmail(copiedByEmail);
+ cm.send();
+ } catch (Exception err) {
+ log.error("Cannot send email to new reviewers of change " + change.getId(), err);
+ }
+ }
+
+ public Result getResult() {
+ checkState(opResult != null, "Batch update wasn't executed yet");
+ return opResult;
+ }
+}