Merge "Always request and show `submittable` info"
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index d9ab64d..5a2fc2e 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -384,6 +384,13 @@
* `CUSTOM_KEYED_VALUES`: include the custom key-value map
---
+[[star]]
+--
+* `STAR`: include the `starred` field in
+ link:#change-info[ChangeInfo], which indicates if the change is starred
+ by the current user or not.
+--
+
.Request
----
GET /changes/?q=97&o=CURRENT_REVISION&o=CURRENT_COMMIT&o=CURRENT_FILES&o=DOWNLOAD_COMMANDS HTTP/1.0
@@ -6972,6 +6979,7 @@
link:rest-api-accounts.html#account-info[ AccountInfo] entity.
|`starred` |not set if `false`|
Whether the calling user has starred this change with the default label.
+Only set if link:#star[requested].
|`stars` |optional|
A list of star labels that are applied by the calling user to this
change. The labels are lexicographically sorted.
diff --git a/java/com/google/gerrit/extensions/client/ListChangesOption.java b/java/com/google/gerrit/extensions/client/ListChangesOption.java
index de4326e..e2a7c1e 100644
--- a/java/com/google/gerrit/extensions/client/ListChangesOption.java
+++ b/java/com/google/gerrit/extensions/client/ListChangesOption.java
@@ -91,7 +91,10 @@
SUBMIT_REQUIREMENTS(24),
/** Include custom keyed values. */
- CUSTOM_KEYED_VALUES(25);
+ CUSTOM_KEYED_VALUES(25),
+
+ /** Include the 'starred' field, that is if the change is starred by the current user . */
+ STAR(26);
private final int value;
diff --git a/java/com/google/gerrit/gpg/server/DeleteGpgKey.java b/java/com/google/gerrit/gpg/server/DeleteGpgKey.java
index bcc8631..f709dd6 100644
--- a/java/com/google/gerrit/gpg/server/DeleteGpgKey.java
+++ b/java/com/google/gerrit/gpg/server/DeleteGpgKey.java
@@ -34,7 +34,7 @@
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
import com.google.gerrit.server.account.externalids.ExternalIds;
-import com.google.gerrit.server.mail.send.DeleteKeySender;
+import com.google.gerrit.server.mail.EmailModule.DeleteKeyEmailFactories;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.io.IOException;
@@ -53,7 +53,7 @@
private final Provider<PublicKeyStore> storeProvider;
private final Provider<AccountsUpdate> accountsUpdateProvider;
private final ExternalIds externalIds;
- private final DeleteKeySender.Factory deleteKeySenderFactory;
+ private final DeleteKeyEmailFactories deleteKeyEmailFactories;
private final ExternalIdKeyFactory externalIdKeyFactory;
@Inject
@@ -62,13 +62,13 @@
Provider<PublicKeyStore> storeProvider,
@UserInitiated Provider<AccountsUpdate> accountsUpdateProvider,
ExternalIds externalIds,
- DeleteKeySender.Factory deleteKeySenderFactory,
+ DeleteKeyEmailFactories deleteKeyEmailFactories,
ExternalIdKeyFactory externalIdKeyFactory) {
this.serverIdent = serverIdent;
this.storeProvider = storeProvider;
this.accountsUpdateProvider = accountsUpdateProvider;
this.externalIds = externalIds;
- this.deleteKeySenderFactory = deleteKeySenderFactory;
+ this.deleteKeyEmailFactories = deleteKeyEmailFactories;
this.externalIdKeyFactory = externalIdKeyFactory;
}
@@ -104,8 +104,8 @@
case NO_CHANGE:
case FAST_FORWARD:
try {
- deleteKeySenderFactory
- .create(rsrc.getUser(), ImmutableList.of(PublicKeyStore.keyToString(key)))
+ deleteKeyEmailFactories
+ .createEmail(rsrc.getUser(), ImmutableList.of(PublicKeyStore.keyToString(key)))
.send();
} catch (EmailException e) {
logger.atSevere().withCause(e).log(
diff --git a/java/com/google/gerrit/gpg/server/PostGpgKeys.java b/java/com/google/gerrit/gpg/server/PostGpgKeys.java
index a45c400..edd5a58 100644
--- a/java/com/google/gerrit/gpg/server/PostGpgKeys.java
+++ b/java/com/google/gerrit/gpg/server/PostGpgKeys.java
@@ -58,8 +58,8 @@
import com.google.gerrit.server.account.externalids.ExternalIdFactory;
import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
import com.google.gerrit.server.account.externalids.ExternalIds;
-import com.google.gerrit.server.mail.send.AddKeySender;
-import com.google.gerrit.server.mail.send.DeleteKeySender;
+import com.google.gerrit.server.mail.EmailModule.AddKeyEmailFactories;
+import com.google.gerrit.server.mail.EmailModule.DeleteKeyEmailFactories;
import com.google.gerrit.server.query.account.InternalAccountQuery;
import com.google.gerrit.server.update.RetryHelper;
import com.google.inject.Inject;
@@ -91,8 +91,8 @@
private final Provider<CurrentUser> self;
private final Provider<PublicKeyStore> storeProvider;
private final GerritPublicKeyChecker.Factory checkerFactory;
- private final AddKeySender.Factory addKeySenderFactory;
- private final DeleteKeySender.Factory deleteKeySenderFactory;
+ private final AddKeyEmailFactories addKeyEmailFactories;
+ private final DeleteKeyEmailFactories deleteKeyEmailFactories;
private final Provider<InternalAccountQuery> accountQueryProvider;
private final ExternalIds externalIds;
private final Provider<AccountsUpdate> accountsUpdateProvider;
@@ -106,8 +106,8 @@
Provider<CurrentUser> self,
Provider<PublicKeyStore> storeProvider,
GerritPublicKeyChecker.Factory checkerFactory,
- AddKeySender.Factory addKeySenderFactory,
- DeleteKeySender.Factory deleteKeySenderFactory,
+ AddKeyEmailFactories addKeyEmailFactories,
+ DeleteKeyEmailFactories deleteKeyEmailFactories,
Provider<InternalAccountQuery> accountQueryProvider,
ExternalIds externalIds,
@UserInitiated Provider<AccountsUpdate> accountsUpdateProvider,
@@ -118,8 +118,8 @@
this.self = self;
this.storeProvider = storeProvider;
this.checkerFactory = checkerFactory;
- this.addKeySenderFactory = addKeySenderFactory;
- this.deleteKeySenderFactory = deleteKeySenderFactory;
+ this.addKeyEmailFactories = addKeyEmailFactories;
+ this.deleteKeyEmailFactories = deleteKeyEmailFactories;
this.accountQueryProvider = accountQueryProvider;
this.externalIds = externalIds;
this.accountsUpdateProvider = accountsUpdateProvider;
@@ -263,7 +263,7 @@
case FORCED:
if (!addedKeys.isEmpty()) {
try {
- addKeySenderFactory.create(user, addedKeys).send();
+ addKeyEmailFactories.createEmail(user, addedKeys).send();
} catch (EmailException e) {
logger.atSevere().withCause(e).log(
"Cannot send GPG key added message to %s",
@@ -272,8 +272,8 @@
}
if (!toRemove.isEmpty()) {
try {
- deleteKeySenderFactory
- .create(user, toRemove.stream().map(Fingerprint::toString).collect(toList()))
+ deleteKeyEmailFactories
+ .createEmail(user, toRemove.stream().map(Fingerprint::toString).collect(toList()))
.send();
} catch (EmailException e) {
logger.atSevere().withCause(e).log(
diff --git a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index 210ba7b..9056732 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -1615,7 +1615,9 @@
private void checkUserSession(HttpServletRequest req) throws AuthException {
CurrentUser user = globals.currentUser.get();
if (isRead(req)) {
- user.setAccessPath(AccessPath.REST_API);
+ if (user.getAccessPath().equals(AccessPath.UNKNOWN)) {
+ user.setAccessPath(AccessPath.REST_API);
+ }
} else if (user instanceof AnonymousUser) {
throw new AuthException("Authentication required");
} else if (!globals.webSession.get().isAccessPathOk(AccessPath.REST_API)) {
diff --git a/java/com/google/gerrit/pgm/init/SitePathInitializer.java b/java/com/google/gerrit/pgm/init/SitePathInitializer.java
index a057e66..d0d03b5 100644
--- a/java/com/google/gerrit/pgm/init/SitePathInitializer.java
+++ b/java/com/google/gerrit/pgm/init/SitePathInitializer.java
@@ -122,6 +122,8 @@
extractMailExample("DeleteReviewerHtml.soy");
extractMailExample("DeleteVote.soy");
extractMailExample("DeleteVoteHtml.soy");
+ extractMailExample("Email.soy");
+ extractMailExample("EmailHtml.soy");
extractMailExample("Footer.soy");
extractMailExample("FooterHtml.soy");
extractMailExample("ChangeHeader.soy");
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/server/StarredChangesUtil.java
index 2d18054..cf04029 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/server/StarredChangesUtil.java
@@ -49,10 +49,12 @@
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
+import java.util.List;
import java.util.NavigableSet;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
+import java.util.stream.Collectors;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
@@ -217,6 +219,29 @@
}
/**
+ * Returns a subset of change IDs among the input {@code changeIds} list that are starred by the
+ * {@code caller} user.
+ */
+ public Set<Change.Id> areStarred(
+ Repository allUsersRepo, List<Change.Id> changeIds, Account.Id caller) {
+ List<String> starRefs =
+ changeIds.stream()
+ .map(c -> RefNames.refsStarredChanges(c, caller))
+ .collect(Collectors.toList());
+ try {
+ return allUsersRepo.getRefDatabase().exactRef(starRefs.toArray(new String[0])).keySet()
+ .stream()
+ .map(r -> Change.Id.fromAllUsersRef(r))
+ .collect(Collectors.toSet());
+ } catch (IOException e) {
+ logger.atWarning().withCause(e).log(
+ "Failed getting starred changes for account %d within changes: %s",
+ caller.get(), Joiner.on(", ").join(changeIds));
+ return ImmutableSet.of();
+ }
+ }
+
+ /**
* Unstar the given change for all users.
*
* <p>Intended for use only when we're about to delete a change. For that reason, the change is
diff --git a/java/com/google/gerrit/server/change/AbandonOp.java b/java/com/google/gerrit/server/change/AbandonOp.java
index 14216b3..621994d 100644
--- a/java/com/google/gerrit/server/change/AbandonOp.java
+++ b/java/com/google/gerrit/server/change/AbandonOp.java
@@ -26,9 +26,9 @@
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.extensions.events.ChangeAbandoned;
import com.google.gerrit.server.mail.EmailModule.AbandonedChangeEmailFactories;
-import com.google.gerrit.server.mail.send.ChangeEmailNew;
+import com.google.gerrit.server.mail.send.ChangeEmail;
import com.google.gerrit.server.mail.send.MessageIdGenerator;
-import com.google.gerrit.server.mail.send.OutgoingEmailNew;
+import com.google.gerrit.server.mail.send.OutgoingEmail;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.ChangeContext;
@@ -112,10 +112,10 @@
public void postUpdate(PostUpdateContext ctx) {
NotifyResolver.Result notify = ctx.getNotify(change.getId());
try {
- ChangeEmailNew changeEmail =
+ ChangeEmail changeEmail =
abandonedChangeEmailFactories.createChangeEmail(ctx.getProject(), change.getId());
changeEmail.setChangeMessage(mailMessage, ctx.getWhen());
- OutgoingEmailNew email = abandonedChangeEmailFactories.createEmail(changeEmail);
+ OutgoingEmail email = abandonedChangeEmailFactories.createEmail(changeEmail);
if (accountState != null) {
email.setFrom(accountState.account().id());
}
diff --git a/java/com/google/gerrit/server/change/ChangeInserter.java b/java/com/google/gerrit/server/change/ChangeInserter.java
index 8773bb7..4273a72 100644
--- a/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -63,8 +63,11 @@
import com.google.gerrit.server.git.GroupCollector;
import com.google.gerrit.server.git.validators.CommitValidationException;
import com.google.gerrit.server.git.validators.CommitValidators;
-import com.google.gerrit.server.mail.send.CreateChangeSender;
+import com.google.gerrit.server.mail.EmailModule.StartReviewChangeEmailFactories;
+import com.google.gerrit.server.mail.send.ChangeEmail;
import com.google.gerrit.server.mail.send.MessageIdGenerator;
+import com.google.gerrit.server.mail.send.OutgoingEmail;
+import com.google.gerrit.server.mail.send.StartReviewChangeEmailDecorator;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.patch.AutoMerger;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
@@ -110,7 +113,7 @@
private final PatchSetUtil psUtil;
private final ApprovalsUtil approvalsUtil;
private final ChangeMessagesUtil cmUtil;
- private final CreateChangeSender.Factory createChangeSenderFactory;
+ private final StartReviewChangeEmailFactories startReviewChangeEmailFactories;
private final ExecutorService sendEmailExecutor;
private final CommitValidators.Factory commitValidatorsFactory;
private final RevisionCreated revisionCreated;
@@ -162,7 +165,7 @@
PatchSetUtil psUtil,
ApprovalsUtil approvalsUtil,
ChangeMessagesUtil cmUtil,
- CreateChangeSender.Factory createChangeSenderFactory,
+ StartReviewChangeEmailFactories startReviewChangeEmailFactories,
@SendEmailExecutor ExecutorService sendEmailExecutor,
CommitValidators.Factory commitValidatorsFactory,
CommentAdded commentAdded,
@@ -180,7 +183,7 @@
this.psUtil = psUtil;
this.approvalsUtil = approvalsUtil;
this.cmUtil = cmUtil;
- this.createChangeSenderFactory = createChangeSenderFactory;
+ this.startReviewChangeEmailFactories = startReviewChangeEmailFactories;
this.sendEmailExecutor = sendEmailExecutor;
this.commitValidatorsFactory = commitValidatorsFactory;
this.revisionCreated = revisionCreated;
@@ -531,24 +534,30 @@
@Override
public void run() {
try {
- CreateChangeSender emailSender =
- createChangeSenderFactory.create(change.getProject(), change.getId());
- emailSender.setFrom(change.getOwner());
- emailSender.setPatchSet(patchSet, patchSetInfo);
- emailSender.setNotify(notify);
- emailSender.addReviewers(
+ StartReviewChangeEmailDecorator startReviewEmail =
+ startReviewChangeEmailFactories.createStartReviewChangeEmail();
+ startReviewEmail.markAsCreateChange();
+ startReviewEmail.addReviewers(
reviewerAdditions.flattenResults(ReviewerOp.Result::addedReviewers).stream()
.map(PatchSetApproval::accountId)
.collect(toImmutableSet()));
- emailSender.addReviewersByEmail(
+ startReviewEmail.addReviewersByEmail(
reviewerAdditions.flattenResults(ReviewerOp.Result::addedReviewersByEmail));
- emailSender.addExtraCC(
+ startReviewEmail.addExtraCC(
reviewerAdditions.flattenResults(ReviewerOp.Result::addedCCs));
- emailSender.addExtraCCByEmail(
+ startReviewEmail.addExtraCCByEmail(
reviewerAdditions.flattenResults(ReviewerOp.Result::addedCCsByEmail));
- emailSender.setMessageId(
+ ChangeEmail changeEmail =
+ startReviewChangeEmailFactories.createChangeEmail(
+ change.getProject(), change.getId(), startReviewEmail);
+ changeEmail.setPatchSet(patchSet, patchSetInfo);
+ OutgoingEmail outgoingEmail =
+ startReviewChangeEmailFactories.createEmail(changeEmail);
+ outgoingEmail.setFrom(change.getOwner());
+ outgoingEmail.setNotify(notify);
+ outgoingEmail.setMessageId(
messageIdGenerator.fromChangeUpdate(ctx.getRepoView(), patchSet.id()));
- emailSender.send();
+ outgoingEmail.send();
} catch (Exception e) {
logger.atSevere().withCause(e).log(
"Cannot send email for new change %s", change.getId());
diff --git a/java/com/google/gerrit/server/change/ChangeJson.java b/java/com/google/gerrit/server/change/ChangeJson.java
index 839629e..4875978 100644
--- a/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/java/com/google/gerrit/server/change/ChangeJson.java
@@ -31,6 +31,7 @@
import static com.google.gerrit.extensions.client.ListChangesOption.REVIEWED;
import static com.google.gerrit.extensions.client.ListChangesOption.REVIEWER_UPDATES;
import static com.google.gerrit.extensions.client.ListChangesOption.SKIP_DIFFSTAT;
+import static com.google.gerrit.extensions.client.ListChangesOption.STAR;
import static com.google.gerrit.extensions.client.ListChangesOption.SUBMITTABLE;
import static com.google.gerrit.extensions.client.ListChangesOption.SUBMIT_REQUIREMENTS;
import static com.google.gerrit.extensions.client.ListChangesOption.TRACKING_IDS;
@@ -100,10 +101,12 @@
import com.google.gerrit.server.account.AccountInfoComparator;
import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.cancellation.RequestCancelledException;
+import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.TrackingFooters;
import com.google.gerrit.server.experiments.ExperimentFeatures;
import com.google.gerrit.server.experiments.ExperimentFeaturesConstants;
+import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ReviewerStateInternal;
@@ -133,6 +136,7 @@
import java.util.stream.Collectors;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
/**
* Produces {@link ChangeInfo} (which is serialized to JSON afterwards) from {@link ChangeData}.
@@ -222,12 +226,15 @@
}
}
+ private final GitRepositoryManager repoManager;
+ private final AllUsersName allUsers;
private final Provider<CurrentUser> userProvider;
private final PermissionBackend permissionBackend;
private final ChangeData.Factory changeDataFactory;
private final AccountLoader.Factory accountLoaderFactory;
private final ImmutableSet<ListChangesOption> options;
private final ChangeMessagesUtil cmUtil;
+ private final StarredChangesUtil starredChangesUtil;
private final Provider<ConsistencyChecker> checkerProvider;
private final ActionJson actionJson;
private final ChangeNotes.Factory notesFactory;
@@ -247,12 +254,15 @@
@Inject
ChangeJson(
+ GitRepositoryManager repoManager,
+ AllUsersName allUsers,
ExperimentFeatures experimentFeatures,
Provider<CurrentUser> user,
PermissionBackend permissionBackend,
ChangeData.Factory cdf,
AccountLoader.Factory ailf,
ChangeMessagesUtil cmUtil,
+ StarredChangesUtil starredChangesUtil,
Provider<ConsistencyChecker> checkerProvider,
ActionJson actionJson,
ChangeNotes.Factory notesFactory,
@@ -264,12 +274,15 @@
@GerritServerConfig Config cfg,
@Assisted Iterable<ListChangesOption> options,
@Assisted Optional<PluginDefinedInfosFactory> pluginDefinedInfosFactory) {
+ this.repoManager = repoManager;
+ this.allUsers = allUsers;
this.experimentFeatures = experimentFeatures;
this.userProvider = user;
this.changeDataFactory = cdf;
this.permissionBackend = permissionBackend;
this.accountLoaderFactory = ailf;
this.cmUtil = cmUtil;
+ this.starredChangesUtil = starredChangesUtil;
this.checkerProvider = checkerProvider;
this.actionJson = actionJson;
this.notesFactory = notesFactory;
@@ -539,6 +552,9 @@
"Omitting corrupt change %s from results", cd.getId());
}
}
+ if (has(STAR)) {
+ populateStarField(changeInfos);
+ }
return changeInfos;
}
}
@@ -956,6 +972,25 @@
return map.build();
}
+ /** Populate the 'starred' field. */
+ private void populateStarField(List<ChangeInfo> changeInfos) {
+ // We populate the 'starred' field for all change infos together so that we open the All-Users
+ // repository only once
+ try (Repository allUsersRepo = repoManager.openRepository(allUsers)) {
+ List<Change.Id> changeIds =
+ changeInfos.stream().map(c -> Change.id(c._number)).collect(Collectors.toList());
+ Set<Change.Id> starredChanges =
+ starredChangesUtil.areStarred(
+ allUsersRepo, changeIds, userProvider.get().asIdentifiedUser().getAccountId());
+ if (starredChanges.isEmpty()) {
+ return;
+ }
+ changeInfos.stream().forEach(c -> c.starred = starredChanges.contains(Change.id(c._number)));
+ } catch (IOException e) {
+ logger.atWarning().withCause(e).log("Failed to open All-Users repo.");
+ }
+ }
+
private List<PluginDefinedInfo> getPluginInfos(ChangeData cd) {
return getPluginInfos(Collections.singleton(cd)).get(cd.getId());
}
diff --git a/java/com/google/gerrit/server/change/DeleteReviewerByEmailOp.java b/java/com/google/gerrit/server/change/DeleteReviewerByEmailOp.java
index 2030ef2..294049f 100644
--- a/java/com/google/gerrit/server/change/DeleteReviewerByEmailOp.java
+++ b/java/com/google/gerrit/server/change/DeleteReviewerByEmailOp.java
@@ -20,10 +20,10 @@
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.mail.EmailModule.DeleteReviewerChangeEmailFactories;
-import com.google.gerrit.server.mail.send.ChangeEmailNew;
+import com.google.gerrit.server.mail.send.ChangeEmail;
import com.google.gerrit.server.mail.send.DeleteReviewerChangeEmailDecorator;
import com.google.gerrit.server.mail.send.MessageIdGenerator;
-import com.google.gerrit.server.mail.send.OutgoingEmailNew;
+import com.google.gerrit.server.mail.send.OutgoingEmail;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.update.ChangeContext;
import com.google.gerrit.server.update.PostUpdateContext;
@@ -82,12 +82,11 @@
DeleteReviewerChangeEmailDecorator deleteReviewerEmail =
deleteReviewerChangeEmailFactories.createDeleteReviewerChangeEmail();
deleteReviewerEmail.addReviewersByEmail(Collections.singleton(reviewer));
- ChangeEmailNew changeEmail =
+ ChangeEmail changeEmail =
deleteReviewerChangeEmailFactories.createChangeEmail(
ctx.getProject(), change.getId(), deleteReviewerEmail);
changeEmail.setChangeMessage(mailMessage, ctx.getWhen());
- OutgoingEmailNew outgoingEmail =
- deleteReviewerChangeEmailFactories.createEmail(changeEmail);
+ OutgoingEmail outgoingEmail = deleteReviewerChangeEmailFactories.createEmail(changeEmail);
outgoingEmail.setFrom(ctx.getAccountId());
outgoingEmail.setNotify(notify);
outgoingEmail.setMessageId(
diff --git a/java/com/google/gerrit/server/change/DeleteReviewerOp.java b/java/com/google/gerrit/server/change/DeleteReviewerOp.java
index a1c4f71..f961d61 100644
--- a/java/com/google/gerrit/server/change/DeleteReviewerOp.java
+++ b/java/com/google/gerrit/server/change/DeleteReviewerOp.java
@@ -37,10 +37,10 @@
import com.google.gerrit.server.approval.ApprovalsUtil;
import com.google.gerrit.server.extensions.events.ReviewerDeleted;
import com.google.gerrit.server.mail.EmailModule.DeleteReviewerChangeEmailFactories;
-import com.google.gerrit.server.mail.send.ChangeEmailNew;
+import com.google.gerrit.server.mail.send.ChangeEmail;
import com.google.gerrit.server.mail.send.DeleteReviewerChangeEmailDecorator;
import com.google.gerrit.server.mail.send.MessageIdGenerator;
-import com.google.gerrit.server.mail.send.OutgoingEmailNew;
+import com.google.gerrit.server.mail.send.OutgoingEmail;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.notedb.ReviewerStateInternal;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -256,11 +256,11 @@
DeleteReviewerChangeEmailDecorator deleteReviewerEmail =
deleteReviewerChangeEmailFactories.createDeleteReviewerChangeEmail();
deleteReviewerEmail.addReviewers(Collections.singleton(reviewer.id()));
- ChangeEmailNew changeEmail =
+ ChangeEmail changeEmail =
deleteReviewerChangeEmailFactories.createChangeEmail(
projectName, change.getId(), deleteReviewerEmail);
changeEmail.setChangeMessage(mailMessage, timestamp.toInstant());
- OutgoingEmailNew outgoingEmail = deleteReviewerChangeEmailFactories.createEmail(changeEmail);
+ OutgoingEmail outgoingEmail = deleteReviewerChangeEmailFactories.createEmail(changeEmail);
outgoingEmail.setFrom(userId);
outgoingEmail.setNotify(notify);
outgoingEmail.setMessageId(
diff --git a/java/com/google/gerrit/server/change/EmailNewPatchSet.java b/java/com/google/gerrit/server/change/EmailNewPatchSet.java
index fedaad2..f695a56 100644
--- a/java/com/google/gerrit/server/change/EmailNewPatchSet.java
+++ b/java/com/google/gerrit/server/change/EmailNewPatchSet.java
@@ -29,10 +29,10 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.SendEmailExecutor;
import com.google.gerrit.server.mail.EmailModule.ReplacePatchSetChangeEmailFactories;
-import com.google.gerrit.server.mail.send.ChangeEmailNew;
+import com.google.gerrit.server.mail.send.ChangeEmail;
import com.google.gerrit.server.mail.send.MessageIdGenerator;
import com.google.gerrit.server.mail.send.MessageIdGenerator.MessageId;
-import com.google.gerrit.server.mail.send.OutgoingEmailNew;
+import com.google.gerrit.server.mail.send.OutgoingEmail;
import com.google.gerrit.server.mail.send.ReplacePatchSetChangeEmailDecorator;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.update.PostUpdateContext;
@@ -221,13 +221,12 @@
replacePatchSetEmail.addReviewers(reviewers);
replacePatchSetEmail.addExtraCC(extraCcs);
replacePatchSetEmail.addOutdatedApproval(outdatedApprovals);
- ChangeEmailNew changeEmail =
+ ChangeEmail changeEmail =
replacePatchSetChangeEmailFactories.createChangeEmail(
projectName, changeId, replacePatchSetEmail);
changeEmail.setPatchSet(patchSet, patchSetInfoFactory.get(projectName, patchSet));
changeEmail.setChangeMessage(message, timestamp);
- OutgoingEmailNew outgoingEmail =
- replacePatchSetChangeEmailFactories.createEmail(changeEmail);
+ OutgoingEmail outgoingEmail = replacePatchSetChangeEmailFactories.createEmail(changeEmail);
outgoingEmail.setFrom(user.getAccountId());
outgoingEmail.setNotify(notify);
outgoingEmail.setMessageId(messageId);
diff --git a/java/com/google/gerrit/server/change/EmailReviewComments.java b/java/com/google/gerrit/server/change/EmailReviewComments.java
index a1201ed..67e09b0 100644
--- a/java/com/google/gerrit/server/change/EmailReviewComments.java
+++ b/java/com/google/gerrit/server/change/EmailReviewComments.java
@@ -29,11 +29,11 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.SendEmailExecutor;
import com.google.gerrit.server.mail.EmailModule.CommentChangeEmailFactories;
-import com.google.gerrit.server.mail.send.ChangeEmailNew;
+import com.google.gerrit.server.mail.send.ChangeEmail;
import com.google.gerrit.server.mail.send.CommentChangeEmailDecorator;
import com.google.gerrit.server.mail.send.MessageIdGenerator;
import com.google.gerrit.server.mail.send.MessageIdGenerator.MessageId;
-import com.google.gerrit.server.mail.send.OutgoingEmailNew;
+import com.google.gerrit.server.mail.send.OutgoingEmail;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.update.PostUpdateContext;
import com.google.gerrit.server.util.LabelVote;
@@ -214,12 +214,12 @@
commentChangeEmail.setComments(comments);
commentChangeEmail.setPatchSetComment(patchSetComment);
commentChangeEmail.setLabels(labels);
- ChangeEmailNew changeEmail =
+ ChangeEmail changeEmail =
commentChangeEmailFactories.createChangeEmail(
projectName, changeId, commentChangeEmail);
changeEmail.setPatchSet(patchSet, patchSetInfoFactory.get(projectName, patchSet));
changeEmail.setChangeMessage(message, timestamp);
- OutgoingEmailNew outgoingEmail = commentChangeEmailFactories.createEmail(changeEmail);
+ OutgoingEmail outgoingEmail = commentChangeEmailFactories.createEmail(changeEmail);
outgoingEmail.setFrom(user.getAccountId());
outgoingEmail.setNotify(notify);
outgoingEmail.setMessageId(messageId);
diff --git a/java/com/google/gerrit/server/change/ModifyReviewersEmail.java b/java/com/google/gerrit/server/change/ModifyReviewersEmail.java
index cb747f6..5f2a5fd 100644
--- a/java/com/google/gerrit/server/change/ModifyReviewersEmail.java
+++ b/java/com/google/gerrit/server/change/ModifyReviewersEmail.java
@@ -24,8 +24,11 @@
import com.google.gerrit.entities.Project;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.SendEmailExecutor;
+import com.google.gerrit.server.mail.EmailModule.StartReviewChangeEmailFactories;
+import com.google.gerrit.server.mail.send.ChangeEmail;
import com.google.gerrit.server.mail.send.MessageIdGenerator;
-import com.google.gerrit.server.mail.send.ModifyReviewerSender;
+import com.google.gerrit.server.mail.send.OutgoingEmail;
+import com.google.gerrit.server.mail.send.StartReviewChangeEmailDecorator;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.Collection;
@@ -36,16 +39,16 @@
public class ModifyReviewersEmail {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- private final ModifyReviewerSender.Factory addReviewerSenderFactory;
+ private final StartReviewChangeEmailFactories startReviewChangeEmailFactories;
private final ExecutorService sendEmailsExecutor;
private final MessageIdGenerator messageIdGenerator;
@Inject
ModifyReviewersEmail(
- ModifyReviewerSender.Factory addReviewerSenderFactory,
+ StartReviewChangeEmailFactories startReviewChangeEmailFactories,
@SendEmailExecutor ExecutorService sendEmailsExecutor,
MessageIdGenerator messageIdGenerator) {
- this.addReviewerSenderFactory = addReviewerSenderFactory;
+ this.startReviewChangeEmailFactories = startReviewChangeEmailFactories;
this.sendEmailsExecutor = sendEmailsExecutor;
this.messageIdGenerator = messageIdGenerator;
}
@@ -90,20 +93,25 @@
sendEmailsExecutor.submit(
() -> {
try {
- ModifyReviewerSender emailSender =
- addReviewerSenderFactory.create(projectNameKey, cId);
- emailSender.setNotify(notify);
- emailSender.setFrom(userId);
- emailSender.addReviewers(immutableToMail);
- emailSender.addReviewersByEmail(immutableAddedByEmail);
- emailSender.addExtraCC(immutableToCopy);
- emailSender.addExtraCCByEmail(immutableCopiedByEmail);
- emailSender.addRemovedReviewers(immutableToRemove);
- emailSender.addRemovedByEmailReviewers(immutableRemovedByEmail);
- emailSender.setMessageId(
+ StartReviewChangeEmailDecorator startReviewEmail =
+ startReviewChangeEmailFactories.createStartReviewChangeEmail();
+ startReviewEmail.addReviewers(immutableToMail);
+ startReviewEmail.addReviewersByEmail(immutableAddedByEmail);
+ startReviewEmail.addExtraCC(immutableToCopy);
+ startReviewEmail.addExtraCCByEmail(immutableCopiedByEmail);
+ startReviewEmail.addRemovedReviewers(immutableToRemove);
+ startReviewEmail.addRemovedByEmailReviewers(immutableRemovedByEmail);
+ ChangeEmail changeEmail =
+ startReviewChangeEmailFactories.createChangeEmail(
+ projectNameKey, cId, startReviewEmail);
+ OutgoingEmail outgoingEmail =
+ startReviewChangeEmailFactories.createEmail(changeEmail);
+ outgoingEmail.setNotify(notify);
+ outgoingEmail.setFrom(userId);
+ outgoingEmail.setMessageId(
messageIdGenerator.fromChangeUpdate(
change.getProject(), change.currentPatchSetId()));
- emailSender.send();
+ outgoingEmail.send();
} catch (Exception err) {
logger.atSevere().withCause(err).log(
"Cannot send email to new reviewers of change %s", change.getId());
diff --git a/java/com/google/gerrit/server/config/GerritGlobalModule.java b/java/com/google/gerrit/server/config/GerritGlobalModule.java
index ea2465b..1d85fc6 100644
--- a/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -169,7 +169,6 @@
import com.google.gerrit.server.mail.MailFilter;
import com.google.gerrit.server.mail.send.FromAddressGenerator;
import com.google.gerrit.server.mail.send.FromAddressGeneratorProvider;
-import com.google.gerrit.server.mail.send.InboundEmailRejectionSender;
import com.google.gerrit.server.mail.send.MailSoySauceModule;
import com.google.gerrit.server.mail.send.MailSoyTemplateProvider;
import com.google.gerrit.server.mime.FileTypeRegistry;
@@ -305,7 +304,6 @@
factory(PatchScriptFactoryForAutoFix.Factory.class);
factory(ProjectState.Factory.class);
factory(RevisionJson.Factory.class);
- factory(InboundEmailRejectionSender.Factory.class);
factory(ExternalUser.Factory.class);
bind(PermissionCollection.Factory.class);
bind(AccountVisibility.class).toProvider(AccountVisibilityProvider.class).in(SINGLETON);
diff --git a/java/com/google/gerrit/server/git/CommitUtil.java b/java/com/google/gerrit/server/git/CommitUtil.java
index 6913b1e..40d714d 100644
--- a/java/com/google/gerrit/server/git/CommitUtil.java
+++ b/java/com/google/gerrit/server/git/CommitUtil.java
@@ -44,9 +44,9 @@
import com.google.gerrit.server.change.ValidationOptionsUtil;
import com.google.gerrit.server.extensions.events.ChangeReverted;
import com.google.gerrit.server.mail.EmailModule.RevertedChangeEmailFactories;
-import com.google.gerrit.server.mail.send.ChangeEmailNew;
+import com.google.gerrit.server.mail.send.ChangeEmail;
import com.google.gerrit.server.mail.send.MessageIdGenerator;
-import com.google.gerrit.server.mail.send.OutgoingEmailNew;
+import com.google.gerrit.server.mail.send.OutgoingEmail;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ReviewerStateInternal;
import com.google.gerrit.server.notedb.Sequences;
@@ -383,10 +383,10 @@
ctx.getChangeData(changeNotesFactory.createChecked(ctx.getProject(), revertingChangeId));
changeReverted.fire(revertedChange, revertingChange, ctx.getWhen());
try {
- ChangeEmailNew changeEmail =
+ ChangeEmail changeEmail =
revertedChangeEmailFactories.createChangeEmail(
ctx.getProject(), revertedChange.getId());
- OutgoingEmailNew outgoingEmail = revertedChangeEmailFactories.createEmail(changeEmail);
+ OutgoingEmail outgoingEmail = revertedChangeEmailFactories.createEmail(changeEmail);
outgoingEmail.setFrom(ctx.getAccountId());
outgoingEmail.setNotify(ctx.getNotify(revertedChangeId));
outgoingEmail.setMessageId(
diff --git a/java/com/google/gerrit/server/git/MergedByPushOp.java b/java/com/google/gerrit/server/git/MergedByPushOp.java
index cb46df1..8844c1e 100644
--- a/java/com/google/gerrit/server/git/MergedByPushOp.java
+++ b/java/com/google/gerrit/server/git/MergedByPushOp.java
@@ -27,9 +27,9 @@
import com.google.gerrit.server.config.SendEmailExecutor;
import com.google.gerrit.server.extensions.events.ChangeMerged;
import com.google.gerrit.server.mail.EmailModule.MergedChangeEmailFactories;
-import com.google.gerrit.server.mail.send.ChangeEmailNew;
+import com.google.gerrit.server.mail.send.ChangeEmail;
import com.google.gerrit.server.mail.send.MessageIdGenerator;
-import com.google.gerrit.server.mail.send.OutgoingEmailNew;
+import com.google.gerrit.server.mail.send.OutgoingEmail;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.update.BatchUpdateOp;
@@ -190,13 +190,13 @@
try {
// The stickyApprovalDiff is always empty here since this is not supported
// for direct pushes.
- ChangeEmailNew changeEmail =
+ ChangeEmail changeEmail =
mergedChangeEmailFactories.createChangeEmail(
ctx.getProject(),
psId.changeId(),
/* stickyApprovalDiff= */ Optional.empty());
changeEmail.setPatchSet(patchSet, info);
- OutgoingEmailNew outgoingEmail =
+ OutgoingEmail outgoingEmail =
mergedChangeEmailFactories.createEmail(changeEmail);
outgoingEmail.setFrom(ctx.getAccountId());
outgoingEmail.setMessageId(
diff --git a/java/com/google/gerrit/server/mail/EmailModule.java b/java/com/google/gerrit/server/mail/EmailModule.java
index 355dd5f..340a103 100644
--- a/java/com/google/gerrit/server/mail/EmailModule.java
+++ b/java/com/google/gerrit/server/mail/EmailModule.java
@@ -14,62 +14,58 @@
package com.google.gerrit.server.mail;
+import com.google.gerrit.entities.Address;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.SubmitRequirement;
import com.google.gerrit.entities.SubmitRequirementResult;
import com.google.gerrit.extensions.client.ChangeKind;
import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountSshKey;
import com.google.gerrit.server.mail.send.AbandonedChangeEmailDecorator;
-import com.google.gerrit.server.mail.send.AddKeySender;
+import com.google.gerrit.server.mail.send.AddKeyEmailDecoratorFactory;
import com.google.gerrit.server.mail.send.AttentionSetChangeEmailDecorator;
import com.google.gerrit.server.mail.send.AttentionSetChangeEmailDecorator.AttentionSetChange;
-import com.google.gerrit.server.mail.send.ChangeEmailNew;
-import com.google.gerrit.server.mail.send.ChangeEmailNewFactory;
+import com.google.gerrit.server.mail.send.ChangeEmail;
+import com.google.gerrit.server.mail.send.ChangeEmailFactory;
import com.google.gerrit.server.mail.send.CommentChangeEmailDecorator;
import com.google.gerrit.server.mail.send.CommentChangeEmailDecoratorFactory;
-import com.google.gerrit.server.mail.send.CreateChangeSender;
-import com.google.gerrit.server.mail.send.DeleteKeySender;
+import com.google.gerrit.server.mail.send.DeleteKeyEmailDecoratorFactory;
import com.google.gerrit.server.mail.send.DeleteReviewerChangeEmailDecorator;
import com.google.gerrit.server.mail.send.DeleteVoteChangeEmailDecorator;
import com.google.gerrit.server.mail.send.EmailArguments;
-import com.google.gerrit.server.mail.send.HttpPasswordUpdateSender;
+import com.google.gerrit.server.mail.send.HttpPasswordUpdateEmailDecoratorFactory;
+import com.google.gerrit.server.mail.send.InboundEmailRejectionEmailDecorator;
+import com.google.gerrit.server.mail.send.InboundEmailRejectionEmailDecorator.InboundEmailError;
import com.google.gerrit.server.mail.send.MergedChangeEmailDecoratorFactory;
-import com.google.gerrit.server.mail.send.ModifyReviewerSender;
-import com.google.gerrit.server.mail.send.OutgoingEmailNew;
-import com.google.gerrit.server.mail.send.OutgoingEmailNewFactory;
-import com.google.gerrit.server.mail.send.RegisterNewEmailSender;
+import com.google.gerrit.server.mail.send.OutgoingEmail;
+import com.google.gerrit.server.mail.send.OutgoingEmailFactory;
+import com.google.gerrit.server.mail.send.RegisterNewEmailDecorator;
+import com.google.gerrit.server.mail.send.RegisterNewEmailDecoratorFactory;
import com.google.gerrit.server.mail.send.ReplacePatchSetChangeEmailDecorator;
import com.google.gerrit.server.mail.send.ReplacePatchSetChangeEmailDecoratorFactory;
import com.google.gerrit.server.mail.send.RestoredChangeEmailDecorator;
import com.google.gerrit.server.mail.send.RevertedChangeEmailDecorator;
+import com.google.gerrit.server.mail.send.StartReviewChangeEmailDecorator;
import com.google.inject.Inject;
+import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.eclipse.jgit.lib.ObjectId;
public class EmailModule extends FactoryModule {
- @Override
- protected void configure() {
- factory(AddKeySender.Factory.class);
- factory(ModifyReviewerSender.Factory.class);
- factory(CreateChangeSender.Factory.class);
- factory(DeleteKeySender.Factory.class);
- factory(HttpPasswordUpdateSender.Factory.class);
- factory(RegisterNewEmailSender.Factory.class);
- }
-
public static class AbandonedChangeEmailFactories {
private final EmailArguments args;
- private final ChangeEmailNewFactory changeEmailFactory;
- private final OutgoingEmailNewFactory outgoingEmailFactory;
+ private final ChangeEmailFactory changeEmailFactory;
+ private final OutgoingEmailFactory outgoingEmailFactory;
private final AbandonedChangeEmailDecorator abandonedChangeEmailDecorator;
@Inject
AbandonedChangeEmailFactories(
EmailArguments args,
- ChangeEmailNewFactory changeEmailFactory,
- OutgoingEmailNewFactory outgoingEmailFactory,
+ ChangeEmailFactory changeEmailFactory,
+ OutgoingEmailFactory outgoingEmailFactory,
AbandonedChangeEmailDecorator abandonedChangeEmailDecorator) {
this.args = args;
this.changeEmailFactory = changeEmailFactory;
@@ -77,26 +73,26 @@
this.abandonedChangeEmailDecorator = abandonedChangeEmailDecorator;
}
- public ChangeEmailNew createChangeEmail(Project.NameKey project, Change.Id changeId) {
+ public ChangeEmail createChangeEmail(Project.NameKey project, Change.Id changeId) {
return changeEmailFactory.create(
args.newChangeData(project, changeId), abandonedChangeEmailDecorator);
}
- public OutgoingEmailNew createEmail(ChangeEmailNew changeEmail) {
+ public OutgoingEmail createEmail(ChangeEmail changeEmail) {
return outgoingEmailFactory.create("abandon", changeEmail);
}
}
public static class AttentionSetChangeEmailFactories {
private final EmailArguments args;
- private final ChangeEmailNewFactory changeEmailFactory;
- private final OutgoingEmailNewFactory outgoingEmailFactory;
+ private final ChangeEmailFactory changeEmailFactory;
+ private final OutgoingEmailFactory outgoingEmailFactory;
@Inject
AttentionSetChangeEmailFactories(
EmailArguments args,
- ChangeEmailNewFactory changeEmailFactory,
- OutgoingEmailNewFactory outgoingEmailFactory) {
+ ChangeEmailFactory changeEmailFactory,
+ OutgoingEmailFactory outgoingEmailFactory) {
this.args = args;
this.changeEmailFactory = changeEmailFactory;
this.outgoingEmailFactory = outgoingEmailFactory;
@@ -106,7 +102,7 @@
return new AttentionSetChangeEmailDecorator();
}
- public ChangeEmailNew createChangeEmail(
+ public ChangeEmail createChangeEmail(
Project.NameKey project,
Change.Id changeId,
AttentionSetChangeEmailDecorator attentionSetChangeEmailDecorator) {
@@ -114,8 +110,8 @@
args.newChangeData(project, changeId), attentionSetChangeEmailDecorator);
}
- public OutgoingEmailNew createEmail(
- AttentionSetChange attentionSetChange, ChangeEmailNew changeEmail) {
+ public OutgoingEmail createEmail(
+ AttentionSetChange attentionSetChange, ChangeEmail changeEmail) {
if (attentionSetChange.equals(AttentionSetChange.USER_ADDED)) {
return outgoingEmailFactory.create("addToAttentionSet", changeEmail);
} else {
@@ -127,15 +123,15 @@
public static class CommentChangeEmailFactories {
private final EmailArguments args;
private final CommentChangeEmailDecoratorFactory commentChangeEmailFactory;
- private final ChangeEmailNewFactory changeEmailFactory;
- private final OutgoingEmailNewFactory outgoingEmailFactory;
+ private final ChangeEmailFactory changeEmailFactory;
+ private final OutgoingEmailFactory outgoingEmailFactory;
@Inject
CommentChangeEmailFactories(
EmailArguments args,
CommentChangeEmailDecoratorFactory commentChangeEmailFactory,
- ChangeEmailNewFactory changeEmailFactory,
- OutgoingEmailNewFactory outgoingEmailFactory) {
+ ChangeEmailFactory changeEmailFactory,
+ OutgoingEmailFactory outgoingEmailFactory) {
this.args = args;
this.commentChangeEmailFactory = commentChangeEmailFactory;
this.changeEmailFactory = changeEmailFactory;
@@ -151,7 +147,7 @@
project, changeId, preUpdateMetaId, postUpdateSubmitRequirementResults);
}
- public ChangeEmailNew createChangeEmail(
+ public ChangeEmail createChangeEmail(
Project.NameKey project,
Change.Id changeId,
CommentChangeEmailDecorator commentChangeEmailDecorator) {
@@ -159,21 +155,21 @@
args.newChangeData(project, changeId), commentChangeEmailDecorator);
}
- public OutgoingEmailNew createEmail(ChangeEmailNew changeEmail) {
+ public OutgoingEmail createEmail(ChangeEmail changeEmail) {
return outgoingEmailFactory.create("comment", changeEmail);
}
}
public static class DeleteReviewerChangeEmailFactories {
private final EmailArguments args;
- private final ChangeEmailNewFactory changeEmailFactory;
- private final OutgoingEmailNewFactory outgoingEmailFactory;
+ private final ChangeEmailFactory changeEmailFactory;
+ private final OutgoingEmailFactory outgoingEmailFactory;
@Inject
DeleteReviewerChangeEmailFactories(
EmailArguments args,
- ChangeEmailNewFactory changeEmailFactory,
- OutgoingEmailNewFactory outgoingEmailFactory) {
+ ChangeEmailFactory changeEmailFactory,
+ OutgoingEmailFactory outgoingEmailFactory) {
this.args = args;
this.changeEmailFactory = changeEmailFactory;
this.outgoingEmailFactory = outgoingEmailFactory;
@@ -183,7 +179,7 @@
return new DeleteReviewerChangeEmailDecorator();
}
- public ChangeEmailNew createChangeEmail(
+ public ChangeEmail createChangeEmail(
Project.NameKey project,
Change.Id changeId,
DeleteReviewerChangeEmailDecorator deleteReviewerChangeEmailDecorator) {
@@ -191,22 +187,22 @@
args.newChangeData(project, changeId), deleteReviewerChangeEmailDecorator);
}
- public OutgoingEmailNew createEmail(ChangeEmailNew changeEmail) {
+ public OutgoingEmail createEmail(ChangeEmail changeEmail) {
return outgoingEmailFactory.create("deleteReviewer", changeEmail);
}
}
public static class DeleteVoteChangeEmailFactories {
private final EmailArguments args;
- private final ChangeEmailNewFactory changeEmailFactory;
- private final OutgoingEmailNewFactory outgoingEmailFactory;
+ private final ChangeEmailFactory changeEmailFactory;
+ private final OutgoingEmailFactory outgoingEmailFactory;
private final DeleteVoteChangeEmailDecorator deleteVoteChangeEmailDecorator;
@Inject
DeleteVoteChangeEmailFactories(
EmailArguments args,
- ChangeEmailNewFactory changeEmailFactory,
- OutgoingEmailNewFactory outgoingEmailFactory,
+ ChangeEmailFactory changeEmailFactory,
+ OutgoingEmailFactory outgoingEmailFactory,
DeleteVoteChangeEmailDecorator deleteVoteChangeEmailDecorator) {
this.args = args;
this.changeEmailFactory = changeEmailFactory;
@@ -214,12 +210,12 @@
this.deleteVoteChangeEmailDecorator = deleteVoteChangeEmailDecorator;
}
- public ChangeEmailNew createChangeEmail(Project.NameKey project, Change.Id changeId) {
+ public ChangeEmail createChangeEmail(Project.NameKey project, Change.Id changeId) {
return changeEmailFactory.create(
args.newChangeData(project, changeId), deleteVoteChangeEmailDecorator);
}
- public OutgoingEmailNew createEmail(ChangeEmailNew changeEmail) {
+ public OutgoingEmail createEmail(ChangeEmail changeEmail) {
return outgoingEmailFactory.create("deleteVote", changeEmail);
}
}
@@ -227,29 +223,29 @@
public static class MergedChangeEmailFactories {
private final EmailArguments args;
private final MergedChangeEmailDecoratorFactory mergedChangeEmailDecoratorFactory;
- private final ChangeEmailNewFactory changeEmailFactory;
- private final OutgoingEmailNewFactory outgoingEmailFactory;
+ private final ChangeEmailFactory changeEmailFactory;
+ private final OutgoingEmailFactory outgoingEmailFactory;
@Inject
MergedChangeEmailFactories(
EmailArguments args,
MergedChangeEmailDecoratorFactory mergedChangeEmailDecoratorFactory,
- ChangeEmailNewFactory changeEmailFactory,
- OutgoingEmailNewFactory outgoingEmailFactory) {
+ ChangeEmailFactory changeEmailFactory,
+ OutgoingEmailFactory outgoingEmailFactory) {
this.args = args;
this.mergedChangeEmailDecoratorFactory = mergedChangeEmailDecoratorFactory;
this.changeEmailFactory = changeEmailFactory;
this.outgoingEmailFactory = outgoingEmailFactory;
}
- public ChangeEmailNew createChangeEmail(
+ public ChangeEmail createChangeEmail(
Project.NameKey project, Change.Id changeId, Optional<String> stickyApprovalDiff) {
return changeEmailFactory.create(
args.newChangeData(project, changeId),
mergedChangeEmailDecoratorFactory.create(stickyApprovalDiff));
}
- public OutgoingEmailNew createEmail(ChangeEmailNew changeEmail) {
+ public OutgoingEmail createEmail(ChangeEmail changeEmail) {
return outgoingEmailFactory.create("merged", changeEmail);
}
}
@@ -258,15 +254,15 @@
private final EmailArguments args;
private final ReplacePatchSetChangeEmailDecoratorFactory
replacePatchSetChangeEmailDecoratorFactory;
- private final ChangeEmailNewFactory changeEmailFactory;
- private final OutgoingEmailNewFactory outgoingEmailFactory;
+ private final ChangeEmailFactory changeEmailFactory;
+ private final OutgoingEmailFactory outgoingEmailFactory;
@Inject
ReplacePatchSetChangeEmailFactories(
EmailArguments args,
ReplacePatchSetChangeEmailDecoratorFactory replacePatchSetChangeEmailDecoratorFactory,
- ChangeEmailNewFactory changeEmailFactory,
- OutgoingEmailNewFactory outgoingEmailFactory) {
+ ChangeEmailFactory changeEmailFactory,
+ OutgoingEmailFactory outgoingEmailFactory) {
this.args = args;
this.replacePatchSetChangeEmailDecoratorFactory = replacePatchSetChangeEmailDecoratorFactory;
this.changeEmailFactory = changeEmailFactory;
@@ -283,7 +279,7 @@
project, changeId, changeKind, preUpdateMetaId, postUpdateSubmitRequirementResults);
}
- public ChangeEmailNew createChangeEmail(
+ public ChangeEmail createChangeEmail(
Project.NameKey project,
Change.Id changeId,
ReplacePatchSetChangeEmailDecorator replacePatchSetChangeEmailDecoratorFactory) {
@@ -291,22 +287,22 @@
args.newChangeData(project, changeId), replacePatchSetChangeEmailDecoratorFactory);
}
- public OutgoingEmailNew createEmail(ChangeEmailNew changeEmail) {
+ public OutgoingEmail createEmail(ChangeEmail changeEmail) {
return outgoingEmailFactory.create("newpatchset", changeEmail);
}
}
public static class RestoredChangeEmailFactories {
private final EmailArguments args;
- private final ChangeEmailNewFactory changeEmailFactory;
- private final OutgoingEmailNewFactory outgoingEmailFactory;
+ private final ChangeEmailFactory changeEmailFactory;
+ private final OutgoingEmailFactory outgoingEmailFactory;
private final RestoredChangeEmailDecorator restoredChangeEmailDecorator;
@Inject
RestoredChangeEmailFactories(
EmailArguments args,
- ChangeEmailNewFactory changeEmailFactory,
- OutgoingEmailNewFactory outgoingEmailFactory,
+ ChangeEmailFactory changeEmailFactory,
+ OutgoingEmailFactory outgoingEmailFactory,
RestoredChangeEmailDecorator restoredChangeEmailDecorator) {
this.args = args;
this.changeEmailFactory = changeEmailFactory;
@@ -314,27 +310,27 @@
this.restoredChangeEmailDecorator = restoredChangeEmailDecorator;
}
- public ChangeEmailNew createChangeEmail(Project.NameKey project, Change.Id changeId) {
+ public ChangeEmail createChangeEmail(Project.NameKey project, Change.Id changeId) {
return changeEmailFactory.create(
args.newChangeData(project, changeId), restoredChangeEmailDecorator);
}
- public OutgoingEmailNew createEmail(ChangeEmailNew changeEmail) {
+ public OutgoingEmail createEmail(ChangeEmail changeEmail) {
return outgoingEmailFactory.create("restore", changeEmail);
}
}
public static class RevertedChangeEmailFactories {
private final EmailArguments args;
- private final ChangeEmailNewFactory changeEmailFactory;
- private final OutgoingEmailNewFactory outgoingEmailFactory;
+ private final ChangeEmailFactory changeEmailFactory;
+ private final OutgoingEmailFactory outgoingEmailFactory;
private final RevertedChangeEmailDecorator revertedChangeEmailDecorator;
@Inject
RevertedChangeEmailFactories(
EmailArguments args,
- ChangeEmailNewFactory changeEmailFactory,
- OutgoingEmailNewFactory outgoingEmailFactory,
+ ChangeEmailFactory changeEmailFactory,
+ OutgoingEmailFactory outgoingEmailFactory,
RevertedChangeEmailDecorator revertedChangeEmailDecorator) {
this.args = args;
this.changeEmailFactory = changeEmailFactory;
@@ -342,13 +338,144 @@
this.revertedChangeEmailDecorator = revertedChangeEmailDecorator;
}
- public ChangeEmailNew createChangeEmail(Project.NameKey project, Change.Id changeId) {
+ public ChangeEmail createChangeEmail(Project.NameKey project, Change.Id changeId) {
return changeEmailFactory.create(
args.newChangeData(project, changeId), revertedChangeEmailDecorator);
}
- public OutgoingEmailNew createEmail(ChangeEmailNew changeEmail) {
+ public OutgoingEmail createEmail(ChangeEmail changeEmail) {
return outgoingEmailFactory.create("revert", changeEmail);
}
}
+
+ public static class StartReviewChangeEmailFactories {
+ private final EmailArguments args;
+ private final ChangeEmailFactory changeEmailFactory;
+ private final OutgoingEmailFactory outgoingEmailFactory;
+
+ @Inject
+ StartReviewChangeEmailFactories(
+ EmailArguments args,
+ ChangeEmailFactory changeEmailFactory,
+ OutgoingEmailFactory outgoingEmailFactory) {
+ this.args = args;
+ this.changeEmailFactory = changeEmailFactory;
+ this.outgoingEmailFactory = outgoingEmailFactory;
+ }
+
+ public StartReviewChangeEmailDecorator createStartReviewChangeEmail() {
+ return new StartReviewChangeEmailDecorator();
+ }
+
+ public ChangeEmail createChangeEmail(
+ Project.NameKey project,
+ Change.Id changeId,
+ StartReviewChangeEmailDecorator startReviewChangeEmailDecorator) {
+ return changeEmailFactory.create(
+ args.newChangeData(project, changeId), startReviewChangeEmailDecorator);
+ }
+
+ public OutgoingEmail createEmail(ChangeEmail changeEmail) {
+ return outgoingEmailFactory.create("newchange", changeEmail);
+ }
+ }
+
+ public static class AddKeyEmailFactories {
+ private final AddKeyEmailDecoratorFactory addKeyEmailDecoratorFactory;
+ private final OutgoingEmailFactory outgoingEmailFactory;
+
+ @Inject
+ AddKeyEmailFactories(
+ AddKeyEmailDecoratorFactory addKeyEmailDecoratorFactory,
+ OutgoingEmailFactory outgoingEmailFactory) {
+ this.addKeyEmailDecoratorFactory = addKeyEmailDecoratorFactory;
+ this.outgoingEmailFactory = outgoingEmailFactory;
+ }
+
+ public OutgoingEmail createEmail(IdentifiedUser user, AccountSshKey sshKey) {
+ return outgoingEmailFactory.create(
+ "addkey", addKeyEmailDecoratorFactory.create(user, sshKey));
+ }
+
+ public OutgoingEmail createEmail(IdentifiedUser user, List<String> gpgKeys) {
+ return outgoingEmailFactory.create(
+ "addkey", addKeyEmailDecoratorFactory.create(user, gpgKeys));
+ }
+ }
+
+ public static class DeleteKeyEmailFactories {
+ private final DeleteKeyEmailDecoratorFactory deleteKeyEmailDecoratorFactory;
+ private final OutgoingEmailFactory outgoingEmailFactory;
+
+ @Inject
+ DeleteKeyEmailFactories(
+ DeleteKeyEmailDecoratorFactory deleteKeyEmailDecoratorFactory,
+ OutgoingEmailFactory outgoingEmailFactory) {
+ this.deleteKeyEmailDecoratorFactory = deleteKeyEmailDecoratorFactory;
+ this.outgoingEmailFactory = outgoingEmailFactory;
+ }
+
+ public OutgoingEmail createEmail(IdentifiedUser user, AccountSshKey sshKey) {
+ return outgoingEmailFactory.create(
+ "deletekey", deleteKeyEmailDecoratorFactory.create(user, sshKey));
+ }
+
+ public OutgoingEmail createEmail(IdentifiedUser user, List<String> gpgKeyFingerprints) {
+ return outgoingEmailFactory.create(
+ "deletekey", deleteKeyEmailDecoratorFactory.create(user, gpgKeyFingerprints));
+ }
+ }
+
+ public static class HttpPasswordUpdateEmailFactory {
+ private final HttpPasswordUpdateEmailDecoratorFactory httpPasswordUpdateEmailDecoratorFactory;
+ private final OutgoingEmailFactory outgoingEmailFactory;
+
+ @Inject
+ HttpPasswordUpdateEmailFactory(
+ HttpPasswordUpdateEmailDecoratorFactory httpPasswordUpdateEmailDecoratorFactory,
+ OutgoingEmailFactory outgoingEmailFactory) {
+ this.httpPasswordUpdateEmailDecoratorFactory = httpPasswordUpdateEmailDecoratorFactory;
+ this.outgoingEmailFactory = outgoingEmailFactory;
+ }
+
+ public OutgoingEmail createEmail(IdentifiedUser user, String operation) {
+ return outgoingEmailFactory.create(
+ "HttpPasswordUpdate", httpPasswordUpdateEmailDecoratorFactory.create(user, operation));
+ }
+ }
+
+ public static class InboundEmailRejectionEmailFactory {
+ private final OutgoingEmailFactory outgoingEmailFactory;
+
+ @Inject
+ InboundEmailRejectionEmailFactory(OutgoingEmailFactory outgoingEmailFactory) {
+ this.outgoingEmailFactory = outgoingEmailFactory;
+ }
+
+ public OutgoingEmail createEmail(Address to, String threadId, InboundEmailError reason) {
+ return outgoingEmailFactory.create(
+ "error", new InboundEmailRejectionEmailDecorator(to, threadId, reason));
+ }
+ }
+
+ public static class RegisterNewEmailFactories {
+ private final RegisterNewEmailDecoratorFactory registerEmailDecoratorFactory;
+ private final OutgoingEmailFactory outgoingEmailFactory;
+
+ @Inject
+ RegisterNewEmailFactories(
+ RegisterNewEmailDecoratorFactory registerEmailDecoratorFactory,
+ OutgoingEmailFactory outgoingEmailFactory) {
+ this.registerEmailDecoratorFactory = registerEmailDecoratorFactory;
+ this.outgoingEmailFactory = outgoingEmailFactory;
+ }
+
+ public RegisterNewEmailDecorator createRegisterNewEmail(String address) {
+ return registerEmailDecoratorFactory.create(address);
+ }
+
+ public OutgoingEmail createEmail(RegisterNewEmailDecorator registerEmail) {
+ return outgoingEmailFactory.create("registernewemail", registerEmail);
+ }
+ }
}
diff --git a/java/com/google/gerrit/server/mail/EmailTokenVerifier.java b/java/com/google/gerrit/server/mail/EmailTokenVerifier.java
index ead4c06..ea55a24 100644
--- a/java/com/google/gerrit/server/mail/EmailTokenVerifier.java
+++ b/java/com/google/gerrit/server/mail/EmailTokenVerifier.java
@@ -16,9 +16,8 @@
import com.google.gerrit.entities.Account;
import com.google.gerrit.server.account.AuthRequest;
-import com.google.gerrit.server.mail.send.RegisterNewEmailSender;
-/** Verifies the token sent by {@link RegisterNewEmailSender}. */
+/** Verifies the token used by new email address verification process. */
public interface EmailTokenVerifier {
/**
* Construct a token to verify an email address for a user.
diff --git a/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java b/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java
index 36e801b..82ffda2 100644
--- a/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java
+++ b/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java
@@ -21,14 +21,13 @@
import com.google.gerrit.entities.Account;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.config.AuthConfig;
-import com.google.gerrit.server.mail.send.RegisterNewEmailSender;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-/** Verifies the token sent by {@link RegisterNewEmailSender}. */
+/** Verifies the token used by new email address verification process. */
@Singleton
public class SignedTokenEmailTokenVerifier implements EmailTokenVerifier {
private final SignedToken emailRegistrationToken;
diff --git a/java/com/google/gerrit/server/mail/receive/MailProcessor.java b/java/com/google/gerrit/server/mail/receive/MailProcessor.java
index 93da997..7fe0515 100644
--- a/java/com/google/gerrit/server/mail/receive/MailProcessor.java
+++ b/java/com/google/gerrit/server/mail/receive/MailProcessor.java
@@ -56,10 +56,11 @@
import com.google.gerrit.server.change.EmailReviewComments;
import com.google.gerrit.server.config.UrlFormatter;
import com.google.gerrit.server.extensions.events.CommentAdded;
+import com.google.gerrit.server.mail.EmailModule.InboundEmailRejectionEmailFactory;
import com.google.gerrit.server.mail.MailFilter;
-import com.google.gerrit.server.mail.send.InboundEmailRejectionSender;
-import com.google.gerrit.server.mail.send.InboundEmailRejectionSender.InboundEmailError;
+import com.google.gerrit.server.mail.send.InboundEmailRejectionEmailDecorator.InboundEmailError;
import com.google.gerrit.server.mail.send.MessageIdGenerator;
+import com.google.gerrit.server.mail.send.OutgoingEmail;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.gerrit.server.query.change.ChangeData;
@@ -108,7 +109,7 @@
CommentForValidation.CommentType.INLINE_COMMENT);
private final Emails emails;
- private final InboundEmailRejectionSender.Factory emailRejectionSender;
+ private final InboundEmailRejectionEmailFactory inboundEmailRejectionEmailFactory;
private final RetryHelper retryHelper;
private final ChangeMessagesUtil changeMessagesUtil;
private final CommentsUtil commentsUtil;
@@ -127,7 +128,7 @@
@Inject
public MailProcessor(
Emails emails,
- InboundEmailRejectionSender.Factory emailRejectionSender,
+ InboundEmailRejectionEmailFactory inboundEmailRejectionEmailFactory,
RetryHelper retryHelper,
ChangeMessagesUtil changeMessagesUtil,
CommentsUtil commentsUtil,
@@ -143,7 +144,7 @@
PluginSetContext<CommentValidator> commentValidators,
MessageIdGenerator messageIdGenerator) {
this.emails = emails;
- this.emailRejectionSender = emailRejectionSender;
+ this.inboundEmailRejectionEmailFactory = inboundEmailRejectionEmailFactory;
this.retryHelper = retryHelper;
this.changeMessagesUtil = changeMessagesUtil;
this.commentsUtil = commentsUtil;
@@ -228,10 +229,10 @@
private void sendRejectionEmail(MailMessage message, InboundEmailError reason) {
try {
- InboundEmailRejectionSender emailSender =
- emailRejectionSender.create(message.from(), message.id(), reason);
- emailSender.setMessageId(messageIdGenerator.fromMailMessage(message));
- emailSender.send();
+ OutgoingEmail email =
+ inboundEmailRejectionEmailFactory.createEmail(message.from(), message.id(), reason);
+ email.setMessageId(messageIdGenerator.fromMailMessage(message));
+ email.send();
} catch (Exception e) {
logger.atSevere().withCause(e).log("Cannot send email to warn for an error");
}
diff --git a/java/com/google/gerrit/server/mail/send/AbandonedChangeEmailDecorator.java b/java/com/google/gerrit/server/mail/send/AbandonedChangeEmailDecorator.java
index 2d25eba..3ec2b35 100644
--- a/java/com/google/gerrit/server/mail/send/AbandonedChangeEmailDecorator.java
+++ b/java/com/google/gerrit/server/mail/send/AbandonedChangeEmailDecorator.java
@@ -18,12 +18,12 @@
import com.google.gerrit.extensions.api.changes.RecipientType;
/** Send notice about a change being abandoned by its owner. */
-public class AbandonedChangeEmailDecorator implements ChangeEmailNew.ChangeEmailDecorator {
- private ChangeEmailNew changeEmail;
- private OutgoingEmailNew email;
+public class AbandonedChangeEmailDecorator implements ChangeEmail.ChangeEmailDecorator {
+ private ChangeEmail changeEmail;
+ private OutgoingEmail email;
@Override
- public void init(OutgoingEmailNew email, ChangeEmailNew changeEmail) {
+ public void init(OutgoingEmail email, ChangeEmail changeEmail) {
this.email = email;
this.changeEmail = changeEmail;
changeEmail.markAsReply();
diff --git a/java/com/google/gerrit/server/mail/send/AddKeyEmailDecorator.java b/java/com/google/gerrit/server/mail/send/AddKeyEmailDecorator.java
new file mode 100644
index 0000000..61e73e3
--- /dev/null
+++ b/java/com/google/gerrit/server/mail/send/AddKeyEmailDecorator.java
@@ -0,0 +1,114 @@
+// Copyright (C) 2015 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.mail.send;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
+import com.google.common.base.Joiner;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.api.changes.RecipientType;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountSshKey;
+import com.google.gerrit.server.mail.send.OutgoingEmail.EmailDecorator;
+import java.util.List;
+
+/** Informs a user by email about the addition of an SSH or GPG key to their account. */
+@AutoFactory
+public class AddKeyEmailDecorator implements EmailDecorator {
+ private OutgoingEmail email;
+
+ private final IdentifiedUser user;
+ private final AccountSshKey sshKey;
+ private final List<String> gpgKeys;
+ private final MessageIdGenerator messageIdGenerator;
+
+ public AddKeyEmailDecorator(
+ @Provided MessageIdGenerator messageIdGenerator, IdentifiedUser user, AccountSshKey sshKey) {
+ this.messageIdGenerator = messageIdGenerator;
+ this.user = user;
+ this.sshKey = sshKey;
+ this.gpgKeys = null;
+ }
+
+ public AddKeyEmailDecorator(
+ @Provided MessageIdGenerator messageIdGenerator, IdentifiedUser user, List<String> gpgKeys) {
+ this.messageIdGenerator = messageIdGenerator;
+ this.user = user;
+ this.sshKey = null;
+ this.gpgKeys = gpgKeys;
+ }
+
+ @Override
+ public void init(OutgoingEmail email) {
+ this.email = email;
+
+ email.setHeader(
+ "Subject", String.format("[Gerrit Code Review] New %s Keys Added", getKeyType()));
+ email.setMessageId(messageIdGenerator.fromAccountUpdate(user.getAccountId()));
+ email.addByAccountId(RecipientType.TO, user.getAccountId());
+ }
+
+ @Override
+ public boolean shouldSendMessage() {
+ if (sshKey == null && (gpgKeys == null || gpgKeys.isEmpty())) {
+ // Don't email if no keys were added.
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public void populateEmailContent() {
+ email.addSoyEmailDataParam("email", getEmail());
+ email.addSoyEmailDataParam("gpgKeys", getGpgKeys());
+ email.addSoyEmailDataParam("keyType", getKeyType());
+ email.addSoyEmailDataParam("sshKey", getSshKey());
+ email.addSoyEmailDataParam("userNameEmail", email.getUserNameEmailFor(user.getAccountId()));
+ email.addSoyEmailDataParam("sshKeysSettingsUrl", email.getSettingsUrl("ssh-keys"));
+ email.addSoyEmailDataParam("gpgKeysSettingsUrl", email.getSettingsUrl("gpg-keys"));
+
+ email.appendText(email.textTemplate("AddKey"));
+ if (email.useHtml()) {
+ email.appendHtml(email.soyHtmlTemplate("AddKeyHtml"));
+ }
+ }
+
+ private String getEmail() {
+ return user.getAccount().preferredEmail();
+ }
+
+ private String getKeyType() {
+ if (sshKey != null) {
+ return "SSH";
+ } else if (gpgKeys != null) {
+ return "GPG";
+ }
+ return "Unknown";
+ }
+
+ @Nullable
+ private String getSshKey() {
+ return (sshKey != null) ? sshKey.sshPublicKey() + "\n" : null;
+ }
+
+ @Nullable
+ private String getGpgKeys() {
+ if (gpgKeys != null) {
+ return Joiner.on("\n").join(gpgKeys);
+ }
+ return null;
+ }
+}
diff --git a/java/com/google/gerrit/server/mail/send/AddKeySender.java b/java/com/google/gerrit/server/mail/send/AddKeySender.java
deleted file mode 100644
index c7b1f2a..0000000
--- a/java/com/google/gerrit/server/mail/send/AddKeySender.java
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright (C) 2015 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.mail.send;
-
-import com.google.common.base.Joiner;
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.exceptions.EmailException;
-import com.google.gerrit.extensions.api.changes.RecipientType;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountSshKey;
-import com.google.inject.assistedinject.Assisted;
-import com.google.inject.assistedinject.AssistedInject;
-import java.util.List;
-
-/** Sender that informs a user by email about the addition of an SSH or GPG key to their account. */
-public class AddKeySender extends OutgoingEmail {
- public interface Factory {
- AddKeySender create(IdentifiedUser user, AccountSshKey sshKey);
-
- AddKeySender create(IdentifiedUser user, List<String> gpgKey);
- }
-
- private final IdentifiedUser user;
- private final AccountSshKey sshKey;
- private final List<String> gpgKeys;
- private final MessageIdGenerator messageIdGenerator;
-
- @AssistedInject
- public AddKeySender(
- EmailArguments args,
- MessageIdGenerator messageIdGenerator,
- @Assisted IdentifiedUser user,
- @Assisted AccountSshKey sshKey) {
- super(args, "addkey");
- this.messageIdGenerator = messageIdGenerator;
- this.user = user;
- this.sshKey = sshKey;
- this.gpgKeys = null;
- }
-
- @AssistedInject
- public AddKeySender(
- EmailArguments args,
- MessageIdGenerator messageIdGenerator,
- @Assisted IdentifiedUser user,
- @Assisted List<String> gpgKeys) {
- super(args, "addkey");
- this.messageIdGenerator = messageIdGenerator;
- this.user = user;
- this.sshKey = null;
- this.gpgKeys = gpgKeys;
- }
-
- @Override
- protected void init() throws EmailException {
- super.init();
- setHeader("Subject", String.format("[Gerrit Code Review] New %s Keys Added", getKeyType()));
- setMessageId(messageIdGenerator.fromAccountUpdate(user.getAccountId()));
- addByAccountId(RecipientType.TO, user.getAccountId());
- }
-
- @Override
- protected boolean shouldSendMessage() {
- if (sshKey == null && (gpgKeys == null || gpgKeys.isEmpty())) {
- // Don't email if no keys were added.
- return false;
- }
-
- return true;
- }
-
- @Override
- protected void format() throws EmailException {
- appendText(textTemplate("AddKey"));
- if (useHtml()) {
- appendHtml(soyHtmlTemplate("AddKeyHtml"));
- }
- }
-
- @Override
- protected void populateEmailContent() throws EmailException {
- super.populateEmailContent();
- addSoyEmailDataParam("email", getEmail());
- addSoyEmailDataParam("gpgKeys", getGpgKeys());
- addSoyEmailDataParam("keyType", getKeyType());
- addSoyEmailDataParam("sshKey", getSshKey());
- addSoyEmailDataParam("userNameEmail", getUserNameEmailFor(user.getAccountId()));
- }
-
- private String getEmail() {
- return user.getAccount().preferredEmail();
- }
-
- private String getKeyType() {
- if (sshKey != null) {
- return "SSH";
- } else if (gpgKeys != null) {
- return "GPG";
- }
- return "Unknown";
- }
-
- @Nullable
- private String getSshKey() {
- return (sshKey != null) ? sshKey.sshPublicKey() + "\n" : null;
- }
-
- @Nullable
- private String getGpgKeys() {
- if (gpgKeys != null) {
- return Joiner.on("\n").join(gpgKeys);
- }
- return null;
- }
-}
diff --git a/java/com/google/gerrit/server/mail/send/AttentionSetChangeEmailDecorator.java b/java/com/google/gerrit/server/mail/send/AttentionSetChangeEmailDecorator.java
index 38ec52f..9f1a5a8 100644
--- a/java/com/google/gerrit/server/mail/send/AttentionSetChangeEmailDecorator.java
+++ b/java/com/google/gerrit/server/mail/send/AttentionSetChangeEmailDecorator.java
@@ -16,7 +16,7 @@
import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.api.changes.RecipientType;
-import com.google.gerrit.server.mail.send.ChangeEmailNew.ChangeEmailDecorator;
+import com.google.gerrit.server.mail.send.ChangeEmail.ChangeEmailDecorator;
/** Base class for Attention Set email senders */
public final class AttentionSetChangeEmailDecorator implements ChangeEmailDecorator {
@@ -25,8 +25,8 @@
USER_REMOVED
}
- private OutgoingEmailNew email;
- private ChangeEmailNew changeEmail;
+ private OutgoingEmail email;
+ private ChangeEmail changeEmail;
private Account.Id attentionSetUser;
private String reason;
@@ -45,7 +45,7 @@
}
@Override
- public void init(OutgoingEmailNew email, ChangeEmailNew changeEmail) {
+ public void init(OutgoingEmail email, ChangeEmail changeEmail) {
this.email = email;
this.changeEmail = changeEmail;
changeEmail.markAsReply();
diff --git a/java/com/google/gerrit/server/mail/send/BranchEmailUtils.java b/java/com/google/gerrit/server/mail/send/BranchEmailUtils.java
index d28311f..e26f83f 100644
--- a/java/com/google/gerrit/server/mail/send/BranchEmailUtils.java
+++ b/java/com/google/gerrit/server/mail/send/BranchEmailUtils.java
@@ -24,8 +24,6 @@
/** Contains utils for email notification related to the events on project+branch. */
class BranchEmailUtils {
-
- // TODO: Remove after all usages are migrated to OutgoingEmailNew.
/** Set a reasonable list id so that filters can be used to sort messages. */
static void setListIdHeader(OutgoingEmail email, BranchNameKey branch) {
email.setHeader(
@@ -36,17 +34,6 @@
}
}
- /** Set a reasonable list id so that filters can be used to sort messages. */
- static void setListIdHeader(OutgoingEmailNew email, BranchNameKey branch) {
- email.setHeader(
- "List-Id",
- "<gerrit-" + branch.project().get().replace('/', '-') + "." + email.getGerritHost() + ">");
- if (email.getSettingsUrl() != null) {
- email.setHeader("List-Unsubscribe", "<" + email.getSettingsUrl() + ">");
- }
- }
-
- // TODO: Remove after all usages are migrated to OutgoingEmailNew.
/** Add branch information to soy template params. */
static void addBranchData(OutgoingEmail email, EmailArguments args, BranchNameKey branch) {
String projectName = branch.project().get();
@@ -70,29 +57,6 @@
email.addFooter(MailHeader.BRANCH.withDelimiter() + branch.shortName());
}
- /** Add branch information to soy template params. */
- static void addBranchData(OutgoingEmailNew email, EmailArguments args, BranchNameKey branch) {
- String projectName = branch.project().get();
- email.addSoyParam("projectName", projectName);
- // shortProjectName is the project name with the path abbreviated.
- email.addSoyParam("shortProjectName", getShortProjectName(projectName));
-
- // instanceAndProjectName is the instance's name followed by the abbreviated project path
- email.addSoyParam(
- "instanceAndProjectName",
- getInstanceAndProjectName(args.instanceNameProvider.get(), projectName));
- email.addSoyParam("addInstanceNameInSubject", args.addInstanceNameInSubject);
-
- email.addSoyEmailDataParam("sshHost", getSshHost(email.getGerritHost(), args.sshAddresses));
-
- Map<String, String> branchData = new HashMap<>();
- branchData.put("shortName", branch.shortName());
- email.addSoyParam("branch", branchData);
-
- email.addFooter(MailHeader.PROJECT.withDelimiter() + branch.project().get());
- email.addFooter(MailHeader.BRANCH.withDelimiter() + branch.shortName());
- }
-
@Nullable
private static String getSshHost(String gerritHost, List<String> sshAddresses) {
String host = Iterables.getFirst(sshAddresses, null);
diff --git a/java/com/google/gerrit/server/mail/send/ChangeEmail.java b/java/com/google/gerrit/server/mail/send/ChangeEmail.java
index bb9f96a..0b909cd 100644
--- a/java/com/google/gerrit/server/mail/send/ChangeEmail.java
+++ b/java/com/google/gerrit/server/mail/send/ChangeEmail.java
@@ -17,6 +17,8 @@
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.gerrit.server.util.AttentionSetUtil.additionsOnly;
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -33,7 +35,6 @@
import com.google.gerrit.entities.Patch;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.PatchSetInfo;
-import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.EmailException;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
@@ -57,8 +58,6 @@
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.ChangeData;
import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
import java.text.MessageFormat;
import java.time.Instant;
import java.util.Collection;
@@ -70,7 +69,6 @@
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.stream.Collectors;
-import org.apache.http.client.utils.URIBuilder;
import org.apache.james.mime4j.dom.field.FieldName;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.internal.JGitText;
@@ -79,31 +77,59 @@
import org.eclipse.jgit.util.RawParseUtils;
import org.eclipse.jgit.util.TemporaryBuffer;
-/** Sends an email to one or more interested parties. */
-public abstract class ChangeEmail extends OutgoingEmail {
+// TODO: Remove ChangeEmail and rename this class once all usages are migrated to ChangeEmailNew.
+/** Populates an email for change related notifications. */
+@AutoFactory
+public final class ChangeEmail implements OutgoingEmail.EmailDecorator {
+
+ /** Implementations of params interface populate details specific to the notification type. */
+ public interface ChangeEmailDecorator {
+ /**
+ * Stores the reference to the {@link OutgoingEmail} and {@link ChangeEmail} for the subsequent
+ * calls.
+ *
+ * <p>Both init and populateEmailContent can be called multiply times in case of retries. Init
+ * is therefore responsible for clearing up any changes which are not idempotent and
+ * initializing data for use in populateEmailContent.
+ *
+ * <p>Can be used to adjust any of the behaviour of the {@link
+ * ChangeEmail#populateEmailContent}.
+ */
+ void init(OutgoingEmail email, ChangeEmail changeEmail) throws EmailException;
+
+ /**
+ * Populate headers, recipients and body of the email.
+ *
+ * <p>Method operates on the email provided in the init method.
+ *
+ * <p>By default, all the contents and parameters of the email should be set in this method.
+ */
+ void populateEmailContent() throws EmailException;
+
+ /** If returns false email is not sent to any recipients. */
+ default boolean shouldSendMessage() throws EmailException {
+ return true;
+ }
+ }
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- protected static ChangeData newChangeData(
- EmailArguments ea, Project.NameKey project, Change.Id id) {
- return ea.changeDataFactory.create(project, id);
- }
-
- protected static ChangeData newChangeData(
- EmailArguments ea, Project.NameKey project, Change.Id id, ObjectId metaId) {
- return ea.changeDataFactory.create(ea.changeNotesFactory.createChecked(project, id, metaId));
- }
-
+ // Available after construction
+ private final EmailArguments args;
private final Set<Account.Id> currentAttentionSet;
private final Change change;
private final ChangeData changeData;
+ private final BranchNameKey branch;
+ private final ChangeEmailDecorator changeEmailDecorator;
+
+ // Available after init or after being explicitly set.
+ private OutgoingEmail email;
private ListMultimap<Account.Id, String> stars;
private PatchSet patchSet;
private PatchSetInfo patchSetInfo;
private String changeMessage;
+ private String changeMessageThreadId;
private Instant timestamp;
- private BranchNameKey branch;
-
private ProjectState projectState;
private Set<Account.Id> authors;
private boolean emailOnlyAuthors;
@@ -112,15 +138,24 @@
private Set<Account.Id> watcherAccounts = new HashSet<>();
// Watcher can only be an email if it's specified in notify section of ProjectConfig.
private Set<Address> watcherEmails = new HashSet<>();
+ private boolean isThreadReply = false;
- protected ChangeEmail(EmailArguments args, String messageClass, ChangeData changeData) {
- super(args, messageClass);
+ public ChangeEmail(
+ @Provided EmailArguments args,
+ ChangeData changeData,
+ ChangeEmailDecorator changeEmailDecorator) {
+ this.args = args;
this.changeData = changeData;
change = changeData.change();
emailOnlyAuthors = false;
emailOnlyAttentionSetIfEnabled = true;
currentAttentionSet = getAttentionSet();
branch = changeData.change().getDest();
+ this.changeEmailDecorator = changeEmailDecorator;
+ }
+
+ public void markAsReply() {
+ isThreadReply = true;
}
public Change getChange() {
@@ -131,6 +166,7 @@
return changeData;
}
+ @Nullable
public Instant getTimestamp() {
return timestamp;
}
@@ -139,6 +175,7 @@
patchSet = ps;
}
+ @Nullable
public PatchSet getPatchSet() {
return patchSet;
}
@@ -157,39 +194,24 @@
emailOnlyAttentionSetIfEnabled = value;
}
- /** Format the message body by calling {@link #appendText(String)}. */
@Override
- protected void format() throws EmailException {
- if (useHtml()) {
- appendHtml(soyHtmlTemplate("ChangeHeaderHtml"));
- }
- appendText(textTemplate("ChangeHeader"));
- formatChange();
- appendText(textTemplate("ChangeFooter"));
- if (useHtml()) {
- appendHtml(soyHtmlTemplate("ChangeFooterHtml"));
- }
- formatFooter();
+ public boolean shouldSendMessage() throws EmailException {
+ return changeEmailDecorator.shouldSendMessage();
}
- /** Format the message body by calling {@link #appendText(String)}. */
- protected abstract void formatChange() throws EmailException;
-
- /**
- * Format the message footer by calling {@link #appendText(String)}.
- *
- * @throws EmailException if an error occurred.
- */
- protected void formatFooter() throws EmailException {}
-
- /** Setup the message headers and envelope (TO, CC, BCC). */
@Override
- protected void init() throws EmailException {
- super.init();
- if (getFrom() != null) {
+ public void init(OutgoingEmail email) throws EmailException {
+ this.email = email;
+
+ changeMessageThreadId =
+ String.format(
+ "<gerrit.%s.%s@%s>",
+ change.getCreatedOn().toEpochMilli(), change.getKey().get(), email.getGerritHost());
+
+ if (email.getFrom() != null) {
// Is the from user in an email squelching group?
try {
- args.permissionBackend.absentUser(getFrom()).check(GlobalPermission.EMAIL_REVIEWERS);
+ args.permissionBackend.absentUser(email.getFrom()).check(GlobalPermission.EMAIL_REVIEWERS);
} catch (AuthException | PermissionBackendException e) {
emailOnlyAuthors = true;
}
@@ -210,7 +232,7 @@
}
if (patchSet != null) {
- setHeader(MailHeader.PATCH_SET.fieldName(), patchSet.number() + "");
+ email.setHeader(MailHeader.PATCH_SET.fieldName(), patchSet.number() + "");
if (patchSetInfo == null) {
try {
patchSetInfo = args.patchSetInfoFactory.get(changeData.notes(), patchSet.id());
@@ -226,32 +248,34 @@
throw new EmailException("Failed to load stars for change " + change.getChangeId(), e);
}
- BranchEmailUtils.setListIdHeader(this, branch);
+ BranchEmailUtils.setListIdHeader(email, branch);
if (timestamp != null) {
- setHeader(FieldName.DATE, timestamp);
+ email.setHeader(FieldName.DATE, timestamp);
}
- setHeader(MailHeader.CHANGE_ID.fieldName(), "" + change.getKey().get());
- setHeader(MailHeader.CHANGE_NUMBER.fieldName(), "" + change.getChangeId());
- setHeader(MailHeader.PROJECT.fieldName(), "" + change.getProject());
+ email.setHeader(MailHeader.CHANGE_ID.fieldName(), "" + change.getKey().get());
+ email.setHeader(MailHeader.CHANGE_NUMBER.fieldName(), "" + change.getChangeId());
+ email.setHeader(MailHeader.PROJECT.fieldName(), "" + change.getProject());
setChangeUrlHeader();
setCommitIdHeader();
+
+ changeEmailDecorator.init(email, this);
}
private void setChangeUrlHeader() {
final String u = getChangeUrl();
if (u != null) {
- setHeader(MailHeader.CHANGE_URL.fieldName(), "<" + u + ">");
+ email.setHeader(MailHeader.CHANGE_URL.fieldName(), "<" + u + ">");
}
}
private void setCommitIdHeader() {
if (patchSet != null) {
- setHeader(MailHeader.COMMIT.fieldName(), patchSet.commitId().name());
+ email.setHeader(MailHeader.COMMIT.fieldName(), patchSet.commitId().name());
}
}
private void setChangeSubjectHeader() {
- setHeader(FieldName.SUBJECT, textTemplate("ChangeSubject"));
+ email.setHeader(FieldName.SUBJECT, email.textTemplate("ChangeSubject"));
}
private int getInsertionsCount() {
@@ -275,25 +299,19 @@
*/
@Nullable
public String getChangeUrl() {
- Optional<String> changeUrl =
- args.urlFormatter.get().getChangeViewUrl(change.getProject(), change.getId());
- if (!changeUrl.isPresent()) return null;
- try {
- URI uri = new URIBuilder(changeUrl.get()).addParameter("usp", "email").build();
- return uri.toString();
- } catch (URISyntaxException e) {
- return null;
- }
+ return args.urlFormatter
+ .get()
+ .getChangeViewUrl(change.getProject(), change.getId())
+ .map(EmailArguments::addUspParam)
+ .orElse(null);
}
- public String getChangeMessageThreadId() {
- return "<gerrit."
- + change.getCreatedOn().toEpochMilli()
- + "."
- + change.getKey().get()
- + "@"
- + getGerritHost()
- + ">";
+ /** Sets headers for conversation grouping */
+ private void setThreadHeaders() {
+ if (isThreadReply) {
+ email.setHeader("In-Reply-To", changeMessageThreadId);
+ }
+ email.setHeader("References", changeMessageThreadId);
}
/** Get the text of the "cover letter". */
@@ -390,19 +408,19 @@
/** TO or CC all vested parties (change owner, patch set uploader, author). */
public void addAuthors(RecipientType rt) {
for (Account.Id id : getAuthors()) {
- addByAccountId(rt, id);
+ email.addByAccountId(rt, id);
}
}
/** BCC any user who has starred this change. */
public void bccStarredBy() {
- if (!NotifyHandling.ALL.equals(getNotify().handling())) {
+ if (!NotifyHandling.ALL.equals(email.getNotify().handling())) {
return;
}
for (Map.Entry<Account.Id, Collection<String>> e : stars.asMap().entrySet()) {
if (e.getValue().contains(StarredChangesUtil.DEFAULT_LABEL)) {
- super.addByAccountId(RecipientType.BCC, e.getKey());
+ email.addByAccountId(RecipientType.BCC, e.getKey());
}
}
}
@@ -431,17 +449,17 @@
private void addWatchers(RecipientType type, WatcherList watcherList) {
watcherAccounts.addAll(watcherList.accounts);
for (Account.Id user : watcherList.accounts) {
- addByAccountId(type, user);
+ email.addByAccountId(type, user);
}
watcherEmails.addAll(watcherList.emails);
for (Address addr : watcherList.emails) {
- addByEmail(type, addr);
+ email.addByEmail(type, addr);
}
}
private final Watchers getWatchers(NotifyType type, boolean includeWatchersFromNotifyConfig) {
- if (!NotifyHandling.ALL.equals(getNotify().handling())) {
+ if (!NotifyHandling.ALL.equals(email.getNotify().handling())) {
return new Watchers();
}
@@ -451,14 +469,14 @@
/** Any user who has published comments on this change. */
public void ccAllApprovals() {
- if (!NotifyHandling.ALL.equals(getNotify().handling())
- && !NotifyHandling.OWNER_REVIEWERS.equals(getNotify().handling())) {
+ if (!NotifyHandling.ALL.equals(email.getNotify().handling())
+ && !NotifyHandling.OWNER_REVIEWERS.equals(email.getNotify().handling())) {
return;
}
try {
for (Account.Id id : changeData.reviewers().all()) {
- addByAccountId(RecipientType.CC, id);
+ email.addByAccountId(RecipientType.CC, id);
}
} catch (StorageException err) {
logger.atWarning().withCause(err).log("Cannot CC users that reviewed updated change");
@@ -467,14 +485,14 @@
/** Users who were added as reviewers to this change. */
public void ccExistingReviewers() {
- if (!NotifyHandling.ALL.equals(getNotify().handling())
- && !NotifyHandling.OWNER_REVIEWERS.equals(getNotify().handling())) {
+ if (!NotifyHandling.ALL.equals(email.getNotify().handling())
+ && !NotifyHandling.OWNER_REVIEWERS.equals(email.getNotify().handling())) {
return;
}
try {
for (Account.Id id : changeData.reviewers().byState(ReviewerStateInternal.REVIEWER)) {
- addByAccountId(RecipientType.CC, id);
+ email.addByAccountId(RecipientType.CC, id);
}
} catch (StorageException err) {
logger.atWarning().withCause(err).log("Cannot CC users that commented on updated change");
@@ -482,7 +500,7 @@
}
@Override
- protected boolean isRecipientAllowed(Address addr) throws PermissionBackendException {
+ public boolean isRecipientAllowed(Address addr) throws PermissionBackendException {
if (!projectState.statePermitsRead()) {
return false;
}
@@ -503,7 +521,7 @@
}
@Override
- protected boolean isRecipientAllowed(Account.Id to) throws PermissionBackendException {
+ public boolean isRecipientAllowed(Account.Id to) throws PermissionBackendException {
if (!projectState.statePermitsRead()) {
return false;
}
@@ -521,7 +539,6 @@
return false;
}
}
-
return args.permissionBackend.absentUser(to).change(changeData).test(ChangePermission.READ);
}
@@ -532,7 +549,7 @@
}
Set<Account.Id> authors = new HashSet<>();
- switch (getNotify().handling()) {
+ switch (email.getNotify().handling()) {
case NONE:
break;
case ALL:
@@ -559,20 +576,20 @@
}
@Override
- protected void populateEmailContent() throws EmailException {
- super.populateEmailContent();
- BranchEmailUtils.addBranchData(this, args, branch);
+ public void populateEmailContent() throws EmailException {
+ BranchEmailUtils.addBranchData(email, args, branch);
+ setThreadHeaders();
- addSoyParam("changeId", change.getKey().get());
- addSoyParam("coverLetter", getCoverLetter());
- addSoyParam("fromName", getNameFor(getFrom()));
- addSoyParam("fromEmail", getNameEmailFor(getFrom()));
- addSoyParam("diffLines", getDiffTemplateData(getUnifiedDiff()));
+ email.addSoyParam("changeId", change.getKey().get());
+ email.addSoyParam("coverLetter", getCoverLetter());
+ email.addSoyParam("fromName", email.getNameFor(email.getFrom()));
+ email.addSoyParam("fromEmail", email.getNameEmailFor(email.getFrom()));
+ email.addSoyParam("diffLines", getDiffTemplateData(getUnifiedDiff()));
- addSoyEmailDataParam("unifiedDiff", getUnifiedDiff());
- addSoyEmailDataParam("changeDetail", getChangeDetail());
- addSoyEmailDataParam("changeUrl", getChangeUrl());
- addSoyEmailDataParam("includeDiff", getIncludeDiff());
+ email.addSoyEmailDataParam("unifiedDiff", getUnifiedDiff());
+ email.addSoyEmailDataParam("changeDetail", getChangeDetail());
+ email.addSoyEmailDataParam("changeUrl", getChangeUrl());
+ email.addSoyEmailDataParam("includeDiff", getIncludeDiff());
Map<String, String> changeData = new HashMap<>();
@@ -583,56 +600,66 @@
changeData.put("shortSubject", shortenSubject(subject));
changeData.put("shortOriginalSubject", shortenSubject(originalSubject));
- changeData.put("ownerName", getNameFor(change.getOwner()));
- changeData.put("ownerEmail", getNameEmailFor(change.getOwner()));
+ changeData.put("ownerName", email.getNameFor(change.getOwner()));
+ changeData.put("ownerEmail", email.getNameEmailFor(change.getOwner()));
changeData.put("changeNumber", Integer.toString(change.getChangeId()));
changeData.put(
"sizeBucket",
ChangeSizeBucket.getChangeSizeBucket(getInsertionsCount() + getDeletionsCount()));
- addSoyParam("change", changeData);
+ email.addSoyParam("change", changeData);
Map<String, Object> patchSetData = new HashMap<>();
patchSetData.put("patchSetId", patchSet.number());
patchSetData.put("refName", patchSet.refName());
- addSoyParam("patchSet", patchSetData);
+ email.addSoyParam("patchSet", patchSetData);
Map<String, Object> patchSetInfoData = new HashMap<>();
patchSetInfoData.put("authorName", patchSetInfo.getAuthor().getName());
patchSetInfoData.put("authorEmail", patchSetInfo.getAuthor().getEmail());
- addSoyParam("patchSetInfo", patchSetInfoData);
+ email.addSoyParam("patchSetInfo", patchSetInfoData);
- addFooter(MailHeader.CHANGE_ID.withDelimiter() + change.getKey().get());
- addFooter(MailHeader.CHANGE_NUMBER.withDelimiter() + change.getChangeId());
- addFooter(MailHeader.PATCH_SET.withDelimiter() + patchSet.number());
- addFooter(MailHeader.OWNER.withDelimiter() + getNameEmailFor(change.getOwner()));
+ email.addFooter(MailHeader.CHANGE_ID.withDelimiter() + change.getKey().get());
+ email.addFooter(MailHeader.CHANGE_NUMBER.withDelimiter() + change.getChangeId());
+ email.addFooter(MailHeader.PATCH_SET.withDelimiter() + patchSet.number());
+ email.addFooter(MailHeader.OWNER.withDelimiter() + email.getNameEmailFor(change.getOwner()));
for (String reviewer : getEmailsByState(ReviewerStateInternal.REVIEWER)) {
- addFooter(MailHeader.REVIEWER.withDelimiter() + reviewer);
+ email.addFooter(MailHeader.REVIEWER.withDelimiter() + reviewer);
}
for (String reviewer : getEmailsByState(ReviewerStateInternal.CC)) {
- addFooter(MailHeader.CC.withDelimiter() + reviewer);
+ email.addFooter(MailHeader.CC.withDelimiter() + reviewer);
}
for (Account.Id attentionUser : currentAttentionSet) {
- addFooter(MailHeader.ATTENTION.withDelimiter() + getNameEmailFor(attentionUser));
+ email.addFooter(MailHeader.ATTENTION.withDelimiter() + email.getNameEmailFor(attentionUser));
}
if (!currentAttentionSet.isEmpty()) {
// We need names rather than account ids / emails to make it user readable.
- addSoyParam(
+ email.addSoyParam(
"attentionSet",
- currentAttentionSet.stream().map(this::getNameFor).sorted().collect(toImmutableList()));
+ currentAttentionSet.stream().map(email::getNameFor).sorted().collect(toImmutableList()));
}
setChangeSubjectHeader();
- if (getNotify().handling().equals(NotifyHandling.OWNER_REVIEWERS)
- || getNotify().handling().equals(NotifyHandling.ALL)) {
+ if (email.getNotify().handling().equals(NotifyHandling.OWNER_REVIEWERS)
+ || email.getNotify().handling().equals(NotifyHandling.ALL)) {
try {
this.changeData.reviewersByEmail().byState(ReviewerStateInternal.CC).stream()
- .forEach(address -> addByEmail(RecipientType.CC, address));
+ .forEach(address -> email.addByEmail(RecipientType.CC, address));
this.changeData.reviewersByEmail().byState(ReviewerStateInternal.REVIEWER).stream()
- .forEach(address -> addByEmail(RecipientType.CC, address));
+ .forEach(address -> email.addByEmail(RecipientType.CC, address));
} catch (StorageException e) {
throw new EmailException("Failed to add unregistered CCs " + change.getChangeId(), e);
}
}
+
+ if (email.useHtml()) {
+ email.appendHtml(email.soyHtmlTemplate("ChangeHeaderHtml"));
+ }
+ email.appendText(email.textTemplate("ChangeHeader"));
+ changeEmailDecorator.populateEmailContent();
+ email.appendText(email.textTemplate("ChangeFooter"));
+ if (email.useHtml()) {
+ email.appendHtml(email.soyHtmlTemplate("ChangeFooterHtml"));
+ }
}
/**
@@ -650,7 +677,7 @@
Set<String> reviewers = new TreeSet<>();
try {
for (Account.Id who : changeData.reviewers().byState(state)) {
- reviewers.add(getNameEmailFor(who));
+ reviewers.add(email.getNameEmailFor(who));
}
} catch (StorageException e) {
logger.atWarning().withCause(e).log("Cannot get change reviewers");
diff --git a/java/com/google/gerrit/server/mail/send/ChangeEmailNew.java b/java/com/google/gerrit/server/mail/send/ChangeEmailNew.java
deleted file mode 100644
index 28dd3b1..0000000
--- a/java/com/google/gerrit/server/mail/send/ChangeEmailNew.java
+++ /dev/null
@@ -1,789 +0,0 @@
-// Copyright (C) 2016 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.mail.send;
-
-import static com.google.common.collect.ImmutableList.toImmutableList;
-import static com.google.gerrit.server.util.AttentionSetUtil.additionsOnly;
-
-import com.google.auto.factory.AutoFactory;
-import com.google.auto.factory.Provided;
-import com.google.common.base.Splitter;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ListMultimap;
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.entities.Account;
-import com.google.gerrit.entities.Address;
-import com.google.gerrit.entities.AttentionSetUpdate;
-import com.google.gerrit.entities.BranchNameKey;
-import com.google.gerrit.entities.Change;
-import com.google.gerrit.entities.ChangeSizeBucket;
-import com.google.gerrit.entities.NotifyConfig.NotifyType;
-import com.google.gerrit.entities.Patch;
-import com.google.gerrit.entities.PatchSet;
-import com.google.gerrit.entities.PatchSetInfo;
-import com.google.gerrit.exceptions.EmailException;
-import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.extensions.api.changes.NotifyHandling;
-import com.google.gerrit.extensions.api.changes.RecipientType;
-import com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailStrategy;
-import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.mail.MailHeader;
-import com.google.gerrit.server.StarredChangesUtil;
-import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.mail.send.ProjectWatch.Watchers;
-import com.google.gerrit.server.mail.send.ProjectWatch.Watchers.WatcherList;
-import com.google.gerrit.server.notedb.ReviewerStateInternal;
-import com.google.gerrit.server.patch.DiffNotAvailableException;
-import com.google.gerrit.server.patch.DiffOptions;
-import com.google.gerrit.server.patch.FilePathAdapter;
-import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
-import com.google.gerrit.server.patch.filediff.FileDiffOutput;
-import com.google.gerrit.server.permissions.ChangePermission;
-import com.google.gerrit.server.permissions.GlobalPermission;
-import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gerrit.server.project.ProjectState;
-import com.google.gerrit.server.query.change.ChangeData;
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.text.MessageFormat;
-import java.time.Instant;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.TreeSet;
-import java.util.stream.Collectors;
-import org.apache.http.client.utils.URIBuilder;
-import org.apache.james.mime4j.dom.field.FieldName;
-import org.eclipse.jgit.diff.DiffFormatter;
-import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.util.RawParseUtils;
-import org.eclipse.jgit.util.TemporaryBuffer;
-
-// TODO: Remove ChangeEmail and rename this class once all usages are migrated to ChangeEmailNew.
-/** Populates an email for change related notifications. */
-@AutoFactory
-public final class ChangeEmailNew implements OutgoingEmailNew.EmailDecorator {
-
- /** Implementations of params interface populate details specific to the notification type. */
- public interface ChangeEmailDecorator {
- /**
- * Stores the reference to the {@link OutgoingEmailNew} and {@link ChangeEmailNew} for the
- * subsequent calls.
- *
- * <p>Both init and populateEmailContent can be called multiply times in case of retries. Init
- * is therefore responsible for clearing up any changes which are not idempotent and
- * initializing data for use in populateEmailContent.
- *
- * <p>Can be used to adjust any of the behaviour of the {@link
- * ChangeEmailNew#populateEmailContent}.
- */
- void init(OutgoingEmailNew email, ChangeEmailNew changeEmail) throws EmailException;
-
- /**
- * Populate headers, recipients and body of the email.
- *
- * <p>Method operates on the email provided in the init method.
- *
- * <p>By default, all the contents and parameters of the email should be set in this method.
- */
- void populateEmailContent() throws EmailException;
-
- /** If returns false email is not sent to any recipients. */
- default boolean shouldSendMessage() throws EmailException {
- return true;
- }
- }
-
- private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
- // Available after construction
- private final EmailArguments args;
- private final Set<Account.Id> currentAttentionSet;
- private final Change change;
- private final ChangeData changeData;
- private final BranchNameKey branch;
- private final ChangeEmailDecorator changeEmailDecorator;
-
- // Available after init or after being explicitly set.
- private OutgoingEmailNew email;
- private ListMultimap<Account.Id, String> stars;
- private PatchSet patchSet;
- private PatchSetInfo patchSetInfo;
- private String changeMessage;
- private String changeMessageThreadId;
- private Instant timestamp;
- private ProjectState projectState;
- private Set<Account.Id> authors;
- private boolean emailOnlyAuthors;
- private boolean emailOnlyAttentionSetIfEnabled;
- // Watchers ignore attention set rules.
- private Set<Account.Id> watcherAccounts = new HashSet<>();
- // Watcher can only be an email if it's specified in notify section of ProjectConfig.
- private Set<Address> watcherEmails = new HashSet<>();
- private boolean isThreadReply = false;
-
- public ChangeEmailNew(
- @Provided EmailArguments args,
- ChangeData changeData,
- ChangeEmailDecorator changeEmailDecorator) {
- this.args = args;
- this.changeData = changeData;
- change = changeData.change();
- emailOnlyAuthors = false;
- emailOnlyAttentionSetIfEnabled = true;
- currentAttentionSet = getAttentionSet();
- branch = changeData.change().getDest();
- this.changeEmailDecorator = changeEmailDecorator;
- }
-
- public void markAsReply() {
- isThreadReply = true;
- }
-
- public Change getChange() {
- return change;
- }
-
- public ChangeData getChangeData() {
- return changeData;
- }
-
- @Nullable
- public Instant getTimestamp() {
- return timestamp;
- }
-
- public void setPatchSet(PatchSet ps) {
- patchSet = ps;
- }
-
- @Nullable
- public PatchSet getPatchSet() {
- return patchSet;
- }
-
- public void setPatchSet(PatchSet ps, PatchSetInfo psi) {
- patchSet = ps;
- patchSetInfo = psi;
- }
-
- public void setChangeMessage(String cm, Instant t) {
- changeMessage = cm;
- timestamp = t;
- }
-
- public void setEmailOnlyAttentionSetIfEnabled(boolean value) {
- emailOnlyAttentionSetIfEnabled = value;
- }
-
- @Override
- public boolean shouldSendMessage() throws EmailException {
- return changeEmailDecorator.shouldSendMessage();
- }
-
- @Override
- public void init(OutgoingEmailNew email) throws EmailException {
- this.email = email;
-
- changeMessageThreadId =
- String.format(
- "<gerrit.%s.%s@%s>",
- change.getCreatedOn().toEpochMilli(), change.getKey().get(), email.getGerritHost());
-
- if (email.getFrom() != null) {
- // Is the from user in an email squelching group?
- try {
- args.permissionBackend.absentUser(email.getFrom()).check(GlobalPermission.EMAIL_REVIEWERS);
- } catch (AuthException | PermissionBackendException e) {
- emailOnlyAuthors = true;
- }
- }
-
- if (args.projectCache != null) {
- projectState = args.projectCache.get(change.getProject()).orElse(null);
- } else {
- projectState = null;
- }
-
- if (patchSet == null) {
- try {
- patchSet = changeData.currentPatchSet();
- } catch (StorageException err) {
- patchSet = null;
- }
- }
-
- if (patchSet != null) {
- email.setHeader(MailHeader.PATCH_SET.fieldName(), patchSet.number() + "");
- if (patchSetInfo == null) {
- try {
- patchSetInfo = args.patchSetInfoFactory.get(changeData.notes(), patchSet.id());
- } catch (PatchSetInfoNotAvailableException | StorageException err) {
- patchSetInfo = null;
- }
- }
- }
-
- try {
- stars = changeData.stars();
- } catch (StorageException e) {
- throw new EmailException("Failed to load stars for change " + change.getChangeId(), e);
- }
-
- BranchEmailUtils.setListIdHeader(email, branch);
- if (timestamp != null) {
- email.setHeader(FieldName.DATE, timestamp);
- }
- email.setHeader(MailHeader.CHANGE_ID.fieldName(), "" + change.getKey().get());
- email.setHeader(MailHeader.CHANGE_NUMBER.fieldName(), "" + change.getChangeId());
- email.setHeader(MailHeader.PROJECT.fieldName(), "" + change.getProject());
- setChangeUrlHeader();
- setCommitIdHeader();
-
- changeEmailDecorator.init(email, this);
- }
-
- private void setChangeUrlHeader() {
- final String u = getChangeUrl();
- if (u != null) {
- email.setHeader(MailHeader.CHANGE_URL.fieldName(), "<" + u + ">");
- }
- }
-
- private void setCommitIdHeader() {
- if (patchSet != null) {
- email.setHeader(MailHeader.COMMIT.fieldName(), patchSet.commitId().name());
- }
- }
-
- private void setChangeSubjectHeader() {
- email.setHeader(FieldName.SUBJECT, email.textTemplate("ChangeSubject"));
- }
-
- private int getInsertionsCount() {
- return listModifiedFiles().entrySet().stream()
- .filter(e -> !Patch.COMMIT_MSG.equals(e.getKey()))
- .map(Map.Entry::getValue)
- .map(FileDiffOutput::insertions)
- .reduce(0, Integer::sum);
- }
-
- private int getDeletionsCount() {
- return listModifiedFiles().values().stream()
- .map(FileDiffOutput::deletions)
- .reduce(0, Integer::sum);
- }
-
- /**
- * Get a link to the change; null if the server doesn't know its own address or if the address is
- * malformed. The link will contain a usp parameter set to "email" to inform the frontend on
- * clickthroughs where the link came from.
- */
- @Nullable
- public String getChangeUrl() {
- Optional<String> changeUrl =
- args.urlFormatter.get().getChangeViewUrl(change.getProject(), change.getId());
- if (!changeUrl.isPresent()) return null;
- try {
- URI uri = new URIBuilder(changeUrl.get()).addParameter("usp", "email").build();
- return uri.toString();
- } catch (URISyntaxException e) {
- return null;
- }
- }
-
- /** Sets headers for conversation grouping */
- private void setThreadHeaders() {
- if (isThreadReply) {
- email.setHeader("In-Reply-To", changeMessageThreadId);
- }
- email.setHeader("References", changeMessageThreadId);
- }
-
- /** Get the text of the "cover letter". */
- public String getCoverLetter() {
- if (changeMessage != null) {
- return changeMessage.trim();
- }
- return "";
- }
-
- /** Create the change message and the affected file list. */
- public String getChangeDetail() {
- try {
- StringBuilder detail = new StringBuilder();
-
- if (patchSetInfo != null) {
- detail.append(patchSetInfo.getMessage().trim()).append("\n");
- } else {
- detail.append(change.getSubject().trim()).append("\n");
- }
-
- if (patchSet != null) {
- detail.append("---\n");
- // Sort files by name.
- TreeMap<String, FileDiffOutput> modifiedFiles = new TreeMap<>(listModifiedFiles());
- for (FileDiffOutput fileDiff : modifiedFiles.values()) {
- if (fileDiff.newPath().isPresent() && Patch.isMagic(fileDiff.newPath().get())) {
- continue;
- }
- detail
- .append(fileDiff.changeType().getCode())
- .append(" ")
- .append(
- FilePathAdapter.getNewPath(
- fileDiff.oldPath(), fileDiff.newPath(), fileDiff.changeType()))
- .append("\n");
- }
- detail.append(
- MessageFormat.format(
- "" //
- + "{0,choice,0#0 files|1#1 file|1<{0} files} changed, " //
- + "{1,choice,0#0 insertions|1#1 insertion|1<{1} insertions}(+), " //
- + "{2,choice,0#0 deletions|1#1 deletion|1<{2} deletions}(-)" //
- + "\n",
- modifiedFiles.size() - 1, // -1 to account for the commit message
- getInsertionsCount(),
- getDeletionsCount()));
- detail.append("\n");
- }
- return detail.toString();
- } catch (Exception err) {
- logger.atWarning().withCause(err).log("Cannot format change detail");
- return "";
- }
- }
-
- /** Get the patch list corresponding to patch set patchSetId of this change. */
- public Map<String, FileDiffOutput> listModifiedFiles(int patchSetId) {
- try {
- PatchSet ps;
- if (patchSetId == patchSet.number()) {
- ps = patchSet;
- } else {
- ps = args.patchSetUtil.get(changeData.notes(), PatchSet.id(change.getId(), patchSetId));
- }
- return args.diffOperations.listModifiedFilesAgainstParent(
- change.getProject(), ps.commitId(), /* parentNum= */ 0, DiffOptions.DEFAULTS);
- } catch (StorageException | DiffNotAvailableException e) {
- logger.atSevere().withCause(e).log("Failed to get modified files");
- return new HashMap<>();
- }
- }
-
- /** Get the patch list corresponding to this patch set. */
- public Map<String, FileDiffOutput> listModifiedFiles() {
- if (patchSet != null) {
- try {
- return args.diffOperations.listModifiedFilesAgainstParent(
- change.getProject(), patchSet.commitId(), /* parentNum= */ 0, DiffOptions.DEFAULTS);
- } catch (DiffNotAvailableException e) {
- logger.atSevere().withCause(e).log("Failed to get modified files");
- }
- } else {
- logger.atSevere().log("no patchSet specified");
- }
- return new HashMap<>();
- }
-
- /** Get the project entity the change is in; null if its been deleted. */
- public ProjectState getProjectState() {
- return projectState;
- }
-
- /** TO or CC all vested parties (change owner, patch set uploader, author). */
- public void addAuthors(RecipientType rt) {
- for (Account.Id id : getAuthors()) {
- email.addByAccountId(rt, id);
- }
- }
-
- /** BCC any user who has starred this change. */
- public void bccStarredBy() {
- if (!NotifyHandling.ALL.equals(email.getNotify().handling())) {
- return;
- }
-
- for (Map.Entry<Account.Id, Collection<String>> e : stars.asMap().entrySet()) {
- if (e.getValue().contains(StarredChangesUtil.DEFAULT_LABEL)) {
- email.addByAccountId(RecipientType.BCC, e.getKey());
- }
- }
- }
-
- /** Include users and groups that want notification of events. */
- public void includeWatchers(NotifyType type) {
- includeWatchers(type, true);
- }
-
- /** Include users and groups that want notification of events. */
- public void includeWatchers(NotifyType type, boolean includeWatchersFromNotifyConfig) {
- try {
- Watchers matching = getWatchers(type, includeWatchersFromNotifyConfig);
- addWatchers(RecipientType.TO, matching.to);
- addWatchers(RecipientType.CC, matching.cc);
- addWatchers(RecipientType.BCC, matching.bcc);
- } catch (StorageException err) {
- // Just don't CC everyone. Better to send a partial message to those
- // we already have queued up then to fail deliver entirely to people
- // who have a lower interest in the change.
- logger.atWarning().withCause(err).log("Cannot BCC watchers for %s", type);
- }
- }
-
- /** Add users or email addresses to the TO, CC, or BCC list. */
- private void addWatchers(RecipientType type, WatcherList watcherList) {
- watcherAccounts.addAll(watcherList.accounts);
- for (Account.Id user : watcherList.accounts) {
- email.addByAccountId(type, user);
- }
-
- watcherEmails.addAll(watcherList.emails);
- for (Address addr : watcherList.emails) {
- email.addByEmail(type, addr);
- }
- }
-
- private final Watchers getWatchers(NotifyType type, boolean includeWatchersFromNotifyConfig) {
- if (!NotifyHandling.ALL.equals(email.getNotify().handling())) {
- return new Watchers();
- }
-
- ProjectWatch watch = new ProjectWatch(args, branch.project(), projectState, changeData);
- return watch.getWatchers(type, includeWatchersFromNotifyConfig);
- }
-
- /** Any user who has published comments on this change. */
- public void ccAllApprovals() {
- if (!NotifyHandling.ALL.equals(email.getNotify().handling())
- && !NotifyHandling.OWNER_REVIEWERS.equals(email.getNotify().handling())) {
- return;
- }
-
- try {
- for (Account.Id id : changeData.reviewers().all()) {
- email.addByAccountId(RecipientType.CC, id);
- }
- } catch (StorageException err) {
- logger.atWarning().withCause(err).log("Cannot CC users that reviewed updated change");
- }
- }
-
- /** Users who were added as reviewers to this change. */
- public void ccExistingReviewers() {
- if (!NotifyHandling.ALL.equals(email.getNotify().handling())
- && !NotifyHandling.OWNER_REVIEWERS.equals(email.getNotify().handling())) {
- return;
- }
-
- try {
- for (Account.Id id : changeData.reviewers().byState(ReviewerStateInternal.REVIEWER)) {
- email.addByAccountId(RecipientType.CC, id);
- }
- } catch (StorageException err) {
- logger.atWarning().withCause(err).log("Cannot CC users that commented on updated change");
- }
- }
-
- @Override
- public boolean isRecipientAllowed(Address addr) throws PermissionBackendException {
- if (!projectState.statePermitsRead()) {
- return false;
- }
- if (emailOnlyAuthors) {
- return false;
- }
-
- // If the email is a watcher email, skip permission check. An email can only be a watcher if
- // it is specified in notify section of ProjectConfig, so we trust that the recipient is
- // allowed.
- if (watcherEmails.contains(addr)) {
- return true;
- }
- return args.permissionBackend
- .user(args.anonymousUser.get())
- .change(changeData)
- .test(ChangePermission.READ);
- }
-
- @Override
- public boolean isRecipientAllowed(Account.Id to) throws PermissionBackendException {
- if (!projectState.statePermitsRead()) {
- return false;
- }
- if (emailOnlyAuthors && !getAuthors().contains(to)) {
- return false;
- }
- // Watchers ignore AttentionSet rules.
- if (!watcherAccounts.contains(to)) {
- Optional<AccountState> accountState = args.accountCache.get(to);
- if (emailOnlyAttentionSetIfEnabled
- && accountState.isPresent()
- && accountState.get().generalPreferences().getEmailStrategy()
- == EmailStrategy.ATTENTION_SET_ONLY
- && !currentAttentionSet.contains(to)) {
- return false;
- }
- }
- return args.permissionBackend.absentUser(to).change(changeData).test(ChangePermission.READ);
- }
-
- /** Lazily finds all users who are authors of any part of this change. */
- private Set<Account.Id> getAuthors() {
- if (this.authors != null) {
- return this.authors;
- }
- Set<Account.Id> authors = new HashSet<>();
-
- switch (email.getNotify().handling()) {
- case NONE:
- break;
- case ALL:
- default:
- if (patchSet != null) {
- authors.add(patchSet.uploader());
- }
- if (patchSetInfo != null) {
- if (patchSetInfo.getAuthor().getAccount() != null) {
- authors.add(patchSetInfo.getAuthor().getAccount());
- }
- if (patchSetInfo.getCommitter().getAccount() != null) {
- authors.add(patchSetInfo.getCommitter().getAccount());
- }
- }
- // $FALL-THROUGH$
- case OWNER_REVIEWERS:
- case OWNER:
- authors.add(change.getOwner());
- break;
- }
-
- return this.authors = authors;
- }
-
- @Override
- public void populateEmailContent() throws EmailException {
- BranchEmailUtils.addBranchData(email, args, branch);
- setThreadHeaders();
-
- email.addSoyParam("changeId", change.getKey().get());
- email.addSoyParam("coverLetter", getCoverLetter());
- email.addSoyParam("fromName", email.getNameFor(email.getFrom()));
- email.addSoyParam("fromEmail", email.getNameEmailFor(email.getFrom()));
- email.addSoyParam("diffLines", getDiffTemplateData(getUnifiedDiff()));
-
- email.addSoyEmailDataParam("unifiedDiff", getUnifiedDiff());
- email.addSoyEmailDataParam("changeDetail", getChangeDetail());
- email.addSoyEmailDataParam("changeUrl", getChangeUrl());
- email.addSoyEmailDataParam("includeDiff", getIncludeDiff());
-
- Map<String, String> changeData = new HashMap<>();
-
- String subject = change.getSubject();
- String originalSubject = change.getOriginalSubject();
- changeData.put("subject", subject);
- changeData.put("originalSubject", originalSubject);
- changeData.put("shortSubject", shortenSubject(subject));
- changeData.put("shortOriginalSubject", shortenSubject(originalSubject));
-
- changeData.put("ownerName", email.getNameFor(change.getOwner()));
- changeData.put("ownerEmail", email.getNameEmailFor(change.getOwner()));
- changeData.put("changeNumber", Integer.toString(change.getChangeId()));
- changeData.put(
- "sizeBucket",
- ChangeSizeBucket.getChangeSizeBucket(getInsertionsCount() + getDeletionsCount()));
- email.addSoyParam("change", changeData);
-
- Map<String, Object> patchSetData = new HashMap<>();
- patchSetData.put("patchSetId", patchSet.number());
- patchSetData.put("refName", patchSet.refName());
- email.addSoyParam("patchSet", patchSetData);
-
- Map<String, Object> patchSetInfoData = new HashMap<>();
- patchSetInfoData.put("authorName", patchSetInfo.getAuthor().getName());
- patchSetInfoData.put("authorEmail", patchSetInfo.getAuthor().getEmail());
- email.addSoyParam("patchSetInfo", patchSetInfoData);
-
- email.addFooter(MailHeader.CHANGE_ID.withDelimiter() + change.getKey().get());
- email.addFooter(MailHeader.CHANGE_NUMBER.withDelimiter() + change.getChangeId());
- email.addFooter(MailHeader.PATCH_SET.withDelimiter() + patchSet.number());
- email.addFooter(MailHeader.OWNER.withDelimiter() + email.getNameEmailFor(change.getOwner()));
- for (String reviewer : getEmailsByState(ReviewerStateInternal.REVIEWER)) {
- email.addFooter(MailHeader.REVIEWER.withDelimiter() + reviewer);
- }
- for (String reviewer : getEmailsByState(ReviewerStateInternal.CC)) {
- email.addFooter(MailHeader.CC.withDelimiter() + reviewer);
- }
- for (Account.Id attentionUser : currentAttentionSet) {
- email.addFooter(MailHeader.ATTENTION.withDelimiter() + email.getNameEmailFor(attentionUser));
- }
- if (!currentAttentionSet.isEmpty()) {
- // We need names rather than account ids / emails to make it user readable.
- email.addSoyParam(
- "attentionSet",
- currentAttentionSet.stream().map(email::getNameFor).sorted().collect(toImmutableList()));
- }
-
- setChangeSubjectHeader();
- if (email.getNotify().handling().equals(NotifyHandling.OWNER_REVIEWERS)
- || email.getNotify().handling().equals(NotifyHandling.ALL)) {
- try {
- this.changeData.reviewersByEmail().byState(ReviewerStateInternal.CC).stream()
- .forEach(address -> email.addByEmail(RecipientType.CC, address));
- this.changeData.reviewersByEmail().byState(ReviewerStateInternal.REVIEWER).stream()
- .forEach(address -> email.addByEmail(RecipientType.CC, address));
- } catch (StorageException e) {
- throw new EmailException("Failed to add unregistered CCs " + change.getChangeId(), e);
- }
- }
-
- if (email.useHtml()) {
- email.appendHtml(email.soyHtmlTemplate("ChangeHeaderHtml"));
- }
- email.appendText(email.textTemplate("ChangeHeader"));
- changeEmailDecorator.populateEmailContent();
- email.appendText(email.textTemplate("ChangeFooter"));
- if (email.useHtml()) {
- email.appendHtml(email.soyHtmlTemplate("ChangeFooterHtml"));
- }
- }
-
- /**
- * A shortened subject is the subject limited to 72 characters, with an ellipsis if it exceeds
- * that limit.
- */
- private static String shortenSubject(String subject) {
- if (subject.length() < 73) {
- return subject;
- }
- return subject.substring(0, 69) + "...";
- }
-
- private Set<String> getEmailsByState(ReviewerStateInternal state) {
- Set<String> reviewers = new TreeSet<>();
- try {
- for (Account.Id who : changeData.reviewers().byState(state)) {
- reviewers.add(email.getNameEmailFor(who));
- }
- } catch (StorageException e) {
- logger.atWarning().withCause(e).log("Cannot get change reviewers");
- }
- return reviewers;
- }
-
- private Set<Account.Id> getAttentionSet() {
- Set<Account.Id> attentionSet = new TreeSet<>();
- try {
- attentionSet =
- additionsOnly(changeData.attentionSet()).stream()
- .map(AttentionSetUpdate::account)
- .collect(Collectors.toSet());
- } catch (StorageException e) {
- logger.atWarning().withCause(e).log("Cannot get change attention set");
- }
- return attentionSet;
- }
-
- public boolean getIncludeDiff() {
- return args.settings.includeDiff;
- }
-
- private static final int HEAP_EST_SIZE = 32 * 1024;
-
- /** Show patch set as unified difference. */
- public String getUnifiedDiff() {
- Map<String, FileDiffOutput> modifiedFiles;
- modifiedFiles = listModifiedFiles();
- if (modifiedFiles.isEmpty()) {
- // Octopus merges are not well supported for diff output by Gerrit.
- // Currently these always have a null oldId in the PatchList.
- return "[Empty change (potentially Octopus merge); cannot be formatted as a diff.]\n";
- }
-
- int maxSize = args.settings.maximumDiffSize;
- TemporaryBuffer.Heap buf = new TemporaryBuffer.Heap(Math.min(HEAP_EST_SIZE, maxSize), maxSize);
- try (DiffFormatter fmt = new DiffFormatter(buf)) {
- try (Repository git = args.server.openRepository(change.getProject())) {
- try {
- ObjectId oldId = modifiedFiles.values().iterator().next().oldCommitId();
- ObjectId newId = modifiedFiles.values().iterator().next().newCommitId();
- if (oldId.equals(ObjectId.zeroId())) {
- // DiffOperations returns ObjectId.zeroId if newCommit is a root commit, i.e. has no
- // parents.
- oldId = null;
- }
- fmt.setRepository(git);
- fmt.setDetectRenames(true);
- fmt.format(oldId, newId);
- return RawParseUtils.decode(buf.toByteArray());
- } catch (IOException e) {
- if (JGitText.get().inMemoryBufferLimitExceeded.equals(e.getMessage())) {
- return "";
- }
- logger.atSevere().withCause(e).log("Cannot format patch");
- return "";
- }
- } catch (IOException e) {
- logger.atSevere().withCause(e).log("Cannot open repository to format patch");
- return "";
- }
- }
- }
-
- /**
- * Generate a list of maps representing each line of the unified diff. The line maps will have a
- * 'type' key which maps to one of 'common', 'add' or 'remove' and a 'text' key which maps to the
- * line's content.
- *
- * @param sourceDiff the unified diff that we're converting to the map.
- * @return map of 'type' to a line's content.
- */
- public static ImmutableList<ImmutableMap<String, String>> getDiffTemplateData(String sourceDiff) {
- ImmutableList.Builder<ImmutableMap<String, String>> result = ImmutableList.builder();
- Splitter lineSplitter = Splitter.on(System.getProperty("line.separator"));
- for (String diffLine : lineSplitter.split(sourceDiff)) {
- ImmutableMap.Builder<String, String> lineData = ImmutableMap.builder();
- lineData.put("text", diffLine);
-
- // Skip empty lines and lines that look like diff headers.
- if (diffLine.isEmpty() || diffLine.startsWith("---") || diffLine.startsWith("+++")) {
- lineData.put("type", "common");
- } else {
- switch (diffLine.charAt(0)) {
- case '+':
- lineData.put("type", "add");
- break;
- case '-':
- lineData.put("type", "remove");
- break;
- default:
- lineData.put("type", "common");
- break;
- }
- }
- result.add(lineData.build());
- }
- return result.build();
- }
-}
diff --git a/java/com/google/gerrit/server/mail/send/CommentChangeEmailDecorator.java b/java/com/google/gerrit/server/mail/send/CommentChangeEmailDecorator.java
index 932e120..48b2257 100644
--- a/java/com/google/gerrit/server/mail/send/CommentChangeEmailDecorator.java
+++ b/java/com/google/gerrit/server/mail/send/CommentChangeEmailDecorator.java
@@ -46,7 +46,7 @@
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.mail.receive.Protocol;
-import com.google.gerrit.server.mail.send.ChangeEmailNew.ChangeEmailDecorator;
+import com.google.gerrit.server.mail.send.ChangeEmail.ChangeEmailDecorator;
import com.google.gerrit.server.patch.PatchFile;
import com.google.gerrit.server.patch.filediff.FileDiffOutput;
import com.google.gerrit.server.util.LabelVote;
@@ -85,19 +85,28 @@
return args.urlFormatter
.get()
.getInlineCommentView(changeEmail.getChange(), uuid)
+ .map(EmailArguments::addUspParam)
.orElse(null);
}
/** Returns a web link to the comment tab view of a change. */
@Nullable
public String getCommentsTabLink() {
- return args.urlFormatter.get().getCommentsTabView(changeEmail.getChange()).orElse(null);
+ return args.urlFormatter
+ .get()
+ .getCommentsTabView(changeEmail.getChange())
+ .map(EmailArguments::addUspParam)
+ .orElse(null);
}
/** Returns a web link to the findings tab view of a change. */
@Nullable
public String getFindingsTabLink() {
- return args.urlFormatter.get().getFindingsTabView(changeEmail.getChange()).orElse(null);
+ return args.urlFormatter
+ .get()
+ .getFindingsTabView(changeEmail.getChange())
+ .map(EmailArguments::addUspParam)
+ .orElse(null);
}
/**
@@ -117,8 +126,8 @@
}
private EmailArguments args;
- private OutgoingEmailNew email;
- private ChangeEmailNew changeEmail;
+ private OutgoingEmail email;
+ private ChangeEmail changeEmail;
private List<? extends Comment> inlineComments = Collections.emptyList();
@Nullable private String patchSetComment;
private ImmutableList<LabelVote> labels = ImmutableList.of();
@@ -166,7 +175,7 @@
}
@Override
- public void init(OutgoingEmailNew email, ChangeEmailNew changeEmail) {
+ public void init(OutgoingEmail email, ChangeEmail changeEmail) {
this.email = email;
this.changeEmail = changeEmail;
// Add header that enables identifying comments on parsed email.
diff --git a/java/com/google/gerrit/server/mail/send/CreateChangeSender.java b/java/com/google/gerrit/server/mail/send/CreateChangeSender.java
deleted file mode 100644
index 4667652..0000000
--- a/java/com/google/gerrit/server/mail/send/CreateChangeSender.java
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright (C) 2009 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.mail.send;
-
-import com.google.gerrit.entities.Change;
-import com.google.gerrit.entities.NotifyConfig.NotifyType;
-import com.google.gerrit.entities.Project;
-import com.google.gerrit.exceptions.EmailException;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-/** Notify interested parties of a brand new change. */
-public class CreateChangeSender extends NewChangeSender {
- public interface Factory {
- CreateChangeSender create(Project.NameKey project, Change.Id changeId);
- }
-
- @Inject
- public CreateChangeSender(
- EmailArguments args, @Assisted Project.NameKey project, @Assisted Change.Id changeId) {
- super(args, newChangeData(args, project, changeId));
- }
-
- @Override
- protected void populateEmailContent() throws EmailException {
- super.populateEmailContent();
-
- includeWatchers(
- NotifyType.NEW_CHANGES, !getChange().isWorkInProgress() && !getChange().isPrivate());
- includeWatchers(
- NotifyType.NEW_PATCHSETS, !getChange().isWorkInProgress() && !getChange().isPrivate());
- }
-}
diff --git a/java/com/google/gerrit/server/mail/send/DeleteKeyEmailDecorator.java b/java/com/google/gerrit/server/mail/send/DeleteKeyEmailDecorator.java
new file mode 100644
index 0000000..b2228f5
--- /dev/null
+++ b/java/com/google/gerrit/server/mail/send/DeleteKeyEmailDecorator.java
@@ -0,0 +1,106 @@
+// 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.mail.send;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
+import com.google.common.base.Joiner;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.api.changes.RecipientType;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountSshKey;
+import com.google.gerrit.server.mail.send.OutgoingEmail.EmailDecorator;
+import java.util.Collections;
+import java.util.List;
+
+/** Informs a user by email about the removal of an SSH or GPG key from their account. */
+@AutoFactory
+public class DeleteKeyEmailDecorator implements EmailDecorator {
+ private OutgoingEmail email;
+
+ private final IdentifiedUser user;
+ private final AccountSshKey sshKey;
+ private final List<String> gpgKeyFingerprints;
+ private final MessageIdGenerator messageIdGenerator;
+
+ public DeleteKeyEmailDecorator(
+ @Provided MessageIdGenerator messageIdGenerator, IdentifiedUser user, AccountSshKey sshKey) {
+ this.messageIdGenerator = messageIdGenerator;
+ this.user = user;
+ this.gpgKeyFingerprints = Collections.emptyList();
+ this.sshKey = sshKey;
+ }
+
+ public DeleteKeyEmailDecorator(
+ @Provided MessageIdGenerator messageIdGenerator,
+ IdentifiedUser user,
+ List<String> gpgKeyFingerprints) {
+ this.messageIdGenerator = messageIdGenerator;
+ this.user = user;
+ this.gpgKeyFingerprints = gpgKeyFingerprints;
+ this.sshKey = null;
+ }
+
+ @Override
+ public void init(OutgoingEmail email) {
+ this.email = email;
+
+ email.setHeader("Subject", String.format("[Gerrit Code Review] %s Keys Deleted", getKeyType()));
+ email.setMessageId(messageIdGenerator.fromAccountUpdate(user.getAccountId()));
+ email.addByAccountId(RecipientType.TO, user.getAccountId());
+ }
+
+ @Override
+ public void populateEmailContent() {
+ email.addSoyEmailDataParam("email", getEmail());
+ email.addSoyEmailDataParam("gpgKeyFingerprints", getGpgKeyFingerprints());
+ email.addSoyEmailDataParam("keyType", getKeyType());
+ email.addSoyEmailDataParam("sshKey", getSshKey());
+ email.addSoyEmailDataParam("userNameEmail", email.getUserNameEmailFor(user.getAccountId()));
+ email.addSoyEmailDataParam("sshKeysSettingsUrl", email.getSettingsUrl("ssh-keys"));
+ email.addSoyEmailDataParam("gpgKeysSettingsUrl", email.getSettingsUrl("gpg-keys"));
+
+ email.appendText(email.textTemplate("DeleteKey"));
+ if (email.useHtml()) {
+ email.appendHtml(email.soyHtmlTemplate("DeleteKeyHtml"));
+ }
+ }
+
+ private String getEmail() {
+ return user.getAccount().preferredEmail();
+ }
+
+ private String getKeyType() {
+ if (sshKey != null) {
+ return "SSH";
+ } else if (gpgKeyFingerprints != null) {
+ return "GPG";
+ }
+ throw new IllegalStateException("key type is not SSH or GPG");
+ }
+
+ @Nullable
+ private String getSshKey() {
+ return (sshKey != null) ? sshKey.sshPublicKey() + "\n" : null;
+ }
+
+ @Nullable
+ private String getGpgKeyFingerprints() {
+ if (!gpgKeyFingerprints.isEmpty()) {
+ return Joiner.on("\n").join(gpgKeyFingerprints);
+ }
+ return null;
+ }
+}
diff --git a/java/com/google/gerrit/server/mail/send/DeleteKeySender.java b/java/com/google/gerrit/server/mail/send/DeleteKeySender.java
deleted file mode 100644
index e9a2ead..0000000
--- a/java/com/google/gerrit/server/mail/send/DeleteKeySender.java
+++ /dev/null
@@ -1,125 +0,0 @@
-// 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.mail.send;
-
-import com.google.common.base.Joiner;
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.exceptions.EmailException;
-import com.google.gerrit.extensions.api.changes.RecipientType;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountSshKey;
-import com.google.inject.assistedinject.Assisted;
-import com.google.inject.assistedinject.AssistedInject;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Sender that informs a user by email about the removal of an SSH or GPG key from their account.
- */
-public class DeleteKeySender extends OutgoingEmail {
- public interface Factory {
- DeleteKeySender create(IdentifiedUser user, AccountSshKey sshKey);
-
- DeleteKeySender create(IdentifiedUser user, List<String> gpgKeyFingerprints);
- }
-
- private final IdentifiedUser user;
- private final AccountSshKey sshKey;
- private final List<String> gpgKeyFingerprints;
- private final MessageIdGenerator messageIdGenerator;
-
- @AssistedInject
- public DeleteKeySender(
- EmailArguments args,
- MessageIdGenerator messageIdGenerator,
- @Assisted IdentifiedUser user,
- @Assisted AccountSshKey sshKey) {
- super(args, "deletekey");
- this.messageIdGenerator = messageIdGenerator;
- this.user = user;
- this.gpgKeyFingerprints = Collections.emptyList();
- this.sshKey = sshKey;
- }
-
- @AssistedInject
- public DeleteKeySender(
- EmailArguments args,
- MessageIdGenerator messageIdGenerator,
- @Assisted IdentifiedUser user,
- @Assisted List<String> gpgKeyFingerprints) {
- super(args, "deletekey");
- this.messageIdGenerator = messageIdGenerator;
- this.user = user;
- this.gpgKeyFingerprints = gpgKeyFingerprints;
- this.sshKey = null;
- }
-
- @Override
- protected void init() throws EmailException {
- super.init();
- setHeader("Subject", String.format("[Gerrit Code Review] %s Keys Deleted", getKeyType()));
- setMessageId(messageIdGenerator.fromAccountUpdate(user.getAccountId()));
- addByAccountId(RecipientType.TO, user.getAccountId());
- }
-
- @Override
- protected boolean shouldSendMessage() {
- return true;
- }
-
- @Override
- protected void format() throws EmailException {
- appendText(textTemplate("DeleteKey"));
- if (useHtml()) {
- appendHtml(soyHtmlTemplate("DeleteKeyHtml"));
- }
- }
-
- @Override
- protected void populateEmailContent() throws EmailException {
- super.populateEmailContent();
- addSoyEmailDataParam("email", getEmail());
- addSoyEmailDataParam("gpgKeyFingerprints", getGpgKeyFingerprints());
- addSoyEmailDataParam("keyType", getKeyType());
- addSoyEmailDataParam("sshKey", getSshKey());
- addSoyEmailDataParam("userNameEmail", getUserNameEmailFor(user.getAccountId()));
- }
-
- private String getEmail() {
- return user.getAccount().preferredEmail();
- }
-
- private String getKeyType() {
- if (sshKey != null) {
- return "SSH";
- } else if (gpgKeyFingerprints != null) {
- return "GPG";
- }
- throw new IllegalStateException("key type is not SSH or GPG");
- }
-
- @Nullable
- private String getSshKey() {
- return (sshKey != null) ? sshKey.sshPublicKey() + "\n" : null;
- }
-
- @Nullable
- private String getGpgKeyFingerprints() {
- if (!gpgKeyFingerprints.isEmpty()) {
- return Joiner.on("\n").join(gpgKeyFingerprints);
- }
- return null;
- }
-}
diff --git a/java/com/google/gerrit/server/mail/send/DeleteReviewerChangeEmailDecorator.java b/java/com/google/gerrit/server/mail/send/DeleteReviewerChangeEmailDecorator.java
index aa6aad2..5b8ac5d 100644
--- a/java/com/google/gerrit/server/mail/send/DeleteReviewerChangeEmailDecorator.java
+++ b/java/com/google/gerrit/server/mail/send/DeleteReviewerChangeEmailDecorator.java
@@ -19,7 +19,7 @@
import com.google.gerrit.entities.Address;
import com.google.gerrit.entities.NotifyConfig.NotifyType;
import com.google.gerrit.extensions.api.changes.RecipientType;
-import com.google.gerrit.server.mail.send.ChangeEmailNew.ChangeEmailDecorator;
+import com.google.gerrit.server.mail.send.ChangeEmail.ChangeEmailDecorator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
@@ -28,8 +28,8 @@
/** Let users know that a reviewer and possibly her review have been removed. */
public class DeleteReviewerChangeEmailDecorator implements ChangeEmailDecorator {
- private OutgoingEmailNew email;
- private ChangeEmailNew changeEmail;
+ private OutgoingEmail email;
+ private ChangeEmail changeEmail;
private final Set<Account.Id> reviewers = new HashSet<>();
private final Set<Address> reviewersByEmail = new HashSet<>();
@@ -58,7 +58,7 @@
}
@Override
- public void init(OutgoingEmailNew email, ChangeEmailNew changeEmail) {
+ public void init(OutgoingEmail email, ChangeEmail changeEmail) {
this.email = email;
this.changeEmail = changeEmail;
changeEmail.markAsReply();
diff --git a/java/com/google/gerrit/server/mail/send/DeleteVoteChangeEmailDecorator.java b/java/com/google/gerrit/server/mail/send/DeleteVoteChangeEmailDecorator.java
index 2bf67e5..873db91 100644
--- a/java/com/google/gerrit/server/mail/send/DeleteVoteChangeEmailDecorator.java
+++ b/java/com/google/gerrit/server/mail/send/DeleteVoteChangeEmailDecorator.java
@@ -16,15 +16,15 @@
import com.google.gerrit.entities.NotifyConfig.NotifyType;
import com.google.gerrit.extensions.api.changes.RecipientType;
-import com.google.gerrit.server.mail.send.ChangeEmailNew.ChangeEmailDecorator;
+import com.google.gerrit.server.mail.send.ChangeEmail.ChangeEmailDecorator;
/** Send notice about a vote that was removed from a change. */
public class DeleteVoteChangeEmailDecorator implements ChangeEmailDecorator {
- private OutgoingEmailNew email;
- private ChangeEmailNew changeEmail;
+ private OutgoingEmail email;
+ private ChangeEmail changeEmail;
@Override
- public void init(OutgoingEmailNew email, ChangeEmailNew changeEmail) {
+ public void init(OutgoingEmail email, ChangeEmail changeEmail) {
this.email = email;
this.changeEmail = changeEmail;
changeEmail.markAsReply();
diff --git a/java/com/google/gerrit/server/mail/send/EmailArguments.java b/java/com/google/gerrit/server/mail/send/EmailArguments.java
index 593c718..f77b2c4 100644
--- a/java/com/google/gerrit/server/mail/send/EmailArguments.java
+++ b/java/com/google/gerrit/server/mail/send/EmailArguments.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.mail.send;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.UsedAt;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.Project;
@@ -51,7 +52,10 @@
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.template.soy.jbcsrc.api.SoySauce;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.util.List;
+import org.apache.http.client.utils.URIBuilder;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
@@ -59,11 +63,10 @@
/**
* Arguments used for sending notification emails.
*
- * <p>Notification emails are sent by out by {@link OutgoingEmail} and it's subclasses, so called
- * senders. To construct an email the sender class needs to get various other classes injected.
- * Instead of injecting these classes into the sender classes directly, they only get {@code
- * EmailArguments} injected and {@code EmailArguments} provides them all dependencies that they
- * need.
+ * <p>Notification emails are sent by out by {@link OutgoingEmail} . To construct an email class (or
+ * its decorators) needs to get various other classes injected. Instead of injecting these classes
+ * into the sender classes directly, they only get {@code EmailArguments} injected and {@code
+ * EmailArguments} provides them all dependencies that they need.
*
* <p>This class is public because plugins need access to it for sending emails.
*/
@@ -177,4 +180,14 @@
public ChangeData newChangeData(Project.NameKey project, Change.Id id, ObjectId metaId) {
return changeDataFactory.create(changeNotesFactory.createChecked(project, id, metaId));
}
+
+ @Nullable
+ public static String addUspParam(String url) {
+ try {
+ URI uri = new URIBuilder(url).addParameter("usp", "email").build();
+ return uri.toString();
+ } catch (URISyntaxException e) {
+ return null;
+ }
+ }
}
diff --git a/java/com/google/gerrit/server/mail/send/HttpPasswordUpdateEmailDecorator.java b/java/com/google/gerrit/server/mail/send/HttpPasswordUpdateEmailDecorator.java
new file mode 100644
index 0000000..af265a6
--- /dev/null
+++ b/java/com/google/gerrit/server/mail/send/HttpPasswordUpdateEmailDecorator.java
@@ -0,0 +1,67 @@
+// 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.mail.send;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
+import com.google.gerrit.extensions.api.changes.RecipientType;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.mail.send.OutgoingEmail.EmailDecorator;
+import com.google.gerrit.server.util.time.TimeUtil;
+
+/** Sender that informs a user by email that the HTTP password of their account was updated. */
+@AutoFactory
+public class HttpPasswordUpdateEmailDecorator implements EmailDecorator {
+ private OutgoingEmail email;
+
+ private final IdentifiedUser user;
+ private final String operation;
+ private final MessageIdGenerator messageIdGenerator;
+
+ public HttpPasswordUpdateEmailDecorator(
+ @Provided MessageIdGenerator messageIdGenerator, IdentifiedUser user, String operation) {
+ this.messageIdGenerator = messageIdGenerator;
+ this.user = user;
+ this.operation = operation;
+ }
+
+ @Override
+ public void init(OutgoingEmail email) {
+ this.email = email;
+
+ email.setHeader("Subject", "[Gerrit Code Review] HTTP password was " + operation);
+ email.setMessageId(
+ messageIdGenerator.fromReasonAccountIdAndTimestamp(
+ "HTTP_password_change", user.getAccountId(), TimeUtil.now()));
+ email.addByAccountId(RecipientType.TO, user.getAccountId());
+ }
+
+ @Override
+ public void populateEmailContent() {
+ email.addSoyEmailDataParam("email", getEmail());
+ email.addSoyEmailDataParam("userNameEmail", email.getUserNameEmailFor(user.getAccountId()));
+ email.addSoyEmailDataParam("operation", operation);
+ email.addSoyEmailDataParam("httpPasswordSettingsUrl", email.getSettingsUrl("http-password"));
+
+ email.appendText(email.textTemplate("HttpPasswordUpdate"));
+ if (email.useHtml()) {
+ email.appendHtml(email.soyHtmlTemplate("HttpPasswordUpdateHtml"));
+ }
+ }
+
+ private String getEmail() {
+ return user.getAccount().preferredEmail();
+ }
+}
diff --git a/java/com/google/gerrit/server/mail/send/HttpPasswordUpdateSender.java b/java/com/google/gerrit/server/mail/send/HttpPasswordUpdateSender.java
deleted file mode 100644
index 0564355..0000000
--- a/java/com/google/gerrit/server/mail/send/HttpPasswordUpdateSender.java
+++ /dev/null
@@ -1,81 +0,0 @@
-// 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.mail.send;
-
-import com.google.gerrit.exceptions.EmailException;
-import com.google.gerrit.extensions.api.changes.RecipientType;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.inject.assistedinject.Assisted;
-import com.google.inject.assistedinject.AssistedInject;
-
-/** Sender that informs a user by email that the HTTP password of their account was updated. */
-public class HttpPasswordUpdateSender extends OutgoingEmail {
- public interface Factory {
- HttpPasswordUpdateSender create(IdentifiedUser user, String operation);
- }
-
- private final IdentifiedUser user;
- private final String operation;
- private final MessageIdGenerator messageIdGenerator;
-
- @AssistedInject
- public HttpPasswordUpdateSender(
- EmailArguments args,
- MessageIdGenerator messageIdGenerator,
- @Assisted IdentifiedUser user,
- @Assisted String operation) {
- super(args, "HttpPasswordUpdate");
- this.messageIdGenerator = messageIdGenerator;
- this.user = user;
- this.operation = operation;
- }
-
- @Override
- protected void init() throws EmailException {
- super.init();
- setHeader("Subject", "[Gerrit Code Review] HTTP password was " + operation);
- setMessageId(
- messageIdGenerator.fromReasonAccountIdAndTimestamp(
- "HTTP_password_change", user.getAccountId(), TimeUtil.now()));
- addByAccountId(RecipientType.TO, user.getAccountId());
- }
-
- @Override
- protected boolean shouldSendMessage() {
- // Always send an email if the HTTP password is updated.
- return true;
- }
-
- @Override
- protected void format() throws EmailException {
- appendText(textTemplate("HttpPasswordUpdate"));
- if (useHtml()) {
- appendHtml(soyHtmlTemplate("HttpPasswordUpdateHtml"));
- }
- }
-
- @Override
- protected void populateEmailContent() throws EmailException {
- super.populateEmailContent();
- addSoyEmailDataParam("email", getEmail());
- addSoyEmailDataParam("userNameEmail", getUserNameEmailFor(user.getAccountId()));
- addSoyEmailDataParam("operation", operation);
- }
-
- private String getEmail() {
- return user.getAccount().preferredEmail();
- }
-}
diff --git a/java/com/google/gerrit/server/mail/send/InboundEmailRejectionEmailDecorator.java b/java/com/google/gerrit/server/mail/send/InboundEmailRejectionEmailDecorator.java
new file mode 100644
index 0000000..1f8fd78
--- /dev/null
+++ b/java/com/google/gerrit/server/mail/send/InboundEmailRejectionEmailDecorator.java
@@ -0,0 +1,79 @@
+// Copyright (C) 2018 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.mail.send;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.gerrit.entities.Address;
+import com.google.gerrit.extensions.api.changes.RecipientType;
+import com.google.gerrit.mail.MailHeader;
+import com.google.gerrit.server.mail.send.OutgoingEmail.EmailDecorator;
+import org.apache.james.mime4j.dom.field.FieldName;
+
+/** Send an email to inform users that parsing their inbound email failed. */
+public class InboundEmailRejectionEmailDecorator implements EmailDecorator {
+
+ /** Used by the templating system to determine what error message should be sent */
+ public enum InboundEmailError {
+ PARSING_ERROR,
+ INACTIVE_ACCOUNT,
+ UNKNOWN_ACCOUNT,
+ INTERNAL_EXCEPTION,
+ COMMENT_REJECTED,
+ CHANGE_NOT_FOUND
+ }
+
+ private OutgoingEmail email;
+ private final Address to;
+ private final InboundEmailError reason;
+ private final String threadId;
+
+ public InboundEmailRejectionEmailDecorator(
+ Address to, String threadId, InboundEmailError reason) {
+ this.to = requireNonNull(to);
+ this.threadId = requireNonNull(threadId);
+ this.reason = requireNonNull(reason);
+ }
+
+ @Override
+ public void init(OutgoingEmail email) {
+ this.email = email;
+
+ setListIdHeader();
+ email.setHeader(FieldName.SUBJECT, "[Gerrit Code Review] Unable to process your email");
+
+ if (!threadId.isEmpty()) {
+ email.setHeader(MailHeader.REFERENCES.fieldName(), threadId);
+ }
+ }
+
+ private void setListIdHeader() {
+ // Set a reasonable list id so that filters can be used to sort messages
+ email.setHeader("List-Id", "<gerrit-noreply." + email.getGerritHost() + ">");
+ if (email.getSettingsUrl() != null) {
+ email.setHeader("List-Unsubscribe", "<" + email.getSettingsUrl() + ">");
+ }
+ }
+
+ @Override
+ public void populateEmailContent() {
+ email.addByEmail(RecipientType.TO, to);
+
+ email.appendText(email.textTemplate("InboundEmailRejection_" + reason.name()));
+ if (email.useHtml()) {
+ email.appendHtml(email.soyHtmlTemplate("InboundEmailRejectionHtml_" + reason.name()));
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/mail/send/InboundEmailRejectionSender.java b/java/com/google/gerrit/server/mail/send/InboundEmailRejectionSender.java
deleted file mode 100644
index 3a91fe6..0000000
--- a/java/com/google/gerrit/server/mail/send/InboundEmailRejectionSender.java
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright (C) 2018 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.mail.send;
-
-import static java.util.Objects.requireNonNull;
-
-import com.google.gerrit.entities.Address;
-import com.google.gerrit.exceptions.EmailException;
-import com.google.gerrit.extensions.api.changes.RecipientType;
-import com.google.gerrit.mail.MailHeader;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-import org.apache.james.mime4j.dom.field.FieldName;
-
-/** Send an email to inform users that parsing their inbound email failed. */
-public class InboundEmailRejectionSender extends OutgoingEmail {
-
- /** Used by the templating system to determine what error message should be sent */
- public enum InboundEmailError {
- PARSING_ERROR,
- INACTIVE_ACCOUNT,
- UNKNOWN_ACCOUNT,
- INTERNAL_EXCEPTION,
- COMMENT_REJECTED,
- CHANGE_NOT_FOUND
- }
-
- public interface Factory {
- InboundEmailRejectionSender create(Address to, String threadId, InboundEmailError reason);
- }
-
- private final Address to;
- private final InboundEmailError reason;
- private final String threadId;
-
- @Inject
- public InboundEmailRejectionSender(
- EmailArguments args,
- @Assisted Address to,
- @Assisted String threadId,
- @Assisted InboundEmailError reason) {
- super(args, "error");
- this.to = requireNonNull(to);
- this.threadId = requireNonNull(threadId);
- this.reason = requireNonNull(reason);
- }
-
- @Override
- protected void init() throws EmailException {
- super.init();
- setListIdHeader();
- setHeader(FieldName.SUBJECT, "[Gerrit Code Review] Unable to process your email");
-
- if (!threadId.isEmpty()) {
- setHeader(MailHeader.REFERENCES.fieldName(), threadId);
- }
- }
-
- private void setListIdHeader() {
- // Set a reasonable list id so that filters can be used to sort messages
- setHeader("List-Id", "<gerrit-noreply." + getGerritHost() + ">");
- if (getSettingsUrl() != null) {
- setHeader("List-Unsubscribe", "<" + getSettingsUrl() + ">");
- }
- }
-
- @Override
- protected void format() throws EmailException {
- appendText(textTemplate("InboundEmailRejection_" + reason.name()));
- if (useHtml()) {
- appendHtml(soyHtmlTemplate("InboundEmailRejectionHtml_" + reason.name()));
- }
- }
-
- @Override
- protected void populateEmailContent() throws EmailException {
- super.populateEmailContent();
- addByEmail(RecipientType.TO, to);
- }
-}
diff --git a/java/com/google/gerrit/server/mail/send/MailSoySauceLoader.java b/java/com/google/gerrit/server/mail/send/MailSoySauceLoader.java
index 0eaafb8..7bc319f 100644
--- a/java/com/google/gerrit/server/mail/send/MailSoySauceLoader.java
+++ b/java/com/google/gerrit/server/mail/send/MailSoySauceLoader.java
@@ -65,6 +65,8 @@
"DeleteReviewerHtml.soy",
"DeleteVote.soy",
"DeleteVoteHtml.soy",
+ "Email.soy",
+ "EmailHtml.soy",
"InboundEmailRejection.soy",
"InboundEmailRejectionHtml.soy",
"Footer.soy",
diff --git a/java/com/google/gerrit/server/mail/send/MergedChangeEmailDecorator.java b/java/com/google/gerrit/server/mail/send/MergedChangeEmailDecorator.java
index dc80ba9..90c8b93 100644
--- a/java/com/google/gerrit/server/mail/send/MergedChangeEmailDecorator.java
+++ b/java/com/google/gerrit/server/mail/send/MergedChangeEmailDecorator.java
@@ -29,7 +29,7 @@
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.api.changes.RecipientType;
import com.google.gerrit.server.change.NotifyResolver;
-import com.google.gerrit.server.mail.send.ChangeEmailNew.ChangeEmailDecorator;
+import com.google.gerrit.server.mail.send.ChangeEmail.ChangeEmailDecorator;
import java.util.Optional;
/** Send notice about a change successfully merged. */
@@ -37,8 +37,8 @@
public class MergedChangeEmailDecorator implements ChangeEmailDecorator {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- private OutgoingEmailNew email;
- private ChangeEmailNew changeEmail;
+ private OutgoingEmail email;
+ private ChangeEmail changeEmail;
private LabelTypes labelTypes;
private final EmailArguments args;
private final Optional<String> stickyApprovalDiff;
@@ -49,7 +49,7 @@
}
@Override
- public void init(OutgoingEmailNew email, ChangeEmailNew changeEmail) {
+ public void init(OutgoingEmail email, ChangeEmail changeEmail) {
this.email = email;
this.changeEmail = changeEmail;
changeEmail.markAsReply();
@@ -137,7 +137,7 @@
if (stickyApprovalDiff.isPresent()) {
email.addSoyEmailDataParam("stickyApprovalDiff", stickyApprovalDiff.get());
email.addSoyEmailDataParam(
- "stickyApprovalDiffHtml", ChangeEmailNew.getDiffTemplateData(stickyApprovalDiff.get()));
+ "stickyApprovalDiffHtml", ChangeEmail.getDiffTemplateData(stickyApprovalDiff.get()));
}
changeEmail.addAuthors(RecipientType.TO);
diff --git a/java/com/google/gerrit/server/mail/send/ModifyReviewerSender.java b/java/com/google/gerrit/server/mail/send/ModifyReviewerSender.java
deleted file mode 100644
index c073805..0000000
--- a/java/com/google/gerrit/server/mail/send/ModifyReviewerSender.java
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (C) 2009 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.mail.send;
-
-import com.google.gerrit.entities.Change;
-import com.google.gerrit.entities.Project;
-import com.google.gerrit.exceptions.EmailException;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-/** Asks a user to review a change. */
-public class ModifyReviewerSender extends NewChangeSender {
- public interface Factory {
- ModifyReviewerSender create(Project.NameKey project, Change.Id changeId);
- }
-
- @Inject
- public ModifyReviewerSender(
- EmailArguments args, @Assisted Project.NameKey project, @Assisted Change.Id changeId) {
- super(args, newChangeData(args, project, changeId));
- }
-
- @Override
- protected void populateEmailContent() throws EmailException {
- super.populateEmailContent();
-
- ccExistingReviewers();
- }
-}
diff --git a/java/com/google/gerrit/server/mail/send/NewChangeSender.java b/java/com/google/gerrit/server/mail/send/NewChangeSender.java
deleted file mode 100644
index 2be5797..0000000
--- a/java/com/google/gerrit/server/mail/send/NewChangeSender.java
+++ /dev/null
@@ -1,133 +0,0 @@
-// Copyright (C) 2009 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.mail.send;
-
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.entities.Account;
-import com.google.gerrit.entities.Address;
-import com.google.gerrit.exceptions.EmailException;
-import com.google.gerrit.extensions.api.changes.RecipientType;
-import com.google.gerrit.server.query.change.ChangeData;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/** Sends an email alerting a user to a new change for them to review. */
-public abstract class NewChangeSender extends ChangeEmail {
- private final Set<Account.Id> reviewers = new HashSet<>();
- private final Set<Address> reviewersByEmail = new HashSet<>();
- private final Set<Account.Id> extraCC = new HashSet<>();
- private final Set<Address> extraCCByEmail = new HashSet<>();
- private final Set<Account.Id> removedReviewers = new HashSet<>();
- private final Set<Address> removedByEmailReviewers = new HashSet<>();
-
- protected NewChangeSender(EmailArguments args, ChangeData changeData) {
- super(args, "newchange", changeData);
- }
-
- public void addReviewers(Collection<Account.Id> cc) {
- reviewers.addAll(cc);
- }
-
- public void addReviewersByEmail(Collection<Address> cc) {
- reviewersByEmail.addAll(cc);
- }
-
- public void addExtraCC(Collection<Account.Id> cc) {
- extraCC.addAll(cc);
- }
-
- public void addExtraCCByEmail(Collection<Address> cc) {
- extraCCByEmail.addAll(cc);
- }
-
- public void addRemovedReviewers(Collection<Account.Id> removed) {
- removedReviewers.addAll(removed);
- }
-
- public void addRemovedByEmailReviewers(Collection<Address> removed) {
- removedByEmailReviewers.addAll(removed);
- }
-
- @Override
- protected void init() throws EmailException {
- super.init();
- String threadId = getChangeMessageThreadId();
- setHeader("References", threadId);
- }
-
- @Override
- protected void formatChange() throws EmailException {
- appendText(textTemplate("NewChange"));
- if (useHtml()) {
- appendHtml(soyHtmlTemplate("NewChangeHtml"));
- }
- }
-
- @Nullable
- private List<String> getReviewerNames() {
- if (reviewers.isEmpty()) {
- return null;
- }
- List<String> names = new ArrayList<>();
- for (Account.Id id : reviewers) {
- names.add(getNameFor(id));
- }
- return names;
- }
-
- @Nullable
- private List<String> getRemovedReviewerNames() {
- if (removedReviewers.isEmpty() && removedByEmailReviewers.isEmpty()) {
- return null;
- }
- List<String> names = new ArrayList<>();
- for (Account.Id id : removedReviewers) {
- names.add(getNameFor(id));
- }
- for (Address address : removedByEmailReviewers) {
- names.add(address.toString());
- }
- return names;
- }
-
- @Override
- protected void populateEmailContent() throws EmailException {
- super.populateEmailContent();
- addSoyParam("ownerName", getNameFor(getChange().getOwner()));
- addSoyEmailDataParam("reviewerNames", getReviewerNames());
- addSoyEmailDataParam("removedReviewerNames", getRemovedReviewerNames());
-
- switch (getNotify().handling()) {
- case NONE:
- case OWNER:
- break;
- case ALL:
- default:
- extraCC.stream().forEach(cc -> addByAccountId(RecipientType.CC, cc));
- extraCCByEmail.stream().forEach(cc -> addByEmail(RecipientType.CC, cc));
- // $FALL-THROUGH$
- case OWNER_REVIEWERS:
- reviewers.stream().forEach(r -> addByAccountId(RecipientType.TO, r, true));
- reviewersByEmail.stream().forEach(r -> addByEmail(RecipientType.TO, r, true));
- removedReviewers.stream().forEach(r -> addByAccountId(RecipientType.TO, r, true));
- removedByEmailReviewers.stream().forEach(r -> addByEmail(RecipientType.TO, r, true));
- break;
- }
- addAuthors(RecipientType.CC);
- }
-}
diff --git a/java/com/google/gerrit/server/mail/send/OutgoingEmail.java b/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
index d432ab8..ca19150 100644
--- a/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
+++ b/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
@@ -18,6 +18,8 @@
import static com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailStrategy.DISABLED;
import static java.util.Objects.requireNonNull;
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
import com.google.common.base.Throwables;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
@@ -39,6 +41,8 @@
import com.google.gerrit.server.update.RetryableAction.ActionType;
import com.google.gerrit.server.validators.OutgoingEmailValidationListener;
import com.google.gerrit.server.validators.ValidationException;
+import com.google.template.soy.data.SanitizedContent.ContentKind;
+import com.google.template.soy.data.UnsafeSanitizedContentOrdainer;
import com.google.template.soy.jbcsrc.api.SoySauce;
import java.net.MalformedURLException;
import java.net.URL;
@@ -57,8 +61,49 @@
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.util.SystemReader;
-/** Sends an email to one or more interested parties. */
-public abstract class OutgoingEmail {
+/** Represents an email notification for some event that can be sent to interested parties. */
+@AutoFactory
+public final class OutgoingEmail {
+
+ /** Provides content, recipients and any customizations of the email. */
+ public interface EmailDecorator {
+ /**
+ * Stores the reference to the email for the subsequent calls.
+ *
+ * <p>Both init and populateEmailContent can be called multiply times in case of retries. Init
+ * is therefore responsible for clearing up any changes which are not idempotent and
+ * initializing data for use in populateEmailContent.
+ *
+ * <p>Can be used to adjust any of the behaviour of the {@link
+ * OutgoingEmail#populateEmailContent}.
+ */
+ void init(OutgoingEmail email) throws EmailException;
+
+ /**
+ * Populate headers, recipients and body of the email.
+ *
+ * <p>Method operates on the email provided in the init method.
+ *
+ * <p>By default, all the contents and parameters of the email should be set in this method.
+ */
+ void populateEmailContent() throws EmailException;
+
+ /** If returns false email is not sent to any recipients. */
+ default boolean shouldSendMessage() throws EmailException {
+ return true;
+ }
+
+ /** Evaluates whether account can be added to the list of recipients. */
+ default boolean isRecipientAllowed(Account.Id rcpt) throws PermissionBackendException {
+ return true;
+ }
+
+ /** Evaluates whether email can be added to the list of recipients. */
+ default boolean isRecipientAllowed(Address rcpt) throws PermissionBackendException {
+ return true;
+ }
+ }
+
private static final String SOY_TEMPLATE_NAMESPACE = "com.google.gerrit.server.mail.template";
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -74,19 +119,24 @@
private Map<String, Object> soyContext;
private Map<String, Object> soyContextEmailData;
private List<String> footers;
- protected final EmailArguments args;
+ private final EmailArguments args;
private Account.Id fromId;
private NotifyResolver.Result notify = NotifyResolver.Result.all();
+ private final EmailDecorator templateProvider;
- protected OutgoingEmail(EmailArguments args, String messageClass) {
+ public OutgoingEmail(
+ @Provided EmailArguments args, String messageClass, EmailDecorator templateProvider) {
this.args = args;
this.messageClass = messageClass;
+ this.templateProvider = templateProvider;
}
+ /** Specify the account that triggered the notification. */
public void setFrom(Account.Id id) {
fromId = id;
}
+ /** Get the account that triggered the notification. */
public Account.Id getFrom() {
return fromId;
}
@@ -101,10 +151,27 @@
return this.notify;
}
+ /** Set identifier for the email. Every email must have one. */
public void setMessageId(MessageIdGenerator.MessageId messageId) {
this.messageId = messageId;
}
+ private String constructTextEmail() {
+ soyContext.put("body", textBody.toString());
+ soyContext.put("footer", textTemplate("Footer"));
+ return textTemplate("Email");
+ }
+
+ private String constructHtmlEmail() {
+ soyContext.put(
+ "body", UnsafeSanitizedContentOrdainer.ordainAsSafe(htmlBody.toString(), ContentKind.HTML));
+ soyContext.put(
+ "footer",
+ UnsafeSanitizedContentOrdainer.ordainAsSafe(
+ soyHtmlTemplate("FooterHtml"), ContentKind.HTML));
+ return soyHtmlTemplate("EmailHtml");
+ }
+
/** Format and enqueue the message for delivery. */
public void send() throws EmailException {
try {
@@ -143,11 +210,6 @@
if (messageId == null) {
throw new IllegalStateException("All emails must have a messageId");
}
- format();
- appendText(textTemplate("Footer"));
- if (useHtml()) {
- appendHtml(soyHtmlTemplate("FooterHtml"));
- }
Set<Address> smtpRcptToPlaintextOnly = new HashSet<>();
if (shouldSendMessage()) {
@@ -247,16 +309,15 @@
setHeader(FieldName.REPLY_TO, j.toString());
}
- String textPart = textBody.toString();
OutgoingEmailValidationListener.Args va = new OutgoingEmailValidationListener.Args();
va.messageClass = messageClass;
va.smtpFromAddress = smtpFromAddress;
va.smtpRcptTo = smtpRcptTo;
va.headers = headers;
- va.body = textPart;
+ va.body = constructTextEmail();
if (useHtml()) {
- va.htmlBody = htmlBody.toString();
+ va.htmlBody = constructHtmlEmail();
} else {
va.htmlBody = null;
}
@@ -284,7 +345,7 @@
shallowCopy.remove(FieldName.CC);
for (Address a : smtpRcptToPlaintextOnly) {
// Add new To
- EmailHeader.AddressList to = new EmailHeader.AddressList();
+ AddressList to = new AddressList();
to.add(a);
shallowCopy.put(FieldName.TO, to);
}
@@ -320,24 +381,21 @@
}
}
- /** Format the message body by calling {@link #appendText(String)}. */
- protected abstract void format() throws EmailException;
-
/**
* Setup the message headers and envelope (TO, CC, BCC).
*
* @throws EmailException if an error occurred.
*/
- protected void init() throws EmailException {
+ public void init() throws EmailException {
soyContext = new HashMap<>();
footers = new ArrayList<>();
soyContextEmailData = new HashMap<>();
smtpFromAddress = args.fromAddressGenerator.get().from(fromId);
setHeader(FieldName.DATE, Instant.now());
- headers.put(FieldName.FROM, new EmailHeader.AddressList(smtpFromAddress));
- headers.put(FieldName.TO, new EmailHeader.AddressList());
- headers.put(FieldName.CC, new EmailHeader.AddressList());
+ headers.put(FieldName.FROM, new AddressList(smtpFromAddress));
+ headers.put(FieldName.TO, new AddressList());
+ headers.put(FieldName.CC, new AddressList());
setHeader(MailHeader.AUTO_SUBMITTED.fieldName(), "auto-generated");
setHeader(MailHeader.MESSAGE_TYPE.fieldName(), messageClass);
@@ -348,9 +406,11 @@
if (fromId != null && args.fromAddressGenerator.get().isGenericAddress(fromId)) {
appendText(getFromLine());
}
+
+ templateProvider.init(this);
}
- protected String getFromLine() {
+ private String getFromLine() {
StringBuilder f = new StringBuilder();
Optional<Account> account = args.accountCache.get(fromId).map(AccountState::account);
if (account.isPresent()) {
@@ -371,9 +431,10 @@
}
public String getGerritHost() {
- if (getGerritUrl() != null) {
+ Optional<String> gerritUrl = args.urlFormatter.get().getWebUrl();
+ if (gerritUrl.isPresent()) {
try {
- return new URL(getGerritUrl()).getHost();
+ return new URL(gerritUrl.get()).getHost();
} catch (MalformedURLException e) {
// Try something else.
}
@@ -388,44 +449,49 @@
@Nullable
public String getSettingsUrl() {
- return args.urlFormatter.get().getSettingsUrl().orElse(null);
+ return args.urlFormatter.get().getSettingsUrl().map(EmailArguments::addUspParam).orElse(null);
}
@Nullable
- private String getGerritUrl() {
- return args.urlFormatter.get().getWebUrl().orElse(null);
+ public String getSettingsUrl(String section) {
+ return args.urlFormatter
+ .get()
+ .getSettingsUrl(section)
+ .map(EmailArguments::addUspParam)
+ .orElse(null);
}
/** Set a header in the outgoing message. */
- protected void setHeader(String name, String value) {
+ public void setHeader(String name, String value) {
headers.put(name, new StringEmailHeader(value));
}
/** Remove a header from the outgoing message. */
- protected void removeHeader(String name) {
+ public void removeHeader(String name) {
headers.remove(name);
}
- protected void setHeader(String name, Instant date) {
+ /** Set a date header in the outgoing message. */
+ public void setHeader(String name, Instant date) {
headers.put(name, new EmailHeader.Date(date));
}
/** Append text to the outgoing email body. */
- protected void appendText(String text) {
+ public void appendText(String text) {
if (text != null) {
textBody.append(text);
}
}
/** Append html to the outgoing email body. */
- protected void appendHtml(String html) {
+ public void appendHtml(String html) {
if (html != null) {
htmlBody.append(html);
}
}
/** Lookup a human readable name for an account, usually the "full name". */
- protected String getNameFor(@Nullable Account.Id accountId) {
+ public String getNameFor(@Nullable Account.Id accountId) {
if (accountId == null) {
return args.gerritPersonIdent.get().getName();
}
@@ -451,7 +517,7 @@
* @param accountId user to fetch.
* @return name/email of account, or Anonymous Coward if unset.
*/
- protected String getNameEmailFor(@Nullable Account.Id accountId) {
+ public String getNameEmailFor(@Nullable Account.Id accountId) {
if (accountId == null) {
PersonIdent gerritIdent = args.gerritPersonIdent.get();
return gerritIdent.getName() + " <" + gerritIdent.getEmailAddress() + ">";
@@ -480,7 +546,7 @@
* @return name/email of account, username, or null if unset or the accountId is null.
*/
@Nullable
- protected String getUserNameEmailFor(@Nullable Account.Id accountId) {
+ public String getUserNameEmailFor(@Nullable Account.Id accountId) {
if (accountId == null) {
return null;
}
@@ -503,7 +569,7 @@
return accountState.get().userName().orElse(null);
}
- protected boolean shouldSendMessage() {
+ private boolean shouldSendMessage() throws EmailException {
if (textBody.length() == 0) {
// If we have no message body, don't send.
logger.atFine().log("Not sending '%s': No message body", messageClass);
@@ -528,7 +594,7 @@
return false;
}
- return true;
+ return templateProvider.shouldSendMessage();
}
/**
@@ -566,8 +632,8 @@
* @throws PermissionBackendException thrown if checking a permission fails due to an error in the
* permission backend
*/
- protected boolean isRecipientAllowed(Address addr) throws PermissionBackendException {
- return true;
+ public boolean isRecipientAllowed(Address addr) throws PermissionBackendException {
+ return templateProvider.isRecipientAllowed(addr);
}
/**
@@ -576,7 +642,7 @@
* @param rt category of recipient (TO, CC, BCC)
* @param to Gerrit Account of the recipient.
*/
- protected void addByAccountId(RecipientType rt, Account.Id to) {
+ public void addByAccountId(RecipientType rt, Account.Id to) {
addByAccountId(rt, to, false);
}
@@ -588,7 +654,7 @@
* @param override if the recipient was added previously and override is false no change is made
* regardless of {@code rt}.
*/
- protected void addByAccountId(RecipientType rt, Account.Id to, boolean override) {
+ public void addByAccountId(RecipientType rt, Account.Id to, boolean override) {
try {
if (!rcptTo.contains(to) && isRecipientAllowed(to)) {
rcptTo.add(to);
@@ -606,8 +672,8 @@
* @throws PermissionBackendException thrown if checking a permission fails due to an error in the
* permission backend
*/
- protected boolean isRecipientAllowed(Account.Id to) throws PermissionBackendException {
- return true;
+ public boolean isRecipientAllowed(Account.Id to) throws PermissionBackendException {
+ return templateProvider.isRecipientAllowed(to);
}
private final void add(RecipientType rt, Address addr, boolean override) {
@@ -619,16 +685,16 @@
if (!override) {
return;
}
- ((EmailHeader.AddressList) headers.get(FieldName.TO)).remove(addr.email());
- ((EmailHeader.AddressList) headers.get(FieldName.CC)).remove(addr.email());
+ ((AddressList) headers.get(FieldName.TO)).remove(addr.email());
+ ((AddressList) headers.get(FieldName.CC)).remove(addr.email());
smtpBccRcptTo.remove(addr);
}
switch (rt) {
case TO:
- ((EmailHeader.AddressList) headers.get(FieldName.TO)).add(addr);
+ ((AddressList) headers.get(FieldName.TO)).add(addr);
break;
case CC:
- ((EmailHeader.AddressList) headers.get(FieldName.CC)).add(addr);
+ ((AddressList) headers.get(FieldName.CC)).add(addr);
break;
case BCC:
smtpBccRcptTo.add(addr);
@@ -653,14 +719,8 @@
return Address.create(account.fullName(), e);
}
- /**
- * Populate the email content.
- *
- * <p>Subclasses may override this method to populate further email content.
- *
- * @throws EmailException thrown if there is an error while populating the email content
- */
- protected void populateEmailContent() throws EmailException {
+ /** Set recipients, headers, body of the email. */
+ public void populateEmailContent() throws EmailException {
for (RecipientType recipientType : notify.accounts().keySet()) {
notify.accounts().get(recipientType).stream().forEach(a -> addByAccountId(recipientType, a));
}
@@ -670,8 +730,9 @@
addSoyEmailDataParam("settingsUrl", getSettingsUrl());
addSoyEmailDataParam("instanceName", getInstanceName());
addSoyEmailDataParam("gerritHost", getGerritHost());
- addSoyEmailDataParam("gerritUrl", getGerritUrl());
addSoyParam("email", soyContextEmailData);
+
+ templateProvider.populateEmailContent();
}
/** Adds param to the data map passed into soy when rendering templates. */
@@ -697,12 +758,12 @@
}
/** Renders a soy template of kind="text". */
- protected String textTemplate(String name) {
+ public String textTemplate(String name) {
return configureRenderer(name).renderText().get();
}
/** Renders a soy template of kind="html". */
- protected String soyHtmlTemplate(String name) {
+ public String soyHtmlTemplate(String name) {
return configureRenderer(name).renderHtml().get().toString();
}
@@ -726,7 +787,8 @@
return soySauce.renderTemplate(fullTemplateName).setData(soyContext);
}
- protected void removeUser(Account user) {
+ /** Remove user from the multipart email recipients. */
+ private void removeUser(Account user) {
String fromEmail = user.preferredEmail();
for (Iterator<Address> j = smtpRcptTo.iterator(); j.hasNext(); ) {
if (j.next().email().equals(fromEmail)) {
@@ -741,7 +803,8 @@
}
}
- protected final boolean useHtml() {
+ /** Return true, if the email should include html body. */
+ public boolean useHtml() {
return args.settings.html;
}
}
diff --git a/java/com/google/gerrit/server/mail/send/OutgoingEmailNew.java b/java/com/google/gerrit/server/mail/send/OutgoingEmailNew.java
deleted file mode 100644
index ae558b7..0000000
--- a/java/com/google/gerrit/server/mail/send/OutgoingEmailNew.java
+++ /dev/null
@@ -1,793 +0,0 @@
-// Copyright (C) 2016 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.mail.send;
-
-import static com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailStrategy.CC_ON_OWN_COMMENTS;
-import static com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailStrategy.DISABLED;
-import static java.util.Objects.requireNonNull;
-
-import com.google.auto.factory.AutoFactory;
-import com.google.auto.factory.Provided;
-import com.google.common.base.Throwables;
-import com.google.common.collect.Sets;
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.entities.Account;
-import com.google.gerrit.entities.Address;
-import com.google.gerrit.entities.EmailHeader;
-import com.google.gerrit.entities.EmailHeader.AddressList;
-import com.google.gerrit.entities.EmailHeader.StringEmailHeader;
-import com.google.gerrit.exceptions.EmailException;
-import com.google.gerrit.extensions.api.changes.RecipientType;
-import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
-import com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailFormat;
-import com.google.gerrit.mail.MailHeader;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.change.NotifyResolver;
-import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gerrit.server.update.RetryableAction.ActionType;
-import com.google.gerrit.server.validators.OutgoingEmailValidationListener;
-import com.google.gerrit.server.validators.ValidationException;
-import com.google.template.soy.jbcsrc.api.SoySauce;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.StringJoiner;
-import org.apache.james.mime4j.dom.field.FieldName;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.util.SystemReader;
-
-/** Represents an email notification for some event that can be sent to interested parties. */
-@AutoFactory
-public final class OutgoingEmailNew {
-
- /** Provides content, recipients and any customizations of the email. */
- public interface EmailDecorator {
- /**
- * Stores the reference to the email for the subsequent calls.
- *
- * <p>Both init and populateEmailContent can be called multiply times in case of retries. Init
- * is therefore responsible for clearing up any changes which are not idempotent and
- * initializing data for use in populateEmailContent.
- *
- * <p>Can be used to adjust any of the behaviour of the {@link
- * OutgoingEmailNew#populateEmailContent}.
- */
- void init(OutgoingEmailNew email) throws EmailException;
-
- /**
- * Populate headers, recipients and body of the email.
- *
- * <p>Method operates on the email provided in the init method.
- *
- * <p>By default, all the contents and parameters of the email should be set in this method.
- */
- void populateEmailContent() throws EmailException;
-
- /** If returns false email is not sent to any recipients. */
- default boolean shouldSendMessage() throws EmailException {
- return true;
- }
-
- /** Evaluates whether account can be added to the list of recipients. */
- default boolean isRecipientAllowed(Account.Id rcpt) throws PermissionBackendException {
- return true;
- }
-
- /** Evaluates whether email can be added to the list of recipients. */
- default boolean isRecipientAllowed(Address rcpt) throws PermissionBackendException {
- return true;
- }
- }
-
- private static final String SOY_TEMPLATE_NAMESPACE = "com.google.gerrit.server.mail.template";
- private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
- private String messageClass;
- private final Set<Account.Id> rcptTo = new HashSet<>();
- private final Map<String, EmailHeader> headers = new LinkedHashMap<>();;
- private final Set<Address> smtpRcptTo = new HashSet<>();
- private final Set<Address> smtpBccRcptTo = new HashSet<>();
- private Address smtpFromAddress;
- private StringBuilder textBody;
- private StringBuilder htmlBody;
- private MessageIdGenerator.MessageId messageId;
- private Map<String, Object> soyContext;
- private Map<String, Object> soyContextEmailData;
- private List<String> footers;
- private final EmailArguments args;
- private Account.Id fromId;
- private NotifyResolver.Result notify = NotifyResolver.Result.all();
- private final EmailDecorator templateProvider;
-
- public OutgoingEmailNew(
- @Provided EmailArguments args, String messageClass, EmailDecorator templateProvider) {
- this.args = args;
- this.messageClass = messageClass;
- this.templateProvider = templateProvider;
- }
-
- /** Specify the account that triggered the notification. */
- public void setFrom(Account.Id id) {
- fromId = id;
- }
-
- /** Get the account that triggered the notification. */
- public Account.Id getFrom() {
- return fromId;
- }
-
- /** Set how widely the email notification is allowed to be sent. */
- public void setNotify(NotifyResolver.Result notify) {
- this.notify = requireNonNull(notify);
- }
-
- /** Returns the setting that controls how widely the email notification is allowed to be sent. */
- public NotifyResolver.Result getNotify() {
- return this.notify;
- }
-
- /** Set identifier for the email. Every email must have one. */
- public void setMessageId(MessageIdGenerator.MessageId messageId) {
- this.messageId = messageId;
- }
-
- /** Format and enqueue the message for delivery. */
- public void send() throws EmailException {
- try {
- args.retryHelper
- .action(
- ActionType.SEND_EMAIL,
- "sendEmail",
- () -> {
- sendImpl();
- return null;
- })
- .retryWithTrace(Exception.class::isInstance)
- .call();
- } catch (Exception e) {
- Throwables.throwIfUnchecked(e);
- Throwables.throwIfInstanceOf(e, EmailException.class);
- throw new EmailException("sending email failed", e);
- }
- }
-
- private void sendImpl() throws EmailException {
- if (!args.emailSender.isEnabled()) {
- // Server has explicitly disabled email sending.
- //
- logger.atFine().log(
- "Not sending '%s': Email sending is disabled by server config", messageClass);
- return;
- }
-
- init();
- if (!notify.shouldNotify()) {
- logger.atFine().log("Not sending '%s': Notify handling is NONE", messageClass);
- return;
- }
- populateEmailContent();
- if (messageId == null) {
- throw new IllegalStateException("All emails must have a messageId");
- }
- appendText(textTemplate("Footer"));
- if (useHtml()) {
- appendHtml(soyHtmlTemplate("FooterHtml"));
- }
-
- Set<Address> smtpRcptToPlaintextOnly = new HashSet<>();
- if (shouldSendMessage()) {
- if (fromId != null) {
- Optional<AccountState> fromUser = args.accountCache.get(fromId);
- if (fromUser.isPresent()) {
- GeneralPreferencesInfo senderPrefs = fromUser.get().generalPreferences();
- CurrentUser user = args.currentUserProvider.get();
- boolean isImpersonating = user.isIdentifiedUser() && user.isImpersonating();
- if (isImpersonating && user.getAccountId() != fromId) {
- // This should not be possible, if this is the case it means the RequestContext is not
- // set up correctly.
- throw new EmailException(
- String.format(
- "User %s is sending email from %s, while acting on behalf of %s",
- user.asIdentifiedUser().getRealUser().getAccountId(),
- fromId,
- user.getAccountId()));
- }
- if (senderPrefs != null && senderPrefs.getEmailStrategy() == CC_ON_OWN_COMMENTS) {
- // Include the sender in email if they enabled email notifications on their own
- // comments.
- //
- logger.atFine().log(
- "CC email sender %s because the email strategy of this user is %s",
- fromUser.get().account().id(), CC_ON_OWN_COMMENTS);
- addByAccountId(RecipientType.CC, fromId);
- } else if (isImpersonating) {
- // If we are impersonating a user, make sure they receive a CC of
- // this message regardless of email strategy, unless email notifications are explicitly
- // disabled for this user. This way they can always review and audit what we sent
- // on their behalf to others.
- logger.atFine().log(
- "CC email sender %s because the email is sent on behalf of and email notifications"
- + " are enabled for this user.",
- fromUser.get().account().id());
- addByAccountId(RecipientType.CC, fromId);
-
- } else if (!notify.accounts().containsValue(fromId) && rcptTo.remove(fromId)) {
- // If they don't want a copy, but we queued one up anyway,
- // drop them from the recipient lists, but only if the user is not being impersonated.
- //
- logger.atFine().log(
- "Not CCing email sender %s because the email strategy of this user is not %s but"
- + " %s",
- fromUser.get().account().id(),
- CC_ON_OWN_COMMENTS,
- senderPrefs != null ? senderPrefs.getEmailStrategy() : null);
- removeUser(fromUser.get().account());
- }
- }
- }
- // Check the preferences of all recipients. If any user has disabled
- // his email notifications then drop him from recipients' list.
- // In addition, check if users only want to receive plaintext email.
- for (Account.Id id : rcptTo) {
- Optional<AccountState> thisUser = args.accountCache.get(id);
- if (thisUser.isPresent()) {
- Account thisUserAccount = thisUser.get().account();
- GeneralPreferencesInfo prefs = thisUser.get().generalPreferences();
- if (prefs == null || prefs.getEmailStrategy() == DISABLED) {
- logger.atFine().log(
- "Not emailing account %s because user has set email strategy to %s", id, DISABLED);
- removeUser(thisUserAccount);
- } else if (useHtml() && prefs.getEmailFormat() == EmailFormat.PLAINTEXT) {
- logger.atFine().log(
- "Removing account %s from HTML email because user prefers plain text emails", id);
- removeUser(thisUserAccount);
- smtpRcptToPlaintextOnly.add(
- Address.create(thisUserAccount.fullName(), thisUserAccount.preferredEmail()));
- }
- }
- if (smtpRcptTo.isEmpty() && smtpRcptToPlaintextOnly.isEmpty()) {
- logger.atFine().log("Not sending '%s': No SMTP recipients", messageClass);
- return;
- }
- }
-
- // Set Reply-To only if it hasn't been set by a child class
- // Reply-To will already be populated for the message types where Gerrit supports
- // inbound email replies.
- if (!headers.containsKey(FieldName.REPLY_TO)) {
- StringJoiner j = new StringJoiner(", ");
- if (fromId != null) {
- Address address = toAddress(fromId);
- if (address != null) {
- j.add(address.email());
- }
- }
- // For users who prefer plaintext, this comes at the cost of not being
- // listed in the multipart To and Cc headers. We work around this by adding
- // all users to the Reply-To address in both the plaintext and multipart
- // email. We should exclude any BCC addresses from reply-to, because they should be
- // invisible to other recipients.
- Sets.difference(Sets.union(smtpRcptTo, smtpRcptToPlaintextOnly), smtpBccRcptTo).stream()
- .forEach(a -> j.add(a.email()));
- setHeader(FieldName.REPLY_TO, j.toString());
- }
-
- String textPart = textBody.toString();
- OutgoingEmailValidationListener.Args va = new OutgoingEmailValidationListener.Args();
- va.messageClass = messageClass;
- va.smtpFromAddress = smtpFromAddress;
- va.smtpRcptTo = smtpRcptTo;
- va.headers = headers;
- va.body = textPart;
-
- if (useHtml()) {
- va.htmlBody = htmlBody.toString();
- } else {
- va.htmlBody = null;
- }
-
- Set<Address> intersection = Sets.intersection(va.smtpRcptTo, smtpRcptToPlaintextOnly);
- if (!intersection.isEmpty()) {
- logger.atSevere().log("Email '%s' will be sent twice to %s", messageClass, intersection);
- }
- if (!va.smtpRcptTo.isEmpty()) {
- // Send multipart message
- addMessageId(va, "-HTML");
- if (!validateEmail(va)) return;
- logger.atFine().log(
- "Sending multipart '%s' from %s to %s",
- messageClass, va.smtpFromAddress, va.smtpRcptTo);
- args.emailSender.send(va.smtpFromAddress, va.smtpRcptTo, va.headers, va.body, va.htmlBody);
- }
- if (!smtpRcptToPlaintextOnly.isEmpty()) {
- addMessageId(va, "-PLAIN");
- // Send plaintext message
- Map<String, EmailHeader> shallowCopy = new HashMap<>();
- shallowCopy.putAll(headers);
- // Remove To and Cc
- shallowCopy.remove(FieldName.TO);
- shallowCopy.remove(FieldName.CC);
- for (Address a : smtpRcptToPlaintextOnly) {
- // Add new To
- AddressList to = new AddressList();
- to.add(a);
- shallowCopy.put(FieldName.TO, to);
- }
- if (!validateEmail(va)) return;
- logger.atFine().log(
- "Sending plaintext '%s' from %s to %s",
- messageClass, va.smtpFromAddress, smtpRcptToPlaintextOnly);
- args.emailSender.send(va.smtpFromAddress, smtpRcptToPlaintextOnly, shallowCopy, va.body);
- }
- }
- }
-
- private boolean validateEmail(OutgoingEmailValidationListener.Args va) {
- for (OutgoingEmailValidationListener validator : args.outgoingEmailValidationListeners) {
- try {
- validator.validateOutgoingEmail(va);
- } catch (ValidationException e) {
- logger.atFine().log(
- "Not sending '%s': Rejected by outgoing email validator: %s",
- messageClass, e.getMessage());
- return false;
- }
- }
- return true;
- }
-
- // All message ids must start with < and end with >. Also, they must have @domain and no spaces.
- private void addMessageId(OutgoingEmailValidationListener.Args va, String suffix) {
- if (messageId != null) {
- String message = "<" + messageId.id() + suffix + "@" + getGerritHost() + ">";
- message = message.replaceAll("\\s", "");
- va.headers.put(FieldName.MESSAGE_ID, new StringEmailHeader(message));
- }
- }
-
- /**
- * Setup the message headers and envelope (TO, CC, BCC).
- *
- * @throws EmailException if an error occurred.
- */
- public void init() throws EmailException {
- soyContext = new HashMap<>();
- footers = new ArrayList<>();
- soyContextEmailData = new HashMap<>();
-
- smtpFromAddress = args.fromAddressGenerator.get().from(fromId);
- setHeader(FieldName.DATE, Instant.now());
- headers.put(FieldName.FROM, new AddressList(smtpFromAddress));
- headers.put(FieldName.TO, new AddressList());
- headers.put(FieldName.CC, new AddressList());
- setHeader(MailHeader.AUTO_SUBMITTED.fieldName(), "auto-generated");
-
- setHeader(MailHeader.MESSAGE_TYPE.fieldName(), messageClass);
- addFooter(MailHeader.MESSAGE_TYPE.withDelimiter() + messageClass);
- textBody = new StringBuilder();
- htmlBody = new StringBuilder();
-
- if (fromId != null && args.fromAddressGenerator.get().isGenericAddress(fromId)) {
- appendText(getFromLine());
- }
-
- templateProvider.init(this);
- }
-
- private String getFromLine() {
- StringBuilder f = new StringBuilder();
- Optional<Account> account = args.accountCache.get(fromId).map(AccountState::account);
- if (account.isPresent()) {
- String name = account.get().fullName();
- String email = account.get().preferredEmail();
- if ((name != null && !name.isEmpty()) || (email != null && !email.isEmpty())) {
- f.append("From");
- if (name != null && !name.isEmpty()) {
- f.append(" ").append(name);
- }
- if (email != null && !email.isEmpty()) {
- f.append(" <").append(email).append(">");
- }
- f.append(":\n\n");
- }
- }
- return f.toString();
- }
-
- public String getGerritHost() {
- if (getGerritUrl() != null) {
- try {
- return new URL(getGerritUrl()).getHost();
- } catch (MalformedURLException e) {
- // Try something else.
- }
- }
-
- // Fall back onto whatever the local operating system thinks
- // this server is called. We hopefully didn't get here as a
- // good admin would have configured the canonical url.
- //
- return SystemReader.getInstance().getHostname();
- }
-
- @Nullable
- public String getSettingsUrl() {
- return args.urlFormatter.get().getSettingsUrl().orElse(null);
- }
-
- @Nullable
- private String getGerritUrl() {
- return args.urlFormatter.get().getWebUrl().orElse(null);
- }
-
- /** Set a header in the outgoing message. */
- public void setHeader(String name, String value) {
- headers.put(name, new StringEmailHeader(value));
- }
-
- /** Remove a header from the outgoing message. */
- public void removeHeader(String name) {
- headers.remove(name);
- }
-
- /** Set a date header in the outgoing message. */
- public void setHeader(String name, Instant date) {
- headers.put(name, new EmailHeader.Date(date));
- }
-
- /** Append text to the outgoing email body. */
- public void appendText(String text) {
- if (text != null) {
- textBody.append(text);
- }
- }
-
- /** Append html to the outgoing email body. */
- public void appendHtml(String html) {
- if (html != null) {
- htmlBody.append(html);
- }
- }
-
- /** Lookup a human readable name for an account, usually the "full name". */
- public String getNameFor(@Nullable Account.Id accountId) {
- if (accountId == null) {
- return args.gerritPersonIdent.get().getName();
- }
-
- Optional<Account> account = args.accountCache.get(accountId).map(AccountState::account);
- String name = null;
- if (account.isPresent()) {
- name = account.get().fullName();
- if (name == null) {
- name = account.get().preferredEmail();
- }
- }
- if (name == null) {
- name = args.anonymousCowardName + " #" + accountId;
- }
- return name;
- }
-
- /**
- * Gets the human readable name and email for an account; if neither are available, returns the
- * Anonymous Coward name.
- *
- * @param accountId user to fetch.
- * @return name/email of account, or Anonymous Coward if unset.
- */
- public String getNameEmailFor(@Nullable Account.Id accountId) {
- if (accountId == null) {
- PersonIdent gerritIdent = args.gerritPersonIdent.get();
- return gerritIdent.getName() + " <" + gerritIdent.getEmailAddress() + ">";
- }
-
- Optional<Account> account = args.accountCache.get(accountId).map(AccountState::account);
- if (account.isPresent()) {
- String name = account.get().fullName();
- String email = account.get().preferredEmail();
- if (name != null && email != null) {
- return name + " <" + email + ">";
- } else if (name != null) {
- return name;
- } else if (email != null) {
- return email;
- }
- }
- return args.anonymousCowardName + " #" + accountId;
- }
-
- /**
- * Gets the human readable name and email for an account; if both are unavailable, returns the
- * username. If no username is set, this function returns null.
- *
- * @param accountId user to fetch.
- * @return name/email of account, username, or null if unset or the accountId is null.
- */
- @Nullable
- public String getUserNameEmailFor(@Nullable Account.Id accountId) {
- if (accountId == null) {
- return null;
- }
-
- Optional<AccountState> accountState = args.accountCache.get(accountId);
- if (!accountState.isPresent()) {
- return null;
- }
-
- Account account = accountState.get().account();
- String name = account.fullName();
- String email = account.preferredEmail();
- if (name != null && email != null) {
- return name + " <" + email + ">";
- } else if (email != null) {
- return email;
- } else if (name != null) {
- return name;
- }
- return accountState.get().userName().orElse(null);
- }
-
- private boolean shouldSendMessage() throws EmailException {
- if (textBody.length() == 0) {
- // If we have no message body, don't send.
- logger.atFine().log("Not sending '%s': No message body", messageClass);
- return false;
- }
-
- if (smtpRcptTo.isEmpty()) {
- // If we have nobody to send this message to, then all of our
- // selection filters previously for this type of message were
- // unable to match a destination. Don't bother sending it.
- logger.atFine().log("Not sending '%s': No recipients", messageClass);
- return false;
- }
-
- if (notify.accounts().isEmpty()
- && smtpRcptTo.size() == 1
- && rcptTo.size() == 1
- && rcptTo.contains(fromId)) {
- // If the only recipient is also the sender, don't bother.
- //
- logger.atFine().log("Not sending '%s': Sender is only recipient", messageClass);
- return false;
- }
-
- return templateProvider.shouldSendMessage();
- }
-
- /**
- * Adds a recipient that the email will be sent to.
- *
- * @param rt category of recipient (TO, CC, BCC)
- * @param addr Name and email of the recipient.
- */
- public final void addByEmail(RecipientType rt, Address addr) {
- addByEmail(rt, addr, false);
- }
-
- /**
- * Adds a recipient that the email will be sent to.
- *
- * @param rt category of recipient (TO, CC, BCC).
- * @param addr Name and email of the recipient.
- * @param override if the recipient was added previously and override is false no change is made
- * regardless of {@code rt}.
- */
- public final void addByEmail(RecipientType rt, Address addr, boolean override) {
- try {
- if (isRecipientAllowed(addr)) {
- add(rt, addr, override);
- }
- } catch (PermissionBackendException e) {
- logger.atSevere().withCause(e).log("Error checking permissions for email address: %s", addr);
- }
- }
-
- /**
- * Returns whether this email is allowed to be sent to the given address
- *
- * @param addr email address of recipient.
- * @throws PermissionBackendException thrown if checking a permission fails due to an error in the
- * permission backend
- */
- public boolean isRecipientAllowed(Address addr) throws PermissionBackendException {
- return templateProvider.isRecipientAllowed(addr);
- }
-
- /**
- * Adds a recipient that the email will be sent to.
- *
- * @param rt category of recipient (TO, CC, BCC)
- * @param to Gerrit Account of the recipient.
- */
- public void addByAccountId(RecipientType rt, Account.Id to) {
- addByAccountId(rt, to, false);
- }
-
- /**
- * Adds a recipient that the email will be sent to.
- *
- * @param rt category of recipient (TO, CC, BCC)
- * @param to Gerrit Account of the recipient.
- * @param override if the recipient was added previously and override is false no change is made
- * regardless of {@code rt}.
- */
- public void addByAccountId(RecipientType rt, Account.Id to, boolean override) {
- try {
- if (!rcptTo.contains(to) && isRecipientAllowed(to)) {
- rcptTo.add(to);
- add(rt, toAddress(to), override);
- }
- } catch (PermissionBackendException e) {
- logger.atSevere().withCause(e).log("Error checking permissions for account: %s", to);
- }
- }
-
- /**
- * Returns whether this email is allowed to be sent to the given account
- *
- * @param to account.
- * @throws PermissionBackendException thrown if checking a permission fails due to an error in the
- * permission backend
- */
- public boolean isRecipientAllowed(Account.Id to) throws PermissionBackendException {
- return templateProvider.isRecipientAllowed(to);
- }
-
- private final void add(RecipientType rt, Address addr, boolean override) {
- if (addr != null && addr.email() != null && addr.email().length() > 0) {
- if (!args.validator.isValid(addr.email())) {
- logger.atWarning().log("Not emailing %s (invalid email address)", addr.email());
- } else if (args.emailSender.canEmail(addr.email())) {
- if (!smtpRcptTo.add(addr)) {
- if (!override) {
- return;
- }
- ((AddressList) headers.get(FieldName.TO)).remove(addr.email());
- ((AddressList) headers.get(FieldName.CC)).remove(addr.email());
- smtpBccRcptTo.remove(addr);
- }
- switch (rt) {
- case TO:
- ((AddressList) headers.get(FieldName.TO)).add(addr);
- break;
- case CC:
- ((AddressList) headers.get(FieldName.CC)).add(addr);
- break;
- case BCC:
- smtpBccRcptTo.add(addr);
- break;
- }
- }
- }
- }
-
- @Nullable
- private Address toAddress(Account.Id id) {
- Optional<Account> accountState = args.accountCache.get(id).map(AccountState::account);
- if (!accountState.isPresent()) {
- return null;
- }
-
- Account account = accountState.get();
- String e = account.preferredEmail();
- if (!account.isActive() || e == null) {
- return null;
- }
- return Address.create(account.fullName(), e);
- }
-
- /** Set recipients, headers, body of the email. */
- public void populateEmailContent() throws EmailException {
- for (RecipientType recipientType : notify.accounts().keySet()) {
- notify.accounts().get(recipientType).stream().forEach(a -> addByAccountId(recipientType, a));
- }
-
- addSoyParam("messageClass", messageClass);
- addSoyParam("footers", footers);
- addSoyEmailDataParam("settingsUrl", getSettingsUrl());
- addSoyEmailDataParam("instanceName", getInstanceName());
- addSoyEmailDataParam("gerritHost", getGerritHost());
- addSoyEmailDataParam("gerritUrl", getGerritUrl());
- addSoyParam("email", soyContextEmailData);
-
- templateProvider.populateEmailContent();
- }
-
- /** Adds param to the data map passed into soy when rendering templates. */
- public void addSoyParam(String key, Object value) {
- soyContext.put(key, value);
- }
-
- /** Adds entry to the `email` param passed to the soy when rendering templates. */
- public void addSoyEmailDataParam(String key, Object value) {
- soyContextEmailData.put(key, value);
- }
-
- /**
- * Add a line to email footer with additional information. Typically, in the form of {@literal
- * <key>: <value>}.
- */
- public void addFooter(String footer) {
- footers.add(footer);
- }
-
- private String getInstanceName() {
- return args.instanceNameProvider.get();
- }
-
- /** Renders a soy template of kind="text". */
- public String textTemplate(String name) {
- return configureRenderer(name).renderText().get();
- }
-
- /** Renders a soy template of kind="html". */
- public String soyHtmlTemplate(String name) {
- return configureRenderer(name).renderHtml().get().toString();
- }
-
- /** Configures a soy renderer for the given template name and rendering data map. */
- private SoySauce.Renderer configureRenderer(String templateName) {
- int baseNameIndex = templateName.indexOf("_");
- // In case there are multiple templates in file (now only InboundEmailRejection and
- // InboundEmailRejectionHtml).
- String fileNamespace =
- baseNameIndex == -1 ? templateName : templateName.substring(0, baseNameIndex);
- String templateInFileNamespace =
- String.join(".", SOY_TEMPLATE_NAMESPACE, fileNamespace, templateName);
- String templateInCommonNamespace = String.join(".", SOY_TEMPLATE_NAMESPACE, templateName);
- SoySauce soySauce = args.soySauce.get();
- // For backwards compatibility with existing customizations and plugin templates with the
- // old non-unique namespace.
- String fullTemplateName =
- soySauce.hasTemplate(templateInFileNamespace)
- ? templateInFileNamespace
- : templateInCommonNamespace;
- return soySauce.renderTemplate(fullTemplateName).setData(soyContext);
- }
-
- /** Remove user from the multipart email recipients. */
- private void removeUser(Account user) {
- String fromEmail = user.preferredEmail();
- for (Iterator<Address> j = smtpRcptTo.iterator(); j.hasNext(); ) {
- if (j.next().email().equals(fromEmail)) {
- j.remove();
- }
- }
- for (Map.Entry<String, EmailHeader> entry : headers.entrySet()) {
- // Don't remove fromEmail from the "From" header though!
- if (entry.getValue() instanceof AddressList && !entry.getKey().equals("From")) {
- ((AddressList) entry.getValue()).remove(fromEmail);
- }
- }
- }
-
- /** Return true, if the email should include html body. */
- public boolean useHtml() {
- return args.settings.html;
- }
-}
diff --git a/java/com/google/gerrit/server/mail/send/RegisterNewEmailDecorator.java b/java/com/google/gerrit/server/mail/send/RegisterNewEmailDecorator.java
new file mode 100644
index 0000000..c82a016
--- /dev/null
+++ b/java/com/google/gerrit/server/mail/send/RegisterNewEmailDecorator.java
@@ -0,0 +1,80 @@
+// Copyright (C) 2009 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.mail.send;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
+import com.google.gerrit.entities.Address;
+import com.google.gerrit.extensions.api.changes.RecipientType;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.mail.EmailTokenVerifier;
+import com.google.gerrit.server.mail.send.OutgoingEmail.EmailDecorator;
+
+/**
+ * Sender that informs a user by email about the registration of a new email address for their
+ * account.
+ */
+@AutoFactory
+public class RegisterNewEmailDecorator implements EmailDecorator {
+ private OutgoingEmail email;
+ private final EmailArguments args;
+ private final EmailTokenVerifier tokenVerifier;
+ private final IdentifiedUser user;
+ private final String addr;
+ private String emailToken;
+
+ RegisterNewEmailDecorator(
+ @Provided EmailArguments args,
+ @Provided EmailTokenVerifier tokenVerifier,
+ @Provided IdentifiedUser callingUser,
+ final String address) {
+ this.args = args;
+ this.tokenVerifier = tokenVerifier;
+ this.user = callingUser;
+ this.addr = address;
+ }
+
+ @Override
+ public void init(OutgoingEmail email) {
+ this.email = email;
+
+ email.setHeader("Subject", "[Gerrit Code Review] Email Verification");
+ email.addByEmail(RecipientType.TO, Address.create(addr));
+ }
+
+ public boolean isAllowed() {
+ return args.emailSender.canEmail(addr);
+ }
+
+ @Override
+ public void populateEmailContent() {
+ email.addSoyEmailDataParam("userNameEmail", email.getUserNameEmailFor(user.getAccountId()));
+ email.addSoyEmailDataParam("emailRegistrationLink", getEmailRegistrationLink());
+
+ email.appendText(email.textTemplate("RegisterNewEmail"));
+ if (email.useHtml()) {
+ email.appendHtml(email.soyHtmlTemplate("RegisterNewEmailHtml"));
+ }
+ }
+
+ private String getEmailRegistrationLink() {
+ if (emailToken == null) {
+ emailToken = requireNonNull(tokenVerifier.encode(user.getAccountId(), addr), "token");
+ }
+ return args.urlFormatter.get().getWebUrl().orElse("") + "#/VE/" + emailToken;
+ }
+}
diff --git a/java/com/google/gerrit/server/mail/send/RegisterNewEmailSender.java b/java/com/google/gerrit/server/mail/send/RegisterNewEmailSender.java
deleted file mode 100644
index 73c5fb5..0000000
--- a/java/com/google/gerrit/server/mail/send/RegisterNewEmailSender.java
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (C) 2009 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.mail.send;
-
-import static java.util.Objects.requireNonNull;
-
-import com.google.gerrit.entities.Address;
-import com.google.gerrit.exceptions.EmailException;
-import com.google.gerrit.extensions.api.changes.RecipientType;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.mail.EmailTokenVerifier;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-/**
- * Sender that informs a user by email about the registration of a new email address for their
- * account.
- */
-public class RegisterNewEmailSender extends OutgoingEmail {
- public interface Factory {
- RegisterNewEmailSender create(String address);
- }
-
- private final EmailTokenVerifier tokenVerifier;
- private final IdentifiedUser user;
- private final String addr;
- private String emailToken;
-
- @Inject
- public RegisterNewEmailSender(
- EmailArguments args,
- EmailTokenVerifier tokenVerifier,
- IdentifiedUser callingUser,
- @Assisted final String address) {
- super(args, "registernewemail");
- this.tokenVerifier = tokenVerifier;
- this.user = callingUser;
- this.addr = address;
- }
-
- @Override
- protected void init() throws EmailException {
- super.init();
- setHeader("Subject", "[Gerrit Code Review] Email Verification");
- addByEmail(RecipientType.TO, Address.create(addr));
- }
-
- @Override
- protected void format() throws EmailException {
- appendText(textTemplate("RegisterNewEmail"));
- if (useHtml()) {
- appendHtml(soyHtmlTemplate("RegisterNewEmailHtml"));
- }
- }
-
- public boolean isAllowed() {
- return args.emailSender.canEmail(addr);
- }
-
- @Override
- protected void populateEmailContent() throws EmailException {
- super.populateEmailContent();
- addSoyEmailDataParam("emailRegistrationToken", getEmailRegistrationToken());
- addSoyEmailDataParam("userNameEmail", getUserNameEmailFor(user.getAccountId()));
- }
-
- private String getEmailRegistrationToken() {
- if (emailToken == null) {
- emailToken = requireNonNull(tokenVerifier.encode(user.getAccountId(), addr), "token");
- }
- return emailToken;
- }
-}
diff --git a/java/com/google/gerrit/server/mail/send/ReplacePatchSetChangeEmailDecorator.java b/java/com/google/gerrit/server/mail/send/ReplacePatchSetChangeEmailDecorator.java
index a9bd32e..a73933c 100644
--- a/java/com/google/gerrit/server/mail/send/ReplacePatchSetChangeEmailDecorator.java
+++ b/java/com/google/gerrit/server/mail/send/ReplacePatchSetChangeEmailDecorator.java
@@ -33,7 +33,7 @@
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.api.changes.RecipientType;
import com.google.gerrit.extensions.client.ChangeKind;
-import com.google.gerrit.server.mail.send.ChangeEmailNew.ChangeEmailDecorator;
+import com.google.gerrit.server.mail.send.ChangeEmail.ChangeEmailDecorator;
import com.google.gerrit.server.util.LabelVote;
import java.util.ArrayList;
import java.util.Collection;
@@ -49,8 +49,8 @@
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final EmailArguments args;
- private OutgoingEmailNew email;
- private ChangeEmailNew changeEmail;
+ private OutgoingEmail email;
+ private ChangeEmail changeEmail;
private final Set<Account.Id> reviewers = new HashSet<>();
private final Set<Account.Id> extraCC = new HashSet<>();
private final ChangeKind changeKind;
@@ -106,7 +106,7 @@
}
@Override
- public void init(OutgoingEmailNew email, ChangeEmailNew changeEmail) {
+ public void init(OutgoingEmail email, ChangeEmail changeEmail) {
this.email = email;
this.changeEmail = changeEmail;
changeEmail.markAsReply();
diff --git a/java/com/google/gerrit/server/mail/send/ReplyToChangeSender.java b/java/com/google/gerrit/server/mail/send/ReplyToChangeSender.java
deleted file mode 100644
index 1f7b6d5..0000000
--- a/java/com/google/gerrit/server/mail/send/ReplyToChangeSender.java
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright (C) 2009 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.mail.send;
-
-import com.google.gerrit.entities.Change;
-import com.google.gerrit.entities.Project;
-import com.google.gerrit.exceptions.EmailException;
-import com.google.gerrit.extensions.api.changes.RecipientType;
-import com.google.gerrit.server.query.change.ChangeData;
-
-/** Alert a user to a reply to a change, usually commentary made during review. */
-public abstract class ReplyToChangeSender extends ChangeEmail {
- public interface Factory<T extends ReplyToChangeSender> {
- T create(Project.NameKey project, Change.Id id);
- }
-
- protected ReplyToChangeSender(EmailArguments args, String messageClass, ChangeData changeData) {
- super(args, messageClass, changeData);
- }
-
- @Override
- protected void populateEmailContent() throws EmailException {
- super.populateEmailContent();
-
- final String threadId = getChangeMessageThreadId();
- setHeader("In-Reply-To", threadId);
- setHeader("References", threadId);
-
- addAuthors(RecipientType.TO);
- }
-}
diff --git a/java/com/google/gerrit/server/mail/send/RestoredChangeEmailDecorator.java b/java/com/google/gerrit/server/mail/send/RestoredChangeEmailDecorator.java
index d8c2696..38eab48 100644
--- a/java/com/google/gerrit/server/mail/send/RestoredChangeEmailDecorator.java
+++ b/java/com/google/gerrit/server/mail/send/RestoredChangeEmailDecorator.java
@@ -16,15 +16,15 @@
import com.google.gerrit.entities.NotifyConfig.NotifyType;
import com.google.gerrit.extensions.api.changes.RecipientType;
-import com.google.gerrit.server.mail.send.ChangeEmailNew.ChangeEmailDecorator;
+import com.google.gerrit.server.mail.send.ChangeEmail.ChangeEmailDecorator;
/** Send notice about a change being restored by its owner. */
public class RestoredChangeEmailDecorator implements ChangeEmailDecorator {
- private OutgoingEmailNew email;
- private ChangeEmailNew changeEmail;
+ private OutgoingEmail email;
+ private ChangeEmail changeEmail;
@Override
- public void init(OutgoingEmailNew email, ChangeEmailNew changeEmail) {
+ public void init(OutgoingEmail email, ChangeEmail changeEmail) {
this.email = email;
this.changeEmail = changeEmail;
changeEmail.markAsReply();
diff --git a/java/com/google/gerrit/server/mail/send/RevertedChangeEmailDecorator.java b/java/com/google/gerrit/server/mail/send/RevertedChangeEmailDecorator.java
index 2a802f3..d1cff9c 100644
--- a/java/com/google/gerrit/server/mail/send/RevertedChangeEmailDecorator.java
+++ b/java/com/google/gerrit/server/mail/send/RevertedChangeEmailDecorator.java
@@ -16,15 +16,15 @@
import com.google.gerrit.entities.NotifyConfig.NotifyType;
import com.google.gerrit.extensions.api.changes.RecipientType;
-import com.google.gerrit.server.mail.send.ChangeEmailNew.ChangeEmailDecorator;
+import com.google.gerrit.server.mail.send.ChangeEmail.ChangeEmailDecorator;
/** Send notice about a change being reverted. */
public class RevertedChangeEmailDecorator implements ChangeEmailDecorator {
- private OutgoingEmailNew email;
- private ChangeEmailNew changeEmail;
+ private OutgoingEmail email;
+ private ChangeEmail changeEmail;
@Override
- public void init(OutgoingEmailNew email, ChangeEmailNew changeEmail) {
+ public void init(OutgoingEmail email, ChangeEmail changeEmail) {
this.email = email;
this.changeEmail = changeEmail;
changeEmail.markAsReply();
diff --git a/java/com/google/gerrit/server/mail/send/StartReviewChangeEmailDecorator.java b/java/com/google/gerrit/server/mail/send/StartReviewChangeEmailDecorator.java
new file mode 100644
index 0000000..6ae1c6a
--- /dev/null
+++ b/java/com/google/gerrit/server/mail/send/StartReviewChangeEmailDecorator.java
@@ -0,0 +1,143 @@
+// Copyright (C) 2009 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.mail.send;
+
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Address;
+import com.google.gerrit.entities.NotifyConfig.NotifyType;
+import com.google.gerrit.extensions.api.changes.RecipientType;
+import com.google.gerrit.server.mail.send.ChangeEmail.ChangeEmailDecorator;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/** Sends an email alerting a user to a new change for them to review. */
+public class StartReviewChangeEmailDecorator implements ChangeEmailDecorator {
+ private OutgoingEmail email;
+ private ChangeEmail changeEmail;
+
+ private final Set<Account.Id> reviewers = new HashSet<>();
+ private final Set<Address> reviewersByEmail = new HashSet<>();
+ private final Set<Account.Id> extraCC = new HashSet<>();
+ private final Set<Address> extraCCByEmail = new HashSet<>();
+ private final Set<Account.Id> removedReviewers = new HashSet<>();
+ private final Set<Address> removedByEmailReviewers = new HashSet<>();
+ private boolean isCreateChange = false;
+
+ public void addReviewers(Collection<Account.Id> cc) {
+ reviewers.addAll(cc);
+ }
+
+ public void addReviewersByEmail(Collection<Address> cc) {
+ reviewersByEmail.addAll(cc);
+ }
+
+ public void addExtraCC(Collection<Account.Id> cc) {
+ extraCC.addAll(cc);
+ }
+
+ public void addExtraCCByEmail(Collection<Address> cc) {
+ extraCCByEmail.addAll(cc);
+ }
+
+ public void addRemovedReviewers(Collection<Account.Id> removed) {
+ removedReviewers.addAll(removed);
+ }
+
+ public void addRemovedByEmailReviewers(Collection<Address> removed) {
+ removedByEmailReviewers.addAll(removed);
+ }
+
+ public void markAsCreateChange() {
+ isCreateChange = true;
+ }
+
+ @Override
+ public void init(OutgoingEmail email, ChangeEmail changeEmail) {
+ this.email = email;
+ this.changeEmail = changeEmail;
+ }
+
+ @Nullable
+ private List<String> getReviewerNames() {
+ if (reviewers.isEmpty()) {
+ return null;
+ }
+ List<String> names = new ArrayList<>();
+ for (Account.Id id : reviewers) {
+ names.add(email.getNameFor(id));
+ }
+ return names;
+ }
+
+ @Nullable
+ private List<String> getRemovedReviewerNames() {
+ if (removedReviewers.isEmpty() && removedByEmailReviewers.isEmpty()) {
+ return null;
+ }
+ List<String> names = new ArrayList<>();
+ for (Account.Id id : removedReviewers) {
+ names.add(email.getNameFor(id));
+ }
+ for (Address address : removedByEmailReviewers) {
+ names.add(address.toString());
+ }
+ return names;
+ }
+
+ @Override
+ public void populateEmailContent() {
+ email.addSoyParam("ownerName", email.getNameFor(changeEmail.getChange().getOwner()));
+ email.addSoyEmailDataParam("reviewerNames", getReviewerNames());
+ email.addSoyEmailDataParam("removedReviewerNames", getRemovedReviewerNames());
+
+ switch (email.getNotify().handling()) {
+ case NONE:
+ case OWNER:
+ break;
+ case ALL:
+ default:
+ extraCC.stream().forEach(cc -> email.addByAccountId(RecipientType.CC, cc));
+ extraCCByEmail.stream().forEach(cc -> email.addByEmail(RecipientType.CC, cc));
+ // $FALL-THROUGH$
+ case OWNER_REVIEWERS:
+ reviewers.stream().forEach(r -> email.addByAccountId(RecipientType.TO, r, true));
+ reviewersByEmail.stream().forEach(r -> email.addByEmail(RecipientType.TO, r, true));
+ removedReviewers.stream().forEach(r -> email.addByAccountId(RecipientType.TO, r, true));
+ removedByEmailReviewers.stream().forEach(r -> email.addByEmail(RecipientType.TO, r, true));
+ break;
+ }
+ changeEmail.addAuthors(RecipientType.CC);
+
+ if (isCreateChange) {
+ changeEmail.includeWatchers(
+ NotifyType.NEW_CHANGES,
+ !changeEmail.getChange().isWorkInProgress() && !changeEmail.getChange().isPrivate());
+ changeEmail.includeWatchers(
+ NotifyType.NEW_PATCHSETS,
+ !changeEmail.getChange().isWorkInProgress() && !changeEmail.getChange().isPrivate());
+ } else {
+ changeEmail.ccExistingReviewers();
+ }
+
+ email.appendText(email.textTemplate("NewChange"));
+ if (email.useHtml()) {
+ email.appendHtml(email.soyHtmlTemplate("NewChangeHtml"));
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/restapi/account/AddSshKey.java b/java/com/google/gerrit/server/restapi/account/AddSshKey.java
index f18cc67..2544d3b 100644
--- a/java/com/google/gerrit/server/restapi/account/AddSshKey.java
+++ b/java/com/google/gerrit/server/restapi/account/AddSshKey.java
@@ -32,7 +32,7 @@
import com.google.gerrit.server.account.AccountResource;
import com.google.gerrit.server.account.AccountSshKey;
import com.google.gerrit.server.account.VersionedAuthorizedKeys;
-import com.google.gerrit.server.mail.send.AddKeySender;
+import com.google.gerrit.server.mail.EmailModule.AddKeyEmailFactories;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -58,7 +58,7 @@
private final PermissionBackend permissionBackend;
private final VersionedAuthorizedKeys.Accessor authorizedKeys;
private final SshKeyCache sshKeyCache;
- private final AddKeySender.Factory addKeyFactory;
+ private final AddKeyEmailFactories addKeyEmailFactories;
@Inject
AddSshKey(
@@ -66,12 +66,12 @@
PermissionBackend permissionBackend,
VersionedAuthorizedKeys.Accessor authorizedKeys,
SshKeyCache sshKeyCache,
- AddKeySender.Factory addKeyFactory) {
+ AddKeyEmailFactories addKeyEmailFactories) {
this.self = self;
this.permissionBackend = permissionBackend;
this.authorizedKeys = authorizedKeys;
this.sshKeyCache = sshKeyCache;
- this.addKeyFactory = addKeyFactory;
+ this.addKeyEmailFactories = addKeyEmailFactories;
}
@Override
@@ -106,7 +106,7 @@
AccountSshKey sshKey = authorizedKeys.addKey(user.getAccountId(), sshPublicKey);
try {
- addKeyFactory.create(user, sshKey).send();
+ addKeyEmailFactories.createEmail(user, sshKey).send();
} catch (EmailException e) {
logger.atSevere().withCause(e).log(
"Cannot send SSH key added message to %s", user.getAccount().preferredEmail());
diff --git a/java/com/google/gerrit/server/restapi/account/CreateEmail.java b/java/com/google/gerrit/server/restapi/account/CreateEmail.java
index 70fbb26..92a7722 100644
--- a/java/com/google/gerrit/server/restapi/account/CreateEmail.java
+++ b/java/com/google/gerrit/server/restapi/account/CreateEmail.java
@@ -37,9 +37,11 @@
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.config.AuthConfig;
+import com.google.gerrit.server.mail.EmailModule.RegisterNewEmailFactories;
import com.google.gerrit.server.mail.send.MessageIdGenerator;
+import com.google.gerrit.server.mail.send.OutgoingEmail;
import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
-import com.google.gerrit.server.mail.send.RegisterNewEmailSender;
+import com.google.gerrit.server.mail.send.RegisterNewEmailDecorator;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -78,7 +80,7 @@
private final Realm realm;
private final PermissionBackend permissionBackend;
private final AccountManager accountManager;
- private final RegisterNewEmailSender.Factory registerNewEmailFactory;
+ private final RegisterNewEmailFactories registerNewEmailFactories;
private final PutPreferred putPreferred;
private final OutgoingEmailValidator validator;
private final MessageIdGenerator messageIdGenerator;
@@ -92,7 +94,7 @@
PermissionBackend permissionBackend,
AuthConfig authConfig,
AccountManager accountManager,
- RegisterNewEmailSender.Factory registerNewEmailFactory,
+ RegisterNewEmailFactories registerNewEmailFactories,
PutPreferred putPreferred,
OutgoingEmailValidator validator,
MessageIdGenerator messageIdGenerator,
@@ -101,7 +103,7 @@
this.realm = realm;
this.permissionBackend = permissionBackend;
this.accountManager = accountManager;
- this.registerNewEmailFactory = registerNewEmailFactory;
+ this.registerNewEmailFactories = registerNewEmailFactories;
this.putPreferred = putPreferred;
this.validator = validator;
this.isDevMode = authConfig.getAuthType() == DEVELOPMENT_BECOME_ANY_ACCOUNT;
@@ -164,12 +166,14 @@
}
} else {
try {
- RegisterNewEmailSender emailSender = registerNewEmailFactory.create(email);
- if (!emailSender.isAllowed()) {
+ RegisterNewEmailDecorator emailDecorator =
+ registerNewEmailFactories.createRegisterNewEmail(email);
+ if (!emailDecorator.isAllowed()) {
throw new MethodNotAllowedException("Not allowed to add email address " + email);
}
- emailSender.setMessageId(messageIdGenerator.fromAccountUpdate(user.getAccountId()));
- emailSender.send();
+ OutgoingEmail outgoingEmail = registerNewEmailFactories.createEmail(emailDecorator);
+ outgoingEmail.setMessageId(messageIdGenerator.fromAccountUpdate(user.getAccountId()));
+ outgoingEmail.send();
info.pendingConfirmation = true;
} catch (EmailException | RuntimeException e) {
logger.atSevere().withCause(e).log("Cannot send email verification message to %s", email);
diff --git a/java/com/google/gerrit/server/restapi/account/DeleteSshKey.java b/java/com/google/gerrit/server/restapi/account/DeleteSshKey.java
index e09e48f..61d43d2 100644
--- a/java/com/google/gerrit/server/restapi/account/DeleteSshKey.java
+++ b/java/com/google/gerrit/server/restapi/account/DeleteSshKey.java
@@ -25,7 +25,7 @@
import com.google.gerrit.server.account.AccountResource;
import com.google.gerrit.server.account.AccountSshKey;
import com.google.gerrit.server.account.VersionedAuthorizedKeys;
-import com.google.gerrit.server.mail.send.DeleteKeySender;
+import com.google.gerrit.server.mail.EmailModule.DeleteKeyEmailFactories;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -51,7 +51,7 @@
private final PermissionBackend permissionBackend;
private final VersionedAuthorizedKeys.Accessor authorizedKeys;
private final SshKeyCache sshKeyCache;
- private final DeleteKeySender.Factory deleteKeySenderFactory;
+ private final DeleteKeyEmailFactories deleteKeyEmailFactories;
@Inject
DeleteSshKey(
@@ -59,12 +59,12 @@
PermissionBackend permissionBackend,
VersionedAuthorizedKeys.Accessor authorizedKeys,
SshKeyCache sshKeyCache,
- DeleteKeySender.Factory deleteKeySenderFactory) {
+ DeleteKeyEmailFactories deleteKeyEmailFactories) {
this.self = self;
this.permissionBackend = permissionBackend;
this.authorizedKeys = authorizedKeys;
this.sshKeyCache = sshKeyCache;
- this.deleteKeySenderFactory = deleteKeySenderFactory;
+ this.deleteKeyEmailFactories = deleteKeyEmailFactories;
}
@Override
@@ -82,7 +82,7 @@
throws RepositoryNotFoundException, IOException, ConfigInvalidException {
authorizedKeys.deleteKey(user.getAccountId(), sshKey.seq());
try {
- deleteKeySenderFactory.create(user, sshKey).send();
+ deleteKeyEmailFactories.createEmail(user, sshKey).send();
} catch (EmailException e) {
logger.atSevere().withCause(e).log(
"Cannot send SSH key deletion message to %s", user.getAccount().preferredEmail());
diff --git a/java/com/google/gerrit/server/restapi/account/PutHttpPassword.java b/java/com/google/gerrit/server/restapi/account/PutHttpPassword.java
index 9361e27..edfc41c 100644
--- a/java/com/google/gerrit/server/restapi/account/PutHttpPassword.java
+++ b/java/com/google/gerrit/server/restapi/account/PutHttpPassword.java
@@ -37,7 +37,7 @@
import com.google.gerrit.server.account.externalids.ExternalIdFactory;
import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
import com.google.gerrit.server.account.externalids.ExternalIds;
-import com.google.gerrit.server.mail.send.HttpPasswordUpdateSender;
+import com.google.gerrit.server.mail.EmailModule.HttpPasswordUpdateEmailFactory;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -78,7 +78,7 @@
private final PermissionBackend permissionBackend;
private final ExternalIds externalIds;
private final Provider<AccountsUpdate> accountsUpdateProvider;
- private final HttpPasswordUpdateSender.Factory httpPasswordUpdateSenderFactory;
+ private final HttpPasswordUpdateEmailFactory httpPasswordUpdateEmailFactory;
private final ExternalIdFactory externalIdFactory;
private final ExternalIdKeyFactory externalIdKeyFactory;
@@ -88,14 +88,14 @@
PermissionBackend permissionBackend,
ExternalIds externalIds,
@UserInitiated Provider<AccountsUpdate> accountsUpdateProvider,
- HttpPasswordUpdateSender.Factory httpPasswordUpdateSenderFactory,
+ HttpPasswordUpdateEmailFactory httpPasswordUpdateEmailFactory,
ExternalIdFactory externalIdFactory,
ExternalIdKeyFactory externalIdKeyFactory) {
this.self = self;
this.permissionBackend = permissionBackend;
this.externalIds = externalIds;
this.accountsUpdateProvider = accountsUpdateProvider;
- this.httpPasswordUpdateSenderFactory = httpPasswordUpdateSenderFactory;
+ this.httpPasswordUpdateEmailFactory = httpPasswordUpdateEmailFactory;
this.externalIdFactory = externalIdFactory;
this.externalIdKeyFactory = externalIdKeyFactory;
}
@@ -146,8 +146,8 @@
extId.key(), extId.accountId(), extId.email(), newPassword)));
try {
- httpPasswordUpdateSenderFactory
- .create(user, newPassword == null ? "deleted" : "added or updated")
+ httpPasswordUpdateEmailFactory
+ .createEmail(user, newPassword == null ? "deleted" : "added or updated")
.send();
} catch (EmailException e) {
logger.atSevere().withCause(e).log(
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteVoteOp.java b/java/com/google/gerrit/server/restapi/change/DeleteVoteOp.java
index ef1b6f6..2ff3ab0 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteVoteOp.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteVoteOp.java
@@ -36,9 +36,9 @@
import com.google.gerrit.server.change.NotifyResolver;
import com.google.gerrit.server.extensions.events.VoteDeleted;
import com.google.gerrit.server.mail.EmailModule.DeleteVoteChangeEmailFactories;
-import com.google.gerrit.server.mail.send.ChangeEmailNew;
+import com.google.gerrit.server.mail.send.ChangeEmail;
import com.google.gerrit.server.mail.send.MessageIdGenerator;
-import com.google.gerrit.server.mail.send.OutgoingEmailNew;
+import com.google.gerrit.server.mail.send.OutgoingEmail;
import com.google.gerrit.server.permissions.LabelRemovalPermission;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.DeleteVoteControl;
@@ -188,10 +188,10 @@
CurrentUser user = ctx.getUser();
try {
- ChangeEmailNew changeEmail =
+ ChangeEmail changeEmail =
deleteVoteChangeEmailFactories.createChangeEmail(ctx.getProject(), change.getId());
changeEmail.setChangeMessage(mailMessage, ctx.getWhen());
- OutgoingEmailNew outgoingEmail = deleteVoteChangeEmailFactories.createEmail(changeEmail);
+ OutgoingEmail outgoingEmail = deleteVoteChangeEmailFactories.createEmail(changeEmail);
NotifyResolver.Result notify = ctx.getNotify(change.getId());
if (user.isIdentifiedUser()) {
outgoingEmail.setFrom(user.getAccountId());
diff --git a/java/com/google/gerrit/server/restapi/change/Restore.java b/java/com/google/gerrit/server/restapi/change/Restore.java
index a47e88d..35ea183 100644
--- a/java/com/google/gerrit/server/restapi/change/Restore.java
+++ b/java/com/google/gerrit/server/restapi/change/Restore.java
@@ -36,9 +36,9 @@
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.extensions.events.ChangeRestored;
import com.google.gerrit.server.mail.EmailModule.RestoredChangeEmailFactories;
-import com.google.gerrit.server.mail.send.ChangeEmailNew;
+import com.google.gerrit.server.mail.send.ChangeEmail;
import com.google.gerrit.server.mail.send.MessageIdGenerator;
-import com.google.gerrit.server.mail.send.OutgoingEmailNew;
+import com.google.gerrit.server.mail.send.OutgoingEmail;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -152,10 +152,10 @@
@Override
public void postUpdate(PostUpdateContext ctx) {
try {
- ChangeEmailNew changeEmail =
+ ChangeEmail changeEmail =
restoredChangeEmailFactories.createChangeEmail(ctx.getProject(), change.getId());
changeEmail.setChangeMessage(mailMessage, ctx.getWhen());
- OutgoingEmailNew outgoingEmail = restoredChangeEmailFactories.createEmail(changeEmail);
+ OutgoingEmail outgoingEmail = restoredChangeEmailFactories.createEmail(changeEmail);
outgoingEmail.setFrom(ctx.getAccountId());
outgoingEmail.setMessageId(
messageIdGenerator.fromChangeUpdate(ctx.getRepoView(), change.currentPatchSetId()));
diff --git a/java/com/google/gerrit/server/submit/EmailMerge.java b/java/com/google/gerrit/server/submit/EmailMerge.java
index 1cac440..a823013 100644
--- a/java/com/google/gerrit/server/submit/EmailMerge.java
+++ b/java/com/google/gerrit/server/submit/EmailMerge.java
@@ -24,9 +24,9 @@
import com.google.gerrit.server.change.NotifyResolver;
import com.google.gerrit.server.config.SendEmailExecutor;
import com.google.gerrit.server.mail.EmailModule.MergedChangeEmailFactories;
-import com.google.gerrit.server.mail.send.ChangeEmailNew;
+import com.google.gerrit.server.mail.send.ChangeEmail;
import com.google.gerrit.server.mail.send.MessageIdGenerator;
-import com.google.gerrit.server.mail.send.OutgoingEmailNew;
+import com.google.gerrit.server.mail.send.OutgoingEmail;
import com.google.gerrit.server.update.RepoView;
import com.google.gerrit.server.util.RequestContext;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
@@ -95,12 +95,12 @@
public void run() {
RequestContext old = requestContext.setContext(this);
try {
- ChangeEmailNew changeEmail =
+ ChangeEmail changeEmail =
mergedChangeEmailFactories.createChangeEmail(
project,
change.getId(),
Optional.ofNullable(Strings.emptyToNull(stickyApprovalDiff)));
- OutgoingEmailNew outgoingEmail = mergedChangeEmailFactories.createEmail(changeEmail);
+ OutgoingEmail outgoingEmail = mergedChangeEmailFactories.createEmail(changeEmail);
if (submitter != null) {
outgoingEmail.setFrom(submitter.getAccountId());
}
diff --git a/java/com/google/gerrit/server/update/BatchUpdate.java b/java/com/google/gerrit/server/update/BatchUpdate.java
index 9250513..14d781d 100644
--- a/java/com/google/gerrit/server/update/BatchUpdate.java
+++ b/java/com/google/gerrit/server/update/BatchUpdate.java
@@ -19,6 +19,7 @@
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMultiset.toImmutableMultiset;
import static com.google.common.flogger.LazyArgs.lazy;
+import static com.google.gerrit.common.UsedAt.Project.GOOGLE;
import static java.util.Comparator.comparing;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toMap;
@@ -39,6 +40,7 @@
import com.google.common.util.concurrent.ListenableFuture;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.UsedAt;
import com.google.gerrit.entities.AttentionSetUpdate;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Change;
@@ -52,6 +54,7 @@
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.RefLogIdentityProvider;
@@ -666,9 +669,14 @@
}
}
+ // For upstream implementation, AccessPath.WEB_BROWSER is never set, so the method will always
+ // return false.
+ @UsedAt(GOOGLE)
private boolean indexAsync() {
- return experimentFeatures.isFeatureEnabled(
- ExperimentFeaturesConstants.GERRIT_BACKEND_FEATURE_DO_NOT_AWAIT_CHANGE_INDEXING);
+ return user.getAccessPath().equals(AccessPath.WEB_BROWSER)
+ && experimentFeatures.isFeatureEnabled(
+ ExperimentFeaturesConstants.GERRIT_BACKEND_FEATURE_DO_NOT_AWAIT_CHANGE_INDEXING,
+ project);
}
private void fireRefChangeEvent() {
@@ -751,6 +759,8 @@
}
}
if (indexAsync) {
+ logger.atFine().log(
+ "Asynchronously reindexing changes, %s in project %s", results.keySet(), project.get());
// We want to index asynchronously. However, the callers will await all
// index futures. This allows us to - even in synchronous case -
// parallelize indexing changes.
diff --git a/java/com/google/gerrit/server/util/AttentionSetEmail.java b/java/com/google/gerrit/server/util/AttentionSetEmail.java
index c00e810..102b052 100644
--- a/java/com/google/gerrit/server/util/AttentionSetEmail.java
+++ b/java/com/google/gerrit/server/util/AttentionSetEmail.java
@@ -24,10 +24,10 @@
import com.google.gerrit.server.mail.EmailModule.AttentionSetChangeEmailFactories;
import com.google.gerrit.server.mail.send.AttentionSetChangeEmailDecorator;
import com.google.gerrit.server.mail.send.AttentionSetChangeEmailDecorator.AttentionSetChange;
-import com.google.gerrit.server.mail.send.ChangeEmailNew;
+import com.google.gerrit.server.mail.send.ChangeEmail;
import com.google.gerrit.server.mail.send.MessageIdGenerator;
import com.google.gerrit.server.mail.send.MessageIdGenerator.MessageId;
-import com.google.gerrit.server.mail.send.OutgoingEmailNew;
+import com.google.gerrit.server.mail.send.OutgoingEmail;
import com.google.gerrit.server.update.Context;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -154,10 +154,10 @@
changeEmailParams.setAttentionSetChange(attentionSetChange);
changeEmailParams.setAttentionSetUser(attentionUserId);
changeEmailParams.setReason(reason);
- ChangeEmailNew changeEmail =
+ ChangeEmail changeEmail =
attentionSetChangeEmailFactories.createChangeEmail(
projectId, changeId, changeEmailParams);
- OutgoingEmailNew outgoingEmail =
+ OutgoingEmail outgoingEmail =
attentionSetChangeEmailFactories.createEmail(attentionSetChange, changeEmail);
Optional<Account.Id> accountId =
diff --git a/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java b/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
index 15baa78..abaf98f 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
@@ -1262,6 +1262,7 @@
+ c
+ "/comment/"
+ ps1List.get(0).id
+ + "?usp=email"
+ " :\n"
+ "PS1, Line 1: initial\n"
+ "what happened to this?\n"
@@ -1274,6 +1275,7 @@
+ c
+ "/comment/"
+ ps1List.get(1).id
+ + "?usp=email"
+ " :\n"
+ "PS1, Line 1: boring\n"
+ "Is it that bad?\n"
@@ -1288,6 +1290,7 @@
+ c
+ "/comment/"
+ ps2List.get(0).id
+ + "?usp=email"
+ " :\n"
+ "PS2, Line 1: initial content\n"
+ "comment 1 on base\n"
@@ -1300,6 +1303,7 @@
+ c
+ "/comment/"
+ ps2List.get(1).id
+ + "?usp=email"
+ " :\n"
+ "PS2, Line 2: \n"
+ "comment 2 on base\n"
@@ -1312,6 +1316,7 @@
+ c
+ "/comment/"
+ ps2List.get(2).id
+ + "?usp=email"
+ " :\n"
+ "PS2, Line 1: interesting\n"
+ "better now\n"
@@ -1324,6 +1329,7 @@
+ c
+ "/comment/"
+ ps2List.get(3).id
+ + "?usp=email"
+ " :\n"
+ "PS2, Line 2: cntent\n"
+ "typo: content\n"
diff --git a/javatests/com/google/gerrit/acceptance/server/mail/MailSenderIT.java b/javatests/com/google/gerrit/acceptance/server/mail/MailSenderIT.java
index f728995..5e00230 100644
--- a/javatests/com/google/gerrit/acceptance/server/mail/MailSenderIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/mail/MailSenderIT.java
@@ -62,7 +62,7 @@
Map<String, EmailHeader> headers = sender.getMessages().iterator().next().headers();
String hostname = URI.create(canonicalWebUrl.get()).getHost();
String listId = String.format("<gerrit-%s.%s>", project.get(), hostname);
- String unsubscribeLink = String.format("<%ssettings>", canonicalWebUrl.get());
+ String unsubscribeLink = String.format("<%ssettings?usp=email>", canonicalWebUrl.get());
String threadId =
String.format(
"<gerrit.%s.%s@%s>",
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index a188251..688e5e4 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -83,6 +83,7 @@
import com.google.gerrit.extensions.api.projects.ConfigInput;
import com.google.gerrit.extensions.api.projects.ProjectInput;
import com.google.gerrit.extensions.client.InheritableBoolean;
+import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.client.ProjectWatchInfo;
import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.extensions.common.AccountInfo;
@@ -2886,7 +2887,8 @@
}
@Test
- public void byStar() throws Exception {
+ public void byStar_withStarOptionSet() throws Exception {
+ // When star option is set, the 'starred' field is set in the change infos in response.
repo = createAndOpenProject("repo");
Change change1 = insert("repo", newChangeWithStatus(repo, Change.Status.MERGED));
@@ -2899,6 +2901,32 @@
// check default star
assertQuery("has:star", change1);
assertQuery("is:starred", change1);
+
+ // The 'Star' bit in the change data is also set correctly
+ List<ChangeInfo> changeInfos =
+ gApi.changes().query("has:star").withOptions(ListChangesOption.STAR).get();
+ assertThat(changeInfos.get(0).starred).isTrue();
+ }
+
+ @Test
+ public void byStar_withStarOptionNotSet() throws Exception {
+ // When star option is not set, the 'starred' field is not set in the change infos in response.
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChangeWithStatus(repo, Change.Status.MERGED));
+
+ Account.Id user2 =
+ accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
+ requestContext.setContext(newRequestContext(user2));
+
+ gApi.accounts().self().starChange(change1.getId().toString());
+
+ // check default star
+ assertQuery("has:star", change1);
+ assertQuery("is:starred", change1);
+
+ // The 'Star' bit in the change data is not set if the backfilling option is not set
+ List<ChangeInfo> changeInfos = gApi.changes().query("has:star").get();
+ assertThat(changeInfos.get(0).starred).isNull();
}
@Test
diff --git a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.ts b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.ts
index 4d0d3f1..e15c240 100644
--- a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.ts
+++ b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.ts
@@ -39,8 +39,6 @@
// The name that gets automatically input when a new reference is added.
const NEW_NAME = 'refs/heads/*';
const REFS_NAME = 'refs/';
-const ON_BEHALF_OF = '(On Behalf Of)';
-const LABEL = 'Label';
@customElement('gr-access-section')
export class GrAccessSection extends LitElement {
@@ -360,14 +358,14 @@
labelOptions.push({
id: 'label-' + labelName,
value: {
- name: `${LABEL} ${labelName}`,
+ name: `Label ${labelName}`,
id: 'label-' + labelName,
},
});
labelOptions.push({
id: 'labelAs-' + labelName,
value: {
- name: `${LABEL} ${labelName} ${ON_BEHALF_OF}`,
+ name: `Label ${labelName} (On Behalf Of)`,
id: 'labelAs-' + labelName,
},
});
@@ -384,11 +382,13 @@
} else if (AccessPermissions[permission.id]) {
return AccessPermissions[permission.id]?.name;
} else if (permission.value.label) {
- let behalfOf = '';
if (permission.id.startsWith('labelAs-')) {
- behalfOf = ON_BEHALF_OF;
+ return `Label ${permission.value.label} (On Behalf Of)`;
+ } else if (permission.id.startsWith('removeLabel-')) {
+ return `Remove Label ${permission.value.label}`;
+ } else {
+ return `Label ${permission.value.label}`;
}
- return `${LABEL} ${permission.value.label}${behalfOf}`;
}
return undefined;
}
diff --git a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.ts b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.ts
index 593a1ed..2c397e0 100644
--- a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.ts
@@ -355,7 +355,20 @@
assert.equal(
element.computePermissionName(permission),
- 'Label Code-Review(On Behalf Of)'
+ 'Label Code-Review (On Behalf Of)'
+ );
+
+ permission = {
+ id: 'removeLabel-Code-Review' as GitRef,
+ value: {
+ label: 'Code-Review',
+ rules: {},
+ },
+ };
+
+ assert.equal(
+ element.computePermissionName(permission),
+ 'Remove Label Code-Review'
);
});
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
index 2e1a8b9..02fdb34 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
@@ -1706,7 +1706,6 @@
const options = {
mergeable: this.mergeable,
- submitEnabled: !!this.isSubmitEnabled(),
revertingChangeStatus: this.revertingChange?.status,
};
return changeStatuses(this.change as ChangeInfo, options);
diff --git a/polygerrit-ui/app/utils/change-util.ts b/polygerrit-ui/app/utils/change-util.ts
index 4490afa..7de5e7e 100644
--- a/polygerrit-ui/app/utils/change-util.ts
+++ b/polygerrit-ui/app/utils/change-util.ts
@@ -16,10 +16,8 @@
import {ParsedChangeInfo} from '../types/types';
import {getUserId, isServiceUser} from './account-util';
-// This can be wrong! See WARNING above
interface ChangeStatusesOptions {
- mergeable: boolean; // This can be wrong! See WARNING above
- submitEnabled: boolean; // This can be wrong! See WARNING above
+ mergeable: boolean;
/** Is there a reverting change and if so, what status has it? */
revertingChangeStatus?: ChangeStatus;
}
@@ -190,11 +188,9 @@
return states;
}
- // If no missing requirements, either active or ready to submit.
- if (change.submittable && options.submitEnabled) {
+ if (change.submittable) {
states.push(ChangeStates.READY_TO_SUBMIT);
} else {
- // Otherwise it is active.
states.push(ChangeStates.ACTIVE);
}
return states;
diff --git a/polygerrit-ui/app/utils/change-util_test.ts b/polygerrit-ui/app/utils/change-util_test.ts
index f768145..1782d80 100644
--- a/polygerrit-ui/app/utils/change-util_test.ts
+++ b/polygerrit-ui/app/utils/change-util_test.ts
@@ -66,29 +66,24 @@
assert.deepEqual(statuses, []);
change.submittable = false;
- statuses = changeStatuses(change, {mergeable: true, submitEnabled: false});
+ statuses = changeStatuses(change, {mergeable: true});
assert.deepEqual(statuses, [ChangeStates.ACTIVE]);
- // With no missing labels but no submitEnabled option.
change.submittable = true;
- statuses = changeStatuses(change, {mergeable: true, submitEnabled: false});
- assert.deepEqual(statuses, [ChangeStates.ACTIVE]);
-
- // Without missing labels and enabled submit
- statuses = changeStatuses(change, {mergeable: true, submitEnabled: true});
+ statuses = changeStatuses(change, {mergeable: true});
assert.deepEqual(statuses, [ChangeStates.READY_TO_SUBMIT]);
change.mergeable = false;
change.submittable = true;
- statuses = changeStatuses(change, {mergeable: false, submitEnabled: false});
+ statuses = changeStatuses(change, {mergeable: false});
assert.deepEqual(statuses, [ChangeStates.MERGE_CONFLICT]);
change.mergeable = true;
- statuses = changeStatuses(change, {mergeable: true, submitEnabled: true});
+ statuses = changeStatuses(change, {mergeable: true});
assert.deepEqual(statuses, [ChangeStates.READY_TO_SUBMIT]);
change.submittable = true;
- statuses = changeStatuses(change, {mergeable: false, submitEnabled: false});
+ statuses = changeStatuses(change, {mergeable: false});
assert.deepEqual(statuses, [ChangeStates.MERGE_CONFLICT]);
});
@@ -141,7 +136,6 @@
changeStatuses(change, {
revertingChangeStatus: ChangeStatus.NEW,
mergeable: true,
- submitEnabled: true,
}),
[ChangeStates.MERGED, ChangeStates.REVERT_CREATED]
);
@@ -149,7 +143,6 @@
changeStatuses(change, {
revertingChangeStatus: ChangeStatus.MERGED,
mergeable: true,
- submitEnabled: true,
}),
[ChangeStates.MERGED, ChangeStates.REVERT_SUBMITTED]
);
diff --git a/resources/com/google/gerrit/server/mail/AddKey.soy b/resources/com/google/gerrit/server/mail/AddKey.soy
index 319db05..8958ea3 100644
--- a/resources/com/google/gerrit/server/mail/AddKey.soy
+++ b/resources/com/google/gerrit/server/mail/AddKey.soy
@@ -48,9 +48,9 @@
You can also manage your {$email.keyType} keys by visiting
{\n}
{if $email.sshKey}
- {$email.gerritUrl}#/settings/ssh-keys
+ {$email.sshKeysSettingsUrl}
{elseif $email.gpgKeys}
- {$email.gerritUrl}#/settings/gpg-keys
+ {$email.gpgKeysSettingsUrl}
{/if}
{\n}
{if $email.userNameEmail}
diff --git a/resources/com/google/gerrit/server/mail/AddKeyHtml.soy b/resources/com/google/gerrit/server/mail/AddKeyHtml.soy
index c356a95..cb5b224 100644
--- a/resources/com/google/gerrit/server/mail/AddKeyHtml.soy
+++ b/resources/com/google/gerrit/server/mail/AddKeyHtml.soy
@@ -47,9 +47,9 @@
<p>
You can also manage your {$email.keyType} keys by following{sp}
{if $email.sshKey}
- <a href="{$email.gerritUrl}#/settings/ssh-keys">this link</a>
+ <a href="{$email.sshKeysSettingsUrl}">this link</a>
{elseif $email.gpgKeys}
- <a href="{$email.gerritUrl}#/settings/gpg-keys">this link</a>
+ <a href="{$email.gpgKeysSettingsUrl}">this link</a>
{/if}
{sp}
{if $email.userNameEmail}
diff --git a/resources/com/google/gerrit/server/mail/DeleteKey.soy b/resources/com/google/gerrit/server/mail/DeleteKey.soy
index 0957dc6..46bfc7e 100644
--- a/resources/com/google/gerrit/server/mail/DeleteKey.soy
+++ b/resources/com/google/gerrit/server/mail/DeleteKey.soy
@@ -47,9 +47,9 @@
You can also manage your {$email.keyType} keys by visiting
{\n}
{if $email.sshKey}
- {$email.gerritUrl}#/settings/ssh-keys
+ {$email.sshKeysSettingsUrl}
{elseif $email.gpgKey}
- {$email.gerritUrl}#/settings/gpg-keys
+ {$email.gpgKeysSettingsUrl}
{/if}
{\n}
{if $email.userNameEmail}
diff --git a/resources/com/google/gerrit/server/mail/DeleteKeyHtml.soy b/resources/com/google/gerrit/server/mail/DeleteKeyHtml.soy
index fea6785..539688e 100644
--- a/resources/com/google/gerrit/server/mail/DeleteKeyHtml.soy
+++ b/resources/com/google/gerrit/server/mail/DeleteKeyHtml.soy
@@ -45,9 +45,9 @@
<p>
You can also manage your {$email.keyType} keys by following{sp}
{if $email.sshKey}
- <a href="{$email.gerritUrl}#/settings/ssh-keys">this link</a>
+ <a href="{$email.sshKeysSettingsUrl}">this link</a>
{elseif $email.gpgKeyFingerprints}
- <a href="{$email.gerritUrl}#/settings/gpg-keys">this link</a>
+ <a href="{$email.gpgKeysSettingsUrl}">this link</a>
{/if}
{sp}
{if $email.userNameEmail}
diff --git a/resources/com/google/gerrit/server/mail/Email.soy b/resources/com/google/gerrit/server/mail/Email.soy
new file mode 100644
index 0000000..9afea72
--- /dev/null
+++ b/resources/com/google/gerrit/server/mail/Email.soy
@@ -0,0 +1,27 @@
+/**
+ * Copyright (C) 2023 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.
+*/
+
+{namespace com.google.gerrit.server.mail.template.Email}
+
+/**
+ * The .Email template defines the structure of the content in the email.
+ */
+{template Email kind="text"}
+ {@param body: string}
+ {@param footer: string}
+ {$body}
+ {$footer}
+{/template}
diff --git a/resources/com/google/gerrit/server/mail/EmailHtml.soy b/resources/com/google/gerrit/server/mail/EmailHtml.soy
new file mode 100644
index 0000000..5b5ea63
--- /dev/null
+++ b/resources/com/google/gerrit/server/mail/EmailHtml.soy
@@ -0,0 +1,38 @@
+/**
+ * Copyright (C) 2023 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.
+*/
+
+{namespace com.google.gerrit.server.mail.template.EmailHtml}
+
+/**
+ * The .EmailHtml template defines the structure of the content in the email.
+ */
+{template EmailHtml}
+ {@param styles: css}
+ {@param body: html}
+ {@param footer: html}
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <style>
+ {$styles}
+ </style>
+ </head>
+ <body>
+ {$body}
+ {$footer}
+ </body>
+ </html>
+{/template}
diff --git a/resources/com/google/gerrit/server/mail/HttpPasswordUpdate.soy b/resources/com/google/gerrit/server/mail/HttpPasswordUpdate.soy
index 49fbccb..3efa8be 100644
--- a/resources/com/google/gerrit/server/mail/HttpPasswordUpdate.soy
+++ b/resources/com/google/gerrit/server/mail/HttpPasswordUpdate.soy
@@ -33,7 +33,7 @@
You can also manage your HTTP password by visiting
{\n}
- {$email.gerritUrl}#/settings/http-password
+ {$email.httpPasswordSettingsUrl}
{\n}
{if $email.userNameEmail}
(while signed in as {$email.userNameEmail})
diff --git a/resources/com/google/gerrit/server/mail/HttpPasswordUpdateHtml.soy b/resources/com/google/gerrit/server/mail/HttpPasswordUpdateHtml.soy
index 3f88a6f..ee033cd 100644
--- a/resources/com/google/gerrit/server/mail/HttpPasswordUpdateHtml.soy
+++ b/resources/com/google/gerrit/server/mail/HttpPasswordUpdateHtml.soy
@@ -30,7 +30,7 @@
<p>
You can also manage your HTTP password by following{sp}
- <a href="{$email.gerritUrl}#/settings/http-password">this link</a>
+ <a href="{$email.httpPasswordSettingsUrl}">this link</a>
{sp}
{if $email.userNameEmail}
(while signed in as {$email.userNameEmail})
diff --git a/resources/com/google/gerrit/server/mail/RegisterNewEmail.soy b/resources/com/google/gerrit/server/mail/RegisterNewEmail.soy
index 273f52f..cd38742 100644
--- a/resources/com/google/gerrit/server/mail/RegisterNewEmail.soy
+++ b/resources/com/google/gerrit/server/mail/RegisterNewEmail.soy
@@ -34,7 +34,7 @@
{\n}
- {$email.gerritUrl}#/VE/{$email.emailRegistrationToken}{\n}
+ {$email.emailRegistrationLink}{\n}
{\n}
diff --git a/resources/com/google/gerrit/server/mail/RegisterNewEmailHtml.soy b/resources/com/google/gerrit/server/mail/RegisterNewEmailHtml.soy
index 7d6cd23..20f9999 100644
--- a/resources/com/google/gerrit/server/mail/RegisterNewEmailHtml.soy
+++ b/resources/com/google/gerrit/server/mail/RegisterNewEmailHtml.soy
@@ -31,7 +31,7 @@
<p>
- {$email.gerritUrl}#/VE/{$email.emailRegistrationToken}
+ {$email.emailRegistrationLink}
</p>
<p>
If you have received this mail in error, you do not need to take any