Split OutgoingEmail into 2 classes
Split OutgoingEmail into one class for basic OutgoingEmail, and one class for emails related to changes: ChangeEmail. This simplifies both classes and allows the registering emails to not have to worry about changes. Change-Id: I21811a961fb787af75547381a78e97d391e04e42
This commit is contained in:
		| @@ -39,7 +39,7 @@ public class AbandonedSender extends ReplyToChangeSender { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   @Override |   @Override | ||||||
|   protected void format() { |   protected void formatChange() { | ||||||
|     appendText(getNameFor(fromId)); |     appendText(getNameFor(fromId)); | ||||||
|     appendText(" has abandoned change " + change.getKey().abbreviate() + ":\n"); |     appendText(" has abandoned change " + change.getKey().abbreviate() + ":\n"); | ||||||
|     appendText("\n"); |     appendText("\n"); | ||||||
|   | |||||||
| @@ -0,0 +1,468 @@ | |||||||
|  | // Copyright (C) 2010 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; | ||||||
|  |  | ||||||
|  | import com.google.gerrit.reviewdb.Account; | ||||||
|  | import com.google.gerrit.reviewdb.AccountGroup; | ||||||
|  | import com.google.gerrit.reviewdb.AccountProjectWatch; | ||||||
|  | import com.google.gerrit.reviewdb.Change; | ||||||
|  | import com.google.gerrit.reviewdb.ChangeMessage; | ||||||
|  | import com.google.gerrit.reviewdb.PatchSet; | ||||||
|  | import com.google.gerrit.reviewdb.PatchSetApproval; | ||||||
|  | import com.google.gerrit.reviewdb.PatchSetInfo; | ||||||
|  | import com.google.gerrit.reviewdb.StarredChange; | ||||||
|  | import com.google.gerrit.reviewdb.UserIdentity; | ||||||
|  | import com.google.gerrit.server.IdentifiedUser; | ||||||
|  | import com.google.gerrit.server.account.AccountState; | ||||||
|  | import com.google.gerrit.server.mail.EmailHeader.AddressList; | ||||||
|  | import com.google.gerrit.server.patch.PatchList; | ||||||
|  | import com.google.gerrit.server.patch.PatchListEntry; | ||||||
|  | import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException; | ||||||
|  | import com.google.gerrit.server.project.ProjectState; | ||||||
|  | import com.google.gerrit.server.query.Predicate; | ||||||
|  | import com.google.gerrit.server.query.QueryParseException; | ||||||
|  | import com.google.gerrit.server.query.change.ChangeData; | ||||||
|  | import com.google.gerrit.server.query.change.ChangeQueryBuilder; | ||||||
|  | import com.google.gwtorm.client.OrmException; | ||||||
|  |  | ||||||
|  | import org.eclipse.jgit.util.SystemReader; | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | import java.net.MalformedURLException; | ||||||
|  | import java.net.URL; | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.Collection; | ||||||
|  | import java.util.Collections; | ||||||
|  | import java.util.Date; | ||||||
|  | import java.util.HashSet; | ||||||
|  | import java.util.Iterator; | ||||||
|  | import java.util.LinkedHashMap; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.Random; | ||||||
|  | import java.util.Set; | ||||||
|  | import java.util.TreeSet; | ||||||
|  |  | ||||||
|  | /** Sends an email to one or more interested parties. */ | ||||||
|  | public abstract class ChangeEmail extends OutgoingEmail { | ||||||
|  |   protected final Change change; | ||||||
|  |   protected String projectName; | ||||||
|  |   protected PatchSet patchSet; | ||||||
|  |   protected PatchSetInfo patchSetInfo; | ||||||
|  |   protected ChangeMessage changeMessage; | ||||||
|  |  | ||||||
|  |   private ProjectState projectState; | ||||||
|  |   protected ChangeData changeData; | ||||||
|  |   private boolean inFooter; | ||||||
|  |  | ||||||
|  |   protected ChangeEmail(EmailArguments ea, final Change c, final String mc) { | ||||||
|  |     super(ea, mc); | ||||||
|  |     change = c; | ||||||
|  |     changeData = change != null ? new ChangeData(change) : null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public void setPatchSet(final PatchSet ps) { | ||||||
|  |     patchSet = ps; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public void setPatchSet(final PatchSet ps, final PatchSetInfo psi) { | ||||||
|  |     patchSet = ps; | ||||||
|  |     patchSetInfo = psi; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public void setChangeMessage(final ChangeMessage cm) { | ||||||
|  |     changeMessage = cm; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** Format the message body by calling {@link #appendText(String)}. */ | ||||||
|  |   protected void format() { | ||||||
|  |     formatChange(); | ||||||
|  |     if (getChangeUrl() != null) { | ||||||
|  |       openFooter(); | ||||||
|  |       appendText("To view visit "); | ||||||
|  |       appendText(getChangeUrl()); | ||||||
|  |       appendText("\n"); | ||||||
|  |     } | ||||||
|  |     if (getSettingsUrl() != null) { | ||||||
|  |       openFooter(); | ||||||
|  |       appendText("To unsubscribe, visit "); | ||||||
|  |       appendText(getSettingsUrl()); | ||||||
|  |       appendText("\n"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (inFooter) { | ||||||
|  |       appendText("\n"); | ||||||
|  |     } else { | ||||||
|  |       openFooter(); | ||||||
|  |     } | ||||||
|  |     appendText("Gerrit-MessageType: " + messageClass + "\n"); | ||||||
|  |     appendText("Gerrit-Project: " + projectName + "\n"); | ||||||
|  |     appendText("Gerrit-Branch: " + change.getDest().getShortName() + "\n"); | ||||||
|  |     appendText("Gerrit-Owner: " + getNameEmailFor(change.getOwner()) + "\n"); | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       HashSet<Account.Id> reviewers = new HashSet<Account.Id>(); | ||||||
|  |       for (PatchSetApproval p : args.db.get().patchSetApprovals().byChange( | ||||||
|  |           change.getId())) { | ||||||
|  |         reviewers.add(p.getAccountId()); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       TreeSet<String> names = new TreeSet<String>(); | ||||||
|  |       for (Account.Id who : reviewers) { | ||||||
|  |         names.add(getNameEmailFor(who)); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       for (String name : names) { | ||||||
|  |         appendText("Gerrit-Reviewer: " + name + "\n"); | ||||||
|  |       } | ||||||
|  |     } catch (OrmException e) { | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** Format the message body by calling {@link #appendText(String)}. */ | ||||||
|  |   protected abstract void formatChange(); | ||||||
|  |  | ||||||
|  |   /** Setup the message headers and envelope (TO, CC, BCC). */ | ||||||
|  |   protected void init() { | ||||||
|  |     super.init(); | ||||||
|  |     if (args.projectCache != null) { | ||||||
|  |       projectState = args.projectCache.get(change.getProject()); | ||||||
|  |       projectName = | ||||||
|  |           projectState != null ? projectState.getProject().getName() : null; | ||||||
|  |     } else { | ||||||
|  |       projectState = null; | ||||||
|  |       projectName = null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (patchSet == null) { | ||||||
|  |       try { | ||||||
|  |         patchSet = args.db.get().patchSets().get(change.currentPatchSetId()); | ||||||
|  |       } catch (OrmException err) { | ||||||
|  |         patchSet = null; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (patchSet != null && patchSetInfo == null) { | ||||||
|  |       try { | ||||||
|  |         patchSetInfo = args.patchSetInfoFactory.get(patchSet.getId()); | ||||||
|  |       } catch (PatchSetInfoNotAvailableException err) { | ||||||
|  |         patchSetInfo = null; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (changeMessage != null && changeMessage.getWrittenOn() != null) { | ||||||
|  |       setHeader("Date", new Date(changeMessage.getWrittenOn().getTime())); | ||||||
|  |     } | ||||||
|  |     setChangeSubjectHeader(); | ||||||
|  |     setHeader("X-Gerrit-Change-Id", "" + change.getKey().get()); | ||||||
|  |     setListIdHeader(); | ||||||
|  |     setChangeUrlHeader(); | ||||||
|  |     setCommitIdHeader(); | ||||||
|  |  | ||||||
|  |     inFooter = false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private void setListIdHeader() { | ||||||
|  |     // Set a reasonable list id so that filters can be used to sort messages | ||||||
|  |     // | ||||||
|  |     final StringBuilder listid = new StringBuilder(); | ||||||
|  |     listid.append("gerrit-"); | ||||||
|  |     listid.append(projectName.replace('/', '-')); | ||||||
|  |     listid.append("@"); | ||||||
|  |     listid.append(getGerritHost()); | ||||||
|  |  | ||||||
|  |     final String listidStr = listid.toString(); | ||||||
|  |     setHeader("Mailing-List", "list " + listidStr); | ||||||
|  |     setHeader("List-Id", "<" + listidStr.replace('@', '.') + ">"); | ||||||
|  |     if (getSettingsUrl() != null) { | ||||||
|  |       setHeader("List-Unsubscribe", "<" + getSettingsUrl() + ">"); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private void setChangeUrlHeader() { | ||||||
|  |     final String u = getChangeUrl(); | ||||||
|  |     if (u != null) { | ||||||
|  |       setHeader("X-Gerrit-ChangeURL", "<" + u + ">"); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private void setCommitIdHeader() { | ||||||
|  |     if (patchSet != null && patchSet.getRevision() != null | ||||||
|  |         && patchSet.getRevision().get() != null | ||||||
|  |         && patchSet.getRevision().get().length() > 0) { | ||||||
|  |       setHeader("X-Gerrit-Commit", patchSet.getRevision().get()); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private void setChangeSubjectHeader() { | ||||||
|  |     final StringBuilder subj = new StringBuilder(); | ||||||
|  |     subj.append("["); | ||||||
|  |     subj.append(change.getDest().getShortName()); | ||||||
|  |     subj.append("] "); | ||||||
|  |     subj.append("Change "); | ||||||
|  |     subj.append(change.getKey().abbreviate()); | ||||||
|  |     subj.append(": ("); | ||||||
|  |     subj.append(projectName); | ||||||
|  |     subj.append(") "); | ||||||
|  |     if (change.getSubject().length() > 60) { | ||||||
|  |       subj.append(change.getSubject().substring(0, 60)); | ||||||
|  |       subj.append("..."); | ||||||
|  |     } else { | ||||||
|  |       subj.append(change.getSubject()); | ||||||
|  |     } | ||||||
|  |     setHeader("Subject", subj.toString()); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** Get a link to the change; null if the server doesn't know its own address. */ | ||||||
|  |   protected String getChangeUrl() { | ||||||
|  |     if (change != null && getGerritUrl() != null) { | ||||||
|  |       final StringBuilder r = new StringBuilder(); | ||||||
|  |       r.append(getGerritUrl()); | ||||||
|  |       r.append(change.getChangeId()); | ||||||
|  |       return r.toString(); | ||||||
|  |     } | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private String getSettingsUrl() { | ||||||
|  |     if (getGerritUrl() != null) { | ||||||
|  |       final StringBuilder r = new StringBuilder(); | ||||||
|  |       r.append(getGerritUrl()); | ||||||
|  |       r.append("settings"); | ||||||
|  |       return r.toString(); | ||||||
|  |     } | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected String getChangeMessageThreadId() { | ||||||
|  |     final StringBuilder r = new StringBuilder(); | ||||||
|  |     r.append('<'); | ||||||
|  |     r.append("gerrit"); | ||||||
|  |     r.append('.'); | ||||||
|  |     r.append(change.getCreatedOn().getTime()); | ||||||
|  |     r.append('.'); | ||||||
|  |     r.append(change.getKey().get()); | ||||||
|  |     r.append('@'); | ||||||
|  |     r.append(getGerritHost()); | ||||||
|  |     r.append('>'); | ||||||
|  |     return r.toString(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private void openFooter() { | ||||||
|  |     if (!inFooter) { | ||||||
|  |       inFooter = true; | ||||||
|  |       appendText("-- \n"); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** Format the sender's "cover letter", {@link #getCoverLetter()}. */ | ||||||
|  |   protected void formatCoverLetter() { | ||||||
|  |     final String cover = getCoverLetter(); | ||||||
|  |     if (!"".equals(cover)) { | ||||||
|  |       appendText(cover); | ||||||
|  |       appendText("\n\n"); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** Get the text of the "cover letter", from {@link ChangeMessage}. */ | ||||||
|  |   protected String getCoverLetter() { | ||||||
|  |     if (changeMessage != null) { | ||||||
|  |       final String txt = changeMessage.getMessage(); | ||||||
|  |       if (txt != null) { | ||||||
|  |         return txt.trim(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return ""; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** Format the change message and the affected file list. */ | ||||||
|  |   protected void formatChangeDetail() { | ||||||
|  |     if (patchSetInfo != null) { | ||||||
|  |       appendText(patchSetInfo.getMessage().trim()); | ||||||
|  |       appendText("\n"); | ||||||
|  |     } else { | ||||||
|  |       appendText(change.getSubject().trim()); | ||||||
|  |       appendText("\n"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (patchSet != null) { | ||||||
|  |       appendText("---\n"); | ||||||
|  |       for (PatchListEntry p : getPatchList().getPatches()) { | ||||||
|  |         appendText(p.getChangeType().getCode() + " " + p.getNewName() + "\n"); | ||||||
|  |       } | ||||||
|  |       appendText("\n"); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** Get the patch list corresponding to this patch set. */ | ||||||
|  |   protected PatchList getPatchList() { | ||||||
|  |     if (patchSet != null) { | ||||||
|  |       return args.patchListCache.get(change, patchSet); | ||||||
|  |     } | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** Get the project entity the change is in; null if its been deleted. */ | ||||||
|  |   protected ProjectState getProjectState() { | ||||||
|  |     return projectState; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** Get the groups which own the project. */ | ||||||
|  |   protected Set<AccountGroup.Id> getProjectOwners() { | ||||||
|  |     final ProjectState r; | ||||||
|  |  | ||||||
|  |     r = args.projectCache.get(change.getProject()); | ||||||
|  |     return r != null ? r.getOwners() : Collections.<AccountGroup.Id> emptySet(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** Schedule this message for delivery to the listed accounts. */ | ||||||
|  |   protected void add(final RecipientType rt, final Collection<Account.Id> list) { | ||||||
|  |     for (final Account.Id id : list) { | ||||||
|  |       add(rt, id); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** TO or CC all vested parties (change owner, patch set uploader, author). */ | ||||||
|  |   protected void rcptToAuthors(final RecipientType rt) { | ||||||
|  |     add(rt, change.getOwner()); | ||||||
|  |     if (patchSet != null) { | ||||||
|  |       add(rt, patchSet.getUploader()); | ||||||
|  |     } | ||||||
|  |     if (patchSetInfo != null) { | ||||||
|  |       add(rt, patchSetInfo.getAuthor()); | ||||||
|  |       add(rt, patchSetInfo.getCommitter()); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private void add(final RecipientType rt, final UserIdentity who) { | ||||||
|  |     if (who != null && who.getAccount() != null) { | ||||||
|  |       add(rt, who.getAccount()); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** BCC any user who has starred this change. */ | ||||||
|  |   protected void bccStarredBy() { | ||||||
|  |     try { | ||||||
|  |       // BCC anyone who has starred this change. | ||||||
|  |       // | ||||||
|  |       for (StarredChange w : args.db.get().starredChanges().byChange( | ||||||
|  |           change.getId())) { | ||||||
|  |         add(RecipientType.BCC, w.getAccountId()); | ||||||
|  |       } | ||||||
|  |     } catch (OrmException err) { | ||||||
|  |       // Just don't BCC 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. | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** BCC any user who has set "notify all comments" on this project. */ | ||||||
|  |   protected void bccWatchesNotifyAllComments() { | ||||||
|  |     try { | ||||||
|  |       // BCC anyone else who has interest in this project's changes | ||||||
|  |       // | ||||||
|  |       for (final AccountProjectWatch w : getWatches()) { | ||||||
|  |         if (w.isNotifyAllComments()) { | ||||||
|  |           add(RecipientType.BCC, w.getAccountId()); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } catch (OrmException 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. | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** Returns all watches that are relevant */ | ||||||
|  |   protected final List<AccountProjectWatch> getWatches() throws OrmException { | ||||||
|  |     if (changeData == null) { | ||||||
|  |       return Collections.emptyList(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     List<AccountProjectWatch> matching = new ArrayList<AccountProjectWatch>(); | ||||||
|  |     Set<Account.Id> projectWatchers = new HashSet<Account.Id>(); | ||||||
|  |  | ||||||
|  |     for (AccountProjectWatch w : args.db.get().accountProjectWatches() | ||||||
|  |         .byProject(change.getProject())) { | ||||||
|  |       projectWatchers.add(w.getAccountId()); | ||||||
|  |       add(matching, w); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     for (AccountProjectWatch w : args.db.get().accountProjectWatches() | ||||||
|  |         .byProject(args.wildProject)) { | ||||||
|  |       if (!projectWatchers.contains(w.getAccountId())) { | ||||||
|  |         add(matching, w); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return Collections.unmodifiableList(matching); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @SuppressWarnings("unchecked") | ||||||
|  |   private void add(List<AccountProjectWatch> matching, AccountProjectWatch w) | ||||||
|  |       throws OrmException { | ||||||
|  |     IdentifiedUser user = | ||||||
|  |         args.identifiedUserFactory.create(args.db, w.getAccountId()); | ||||||
|  |     ChangeQueryBuilder qb = args.queryBuilder.create(user); | ||||||
|  |     Predicate<ChangeData> p = qb.is_visible(); | ||||||
|  |     if (w.getFilter() != null) { | ||||||
|  |       try { | ||||||
|  |         qb.setAllowFile(true); | ||||||
|  |         p = Predicate.and(qb.parse(w.getFilter()), p); | ||||||
|  |         p = args.queryRewriter.get().rewrite(p); | ||||||
|  |         if (p.match(changeData)) { | ||||||
|  |           matching.add(w); | ||||||
|  |         } | ||||||
|  |       } catch (QueryParseException e) { | ||||||
|  |         // Ignore broken filter expressions. | ||||||
|  |       } | ||||||
|  |     } else if (p.match(changeData)) { | ||||||
|  |       matching.add(w); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** Any user who has published comments on this change. */ | ||||||
|  |   protected void ccAllApprovals() { | ||||||
|  |     ccApprovals(true); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** Users who have non-zero approval codes on the change. */ | ||||||
|  |   protected void ccExistingReviewers() { | ||||||
|  |     ccApprovals(false); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private void ccApprovals(final boolean includeZero) { | ||||||
|  |     try { | ||||||
|  |       // CC anyone else who has posted an approval mark on this change | ||||||
|  |       // | ||||||
|  |       for (PatchSetApproval ap : args.db.get().patchSetApprovals().byChange( | ||||||
|  |           change.getId())) { | ||||||
|  |         if (!includeZero && ap.getValue() == 0) { | ||||||
|  |           continue; | ||||||
|  |         } | ||||||
|  |         add(RecipientType.CC, ap.getAccountId()); | ||||||
|  |       } | ||||||
|  |     } catch (OrmException err) { | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected boolean isVisibleTo(final Account.Id to) { | ||||||
|  |     return projectState == null | ||||||
|  |         || change == null | ||||||
|  |         || projectState.controlFor(args.identifiedUserFactory.create(to)) | ||||||
|  |             .controlFor(change).isVisible(); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -65,7 +65,7 @@ public class CommentSender extends ReplyToChangeSender { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   @Override |   @Override | ||||||
|   protected void format() { |   protected void formatChange() { | ||||||
|     if (!"".equals(getCoverLetter()) || !inlineComments.isEmpty()) { |     if (!"".equals(getCoverLetter()) || !inlineComments.isEmpty()) { | ||||||
|       appendText("Comments on Patch Set " + patchSet.getPatchSetId() + ":\n"); |       appendText("Comments on Patch Set " + patchSet.getPatchSetId() + ":\n"); | ||||||
|       appendText("\n"); |       appendText("\n"); | ||||||
|   | |||||||
| @@ -37,7 +37,7 @@ public class MergeFailSender extends ReplyToChangeSender { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   @Override |   @Override | ||||||
|   protected void format() { |   protected void formatChange() { | ||||||
|     appendText("Change " + change.getKey().abbreviate()); |     appendText("Change " + change.getKey().abbreviate()); | ||||||
|     if (patchSetInfo != null && patchSetInfo.getAuthor() != null |     if (patchSetInfo != null && patchSetInfo.getAuthor() != null | ||||||
|         && patchSetInfo.getAuthor().getName() != null) { |         && patchSetInfo.getAuthor().getName() != null) { | ||||||
|   | |||||||
| @@ -61,7 +61,7 @@ public class MergedSender extends ReplyToChangeSender { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   @Override |   @Override | ||||||
|   protected void format() { |   protected void formatChange() { | ||||||
|     appendText("Change " + change.getKey().abbreviate()); |     appendText("Change " + change.getKey().abbreviate()); | ||||||
|     if (patchSetInfo != null && patchSetInfo.getAuthor() != null |     if (patchSetInfo != null && patchSetInfo.getAuthor() != null | ||||||
|         && patchSetInfo.getAuthor().getName() != null) { |         && patchSetInfo.getAuthor().getName() != null) { | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ import java.util.List; | |||||||
| import java.util.Set; | import java.util.Set; | ||||||
|  |  | ||||||
| /** Sends an email alerting a user to a new change for them to review. */ | /** Sends an email alerting a user to a new change for them to review. */ | ||||||
| public abstract class NewChangeSender extends OutgoingEmail { | public abstract class NewChangeSender extends ChangeEmail { | ||||||
|   private final SshInfo sshInfo; |   private final SshInfo sshInfo; | ||||||
|   private final Set<Account.Id> reviewers = new HashSet<Account.Id>(); |   private final Set<Account.Id> reviewers = new HashSet<Account.Id>(); | ||||||
|   private final Set<Account.Id> extraCC = new HashSet<Account.Id>(); |   private final Set<Account.Id> extraCC = new HashSet<Account.Id>(); | ||||||
| @@ -57,7 +57,7 @@ public abstract class NewChangeSender extends OutgoingEmail { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   @Override |   @Override | ||||||
|   protected void format() { |   protected void formatChange() { | ||||||
|     formatSalutation(); |     formatSalutation(); | ||||||
|     formatChangeDetail(); |     formatChangeDetail(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -64,54 +64,26 @@ public abstract class OutgoingEmail { | |||||||
|   private static final String HDR_CC = "CC"; |   private static final String HDR_CC = "CC"; | ||||||
|  |  | ||||||
|   private static final Random RNG = new Random(); |   private static final Random RNG = new Random(); | ||||||
|   private final String messageClass; |   protected String messageClass; | ||||||
|   protected final Change change; |  | ||||||
|   protected String projectName; |  | ||||||
|   private final HashSet<Account.Id> rcptTo = new HashSet<Account.Id>(); |   private final HashSet<Account.Id> rcptTo = new HashSet<Account.Id>(); | ||||||
|   private final Map<String, EmailHeader> headers; |   private final Map<String, EmailHeader> headers; | ||||||
|   private final List<Address> smtpRcptTo = new ArrayList<Address>(); |   private final List<Address> smtpRcptTo = new ArrayList<Address>(); | ||||||
|   private Address smtpFromAddress; |   private Address smtpFromAddress; | ||||||
|   private StringBuilder body; |   private StringBuilder body; | ||||||
|   private boolean inFooter; |  | ||||||
|  |  | ||||||
|   protected final EmailArguments args; |   protected final EmailArguments args; | ||||||
|   protected Account.Id fromId; |   protected Account.Id fromId; | ||||||
|   protected PatchSet patchSet; |  | ||||||
|   protected PatchSetInfo patchSetInfo; |  | ||||||
|   protected ChangeMessage changeMessage; |  | ||||||
|  |  | ||||||
|   private ProjectState projectState; |   protected OutgoingEmail(EmailArguments ea, final String mc) { | ||||||
|   protected ChangeData changeData; |  | ||||||
|  |  | ||||||
|   protected OutgoingEmail(EmailArguments ea, final Change c, final String mc) { |  | ||||||
|     args = ea; |     args = ea; | ||||||
|     change = c; |  | ||||||
|     changeData = change != null ? new ChangeData(change) : null; |  | ||||||
|     messageClass = mc; |     messageClass = mc; | ||||||
|     headers = new LinkedHashMap<String, EmailHeader>(); |     headers = new LinkedHashMap<String, EmailHeader>(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   protected OutgoingEmail(EmailArguments ea, final String mc) { |  | ||||||
|     this(ea, null, mc); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   public void setFrom(final Account.Id id) { |   public void setFrom(final Account.Id id) { | ||||||
|     fromId = id; |     fromId = id; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public void setPatchSet(final PatchSet ps) { |  | ||||||
|     patchSet = ps; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   public void setPatchSet(final PatchSet ps, final PatchSetInfo psi) { |  | ||||||
|     patchSet = ps; |  | ||||||
|     patchSetInfo = psi; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   public void setChangeMessage(final ChangeMessage cm) { |  | ||||||
|     changeMessage = cm; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * Format and enqueue the message for delivery. |    * Format and enqueue the message for delivery. | ||||||
|    * |    * | ||||||
| @@ -159,49 +131,6 @@ public abstract class OutgoingEmail { | |||||||
|         } |         } | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       if (change != null) { |  | ||||||
|         if (getChangeUrl() != null) { |  | ||||||
|           openFooter(); |  | ||||||
|           appendText("To view visit "); |  | ||||||
|           appendText(getChangeUrl()); |  | ||||||
|           appendText("\n"); |  | ||||||
|         } |  | ||||||
|         if (getSettingsUrl() != null) { |  | ||||||
|           openFooter(); |  | ||||||
|           appendText("To unsubscribe, visit "); |  | ||||||
|           appendText(getSettingsUrl()); |  | ||||||
|           appendText("\n"); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (inFooter) { |  | ||||||
|           appendText("\n"); |  | ||||||
|         } else { |  | ||||||
|           openFooter(); |  | ||||||
|         } |  | ||||||
|         appendText("Gerrit-MessageType: " + messageClass + "\n"); |  | ||||||
|         appendText("Gerrit-Project: " + projectName + "\n"); |  | ||||||
|         appendText("Gerrit-Branch: " + change.getDest().getShortName() + "\n"); |  | ||||||
|         appendText("Gerrit-Owner: " + getNameEmailFor(change.getOwner()) + "\n"); |  | ||||||
|  |  | ||||||
|         try { |  | ||||||
|           HashSet<Account.Id> reviewers = new HashSet<Account.Id>(); |  | ||||||
|           for (PatchSetApproval p : args.db.get().patchSetApprovals().byChange( |  | ||||||
|               change.getId())) { |  | ||||||
|             reviewers.add(p.getAccountId()); |  | ||||||
|           } |  | ||||||
|  |  | ||||||
|           TreeSet<String> names = new TreeSet<String>(); |  | ||||||
|           for (Account.Id who : reviewers) { |  | ||||||
|             names.add(getNameEmailFor(who)); |  | ||||||
|           } |  | ||||||
|  |  | ||||||
|           for (String name : names) { |  | ||||||
|             appendText("Gerrit-Reviewer: " + name + "\n"); |  | ||||||
|           } |  | ||||||
|         } catch (OrmException e) { |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       if (headers.get("Message-ID").isEmpty()) { |       if (headers.get("Message-ID").isEmpty()) { | ||||||
|         final StringBuilder rndid = new StringBuilder(); |         final StringBuilder rndid = new StringBuilder(); | ||||||
|         rndid.append("<"); |         rndid.append("<"); | ||||||
| @@ -223,27 +152,11 @@ public abstract class OutgoingEmail { | |||||||
|  |  | ||||||
|   /** Setup the message headers and envelope (TO, CC, BCC). */ |   /** Setup the message headers and envelope (TO, CC, BCC). */ | ||||||
|   protected void init() { |   protected void init() { | ||||||
|     if (change != null && args.projectCache != null) { |  | ||||||
|       projectState = args.projectCache.get(change.getProject()); |  | ||||||
|       projectName = |  | ||||||
|           projectState != null ? projectState.getProject().getName() : null; |  | ||||||
|     } else { |  | ||||||
|       projectState = null; |  | ||||||
|       projectName = null; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     smtpFromAddress = args.fromAddressGenerator.from(fromId); |     smtpFromAddress = args.fromAddressGenerator.from(fromId); | ||||||
|     if (changeMessage != null && changeMessage.getWrittenOn() != null) { |     setHeader("Date", new Date()); | ||||||
|       setHeader("Date", new Date(changeMessage.getWrittenOn().getTime())); |  | ||||||
|     } else { |  | ||||||
|       setHeader("Date", new Date()); |  | ||||||
|     } |  | ||||||
|     headers.put("From", new EmailHeader.AddressList(smtpFromAddress)); |     headers.put("From", new EmailHeader.AddressList(smtpFromAddress)); | ||||||
|     headers.put(HDR_TO, new EmailHeader.AddressList()); |     headers.put(HDR_TO, new EmailHeader.AddressList()); | ||||||
|     headers.put(HDR_CC, new EmailHeader.AddressList()); |     headers.put(HDR_CC, new EmailHeader.AddressList()); | ||||||
|     if (change != null) { |  | ||||||
|       setChangeSubjectHeader(); |  | ||||||
|     } |  | ||||||
|     setHeader("Message-ID", ""); |     setHeader("Message-ID", ""); | ||||||
|  |  | ||||||
|     if (fromId != null) { |     if (fromId != null) { | ||||||
| @@ -259,14 +172,7 @@ public abstract class OutgoingEmail { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     setHeader("X-Gerrit-MessageType", messageClass); |     setHeader("X-Gerrit-MessageType", messageClass); | ||||||
|     if (change != null) { |  | ||||||
|       setHeader("X-Gerrit-Change-Id", "" + change.getKey().get()); |  | ||||||
|       setListIdHeader(); |  | ||||||
|       setChangeUrlHeader(); |  | ||||||
|       setCommitIdHeader(); |  | ||||||
|     } |  | ||||||
|     body = new StringBuilder(); |     body = new StringBuilder(); | ||||||
|     inFooter = false; |  | ||||||
|  |  | ||||||
|     if (fromId != null && args.fromAddressGenerator.isGenericAddress(fromId)) { |     if (fromId != null && args.fromAddressGenerator.isGenericAddress(fromId)) { | ||||||
|       final Account account = args.accountCache.get(fromId).getAccount(); |       final Account account = args.accountCache.get(fromId).getAccount(); | ||||||
| @@ -285,75 +191,6 @@ public abstract class OutgoingEmail { | |||||||
|         body.append(":\n\n"); |         body.append(":\n\n"); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (change != null) { |  | ||||||
|       if (patchSet == null) { |  | ||||||
|         try { |  | ||||||
|           patchSet = args.db.get().patchSets().get(change.currentPatchSetId()); |  | ||||||
|         } catch (OrmException err) { |  | ||||||
|           patchSet = null; |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       if (patchSet != null && patchSetInfo == null) { |  | ||||||
|         try { |  | ||||||
|           patchSetInfo = args.patchSetInfoFactory.get(patchSet.getId()); |  | ||||||
|         } catch (PatchSetInfoNotAvailableException err) { |  | ||||||
|           patchSetInfo = null; |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private void setListIdHeader() { |  | ||||||
|     // Set a reasonable list id so that filters can be used to sort messages |  | ||||||
|     // |  | ||||||
|     final StringBuilder listid = new StringBuilder(); |  | ||||||
|     listid.append("gerrit-"); |  | ||||||
|     listid.append(projectName.replace('/', '-')); |  | ||||||
|     listid.append("@"); |  | ||||||
|     listid.append(getGerritHost()); |  | ||||||
|  |  | ||||||
|     final String listidStr = listid.toString(); |  | ||||||
|     setHeader("Mailing-List", "list " + listidStr); |  | ||||||
|     setHeader("List-Id", "<" + listidStr.replace('@', '.') + ">"); |  | ||||||
|     if (getSettingsUrl() != null) { |  | ||||||
|       setHeader("List-Unsubscribe", "<" + getSettingsUrl() + ">"); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private void setChangeUrlHeader() { |  | ||||||
|     final String u = getChangeUrl(); |  | ||||||
|     if (u != null) { |  | ||||||
|       setHeader("X-Gerrit-ChangeURL", "<" + u + ">"); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private void setCommitIdHeader() { |  | ||||||
|     if (patchSet != null && patchSet.getRevision() != null |  | ||||||
|         && patchSet.getRevision().get() != null |  | ||||||
|         && patchSet.getRevision().get().length() > 0) { |  | ||||||
|       setHeader("X-Gerrit-Commit", patchSet.getRevision().get()); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private void setChangeSubjectHeader() { |  | ||||||
|     final StringBuilder subj = new StringBuilder(); |  | ||||||
|     subj.append("["); |  | ||||||
|     subj.append(change.getDest().getShortName()); |  | ||||||
|     subj.append("] "); |  | ||||||
|     subj.append("Change "); |  | ||||||
|     subj.append(change.getKey().abbreviate()); |  | ||||||
|     subj.append(": ("); |  | ||||||
|     subj.append(projectName); |  | ||||||
|     subj.append(") "); |  | ||||||
|     if (change.getSubject().length() > 60) { |  | ||||||
|       subj.append(change.getSubject().substring(0, 60)); |  | ||||||
|       subj.append("..."); |  | ||||||
|     } else { |  | ||||||
|       subj.append(change.getSubject()); |  | ||||||
|     } |  | ||||||
|     setHeader("Subject", subj.toString()); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   protected String getGerritHost() { |   protected String getGerritHost() { | ||||||
| @@ -372,17 +209,6 @@ public abstract class OutgoingEmail { | |||||||
|     return SystemReader.getInstance().getHostname(); |     return SystemReader.getInstance().getHostname(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** Get a link to the change; null if the server doesn't know its own address. */ |  | ||||||
|   protected String getChangeUrl() { |  | ||||||
|     if (change != null && getGerritUrl() != null) { |  | ||||||
|       final StringBuilder r = new StringBuilder(); |  | ||||||
|       r.append(getGerritUrl()); |  | ||||||
|       r.append(change.getChangeId()); |  | ||||||
|       return r.toString(); |  | ||||||
|     } |  | ||||||
|     return null; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private String getSettingsUrl() { |   private String getSettingsUrl() { | ||||||
|     if (getGerritUrl() != null) { |     if (getGerritUrl() != null) { | ||||||
|       final StringBuilder r = new StringBuilder(); |       final StringBuilder r = new StringBuilder(); | ||||||
| @@ -397,20 +223,6 @@ public abstract class OutgoingEmail { | |||||||
|     return args.urlProvider.get(); |     return args.urlProvider.get(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   protected String getChangeMessageThreadId() { |  | ||||||
|     final StringBuilder r = new StringBuilder(); |  | ||||||
|     r.append('<'); |  | ||||||
|     r.append("gerrit"); |  | ||||||
|     r.append('.'); |  | ||||||
|     r.append(change.getCreatedOn().getTime()); |  | ||||||
|     r.append('.'); |  | ||||||
|     r.append(change.getKey().get()); |  | ||||||
|     r.append('@'); |  | ||||||
|     r.append(getGerritHost()); |  | ||||||
|     r.append('>'); |  | ||||||
|     return r.toString(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** Set a header in the outgoing message. */ |   /** Set a header in the outgoing message. */ | ||||||
|   protected void setHeader(final String name, final String value) { |   protected void setHeader(final String name, final String value) { | ||||||
|     headers.put(name, new EmailHeader.String(value)); |     headers.put(name, new EmailHeader.String(value)); | ||||||
| @@ -427,60 +239,6 @@ public abstract class OutgoingEmail { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private void openFooter() { |  | ||||||
|     if (!inFooter) { |  | ||||||
|       inFooter = true; |  | ||||||
|       appendText("-- \n"); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** Format the sender's "cover letter", {@link #getCoverLetter()}. */ |  | ||||||
|   protected void formatCoverLetter() { |  | ||||||
|     final String cover = getCoverLetter(); |  | ||||||
|     if (!"".equals(cover)) { |  | ||||||
|       appendText(cover); |  | ||||||
|       appendText("\n\n"); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** Get the text of the "cover letter", from {@link ChangeMessage}. */ |  | ||||||
|   protected String getCoverLetter() { |  | ||||||
|     if (changeMessage != null) { |  | ||||||
|       final String txt = changeMessage.getMessage(); |  | ||||||
|       if (txt != null) { |  | ||||||
|         return txt.trim(); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     return ""; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** Format the change message and the affected file list. */ |  | ||||||
|   protected void formatChangeDetail() { |  | ||||||
|     if (patchSetInfo != null) { |  | ||||||
|       appendText(patchSetInfo.getMessage().trim()); |  | ||||||
|       appendText("\n"); |  | ||||||
|     } else { |  | ||||||
|       appendText(change.getSubject().trim()); |  | ||||||
|       appendText("\n"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (patchSet != null) { |  | ||||||
|       appendText("---\n"); |  | ||||||
|       for (PatchListEntry p : getPatchList().getPatches()) { |  | ||||||
|         appendText(p.getChangeType().getCode() + " " + p.getNewName() + "\n"); |  | ||||||
|       } |  | ||||||
|       appendText("\n"); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** Get the patch list corresponding to this patch set. */ |  | ||||||
|   protected PatchList getPatchList() { |  | ||||||
|     if (patchSet != null) { |  | ||||||
|       return args.patchListCache.get(change, patchSet); |  | ||||||
|     } |  | ||||||
|     return null; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** Lookup a human readable name for an account, usually the "full name". */ |   /** Lookup a human readable name for an account, usually the "full name". */ | ||||||
|   protected String getNameFor(final Account.Id accountId) { |   protected String getNameFor(final Account.Id accountId) { | ||||||
|     if (accountId == null) { |     if (accountId == null) { | ||||||
| @@ -498,7 +256,7 @@ public abstract class OutgoingEmail { | |||||||
|     return name; |     return name; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private String getNameEmailFor(Account.Id accountId) { |   protected String getNameEmailFor(Account.Id accountId) { | ||||||
|     AccountState who = args.accountCache.get(accountId); |     AccountState who = args.accountCache.get(accountId); | ||||||
|     String name = who.getAccount().getFullName(); |     String name = who.getAccount().getFullName(); | ||||||
|     String email = who.getAccount().getPreferredEmail(); |     String email = who.getAccount().getPreferredEmail(); | ||||||
| @@ -540,19 +298,6 @@ public abstract class OutgoingEmail { | |||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** Get the project entity the change is in; null if its been deleted. */ |  | ||||||
|   protected ProjectState getProjectState() { |  | ||||||
|     return projectState; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** Get the groups which own the project. */ |  | ||||||
|   protected Set<AccountGroup.Id> getProjectOwners() { |  | ||||||
|     final ProjectState r; |  | ||||||
|  |  | ||||||
|     r = args.projectCache.get(change.getProject()); |  | ||||||
|     return r != null ? r.getOwners() : Collections.<AccountGroup.Id> emptySet(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** Schedule this message for delivery to the listed accounts. */ |   /** Schedule this message for delivery to the listed accounts. */ | ||||||
|   protected void add(final RecipientType rt, final Collection<Account.Id> list) { |   protected void add(final RecipientType rt, final Collection<Account.Id> list) { | ||||||
|     for (final Account.Id id : list) { |     for (final Account.Id id : list) { | ||||||
| @@ -560,130 +305,12 @@ public abstract class OutgoingEmail { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** TO or CC all vested parties (change owner, patch set uploader, author). */ |  | ||||||
|   protected void rcptToAuthors(final RecipientType rt) { |  | ||||||
|     add(rt, change.getOwner()); |  | ||||||
|     if (patchSet != null) { |  | ||||||
|       add(rt, patchSet.getUploader()); |  | ||||||
|     } |  | ||||||
|     if (patchSetInfo != null) { |  | ||||||
|       add(rt, patchSetInfo.getAuthor()); |  | ||||||
|       add(rt, patchSetInfo.getCommitter()); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private void add(final RecipientType rt, final UserIdentity who) { |   private void add(final RecipientType rt, final UserIdentity who) { | ||||||
|     if (who != null && who.getAccount() != null) { |     if (who != null && who.getAccount() != null) { | ||||||
|       add(rt, who.getAccount()); |       add(rt, who.getAccount()); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** BCC any user who has starred this change. */ |  | ||||||
|   protected void bccStarredBy() { |  | ||||||
|     try { |  | ||||||
|       // BCC anyone who has starred this change. |  | ||||||
|       // |  | ||||||
|       for (StarredChange w : args.db.get().starredChanges().byChange( |  | ||||||
|           change.getId())) { |  | ||||||
|         add(RecipientType.BCC, w.getAccountId()); |  | ||||||
|       } |  | ||||||
|     } catch (OrmException err) { |  | ||||||
|       // Just don't BCC 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. |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** BCC any user who has set "notify all comments" on this project. */ |  | ||||||
|   protected void bccWatchesNotifyAllComments() { |  | ||||||
|     try { |  | ||||||
|       // BCC anyone else who has interest in this project's changes |  | ||||||
|       // |  | ||||||
|       for (final AccountProjectWatch w : getWatches()) { |  | ||||||
|         if (w.isNotifyAllComments()) { |  | ||||||
|           add(RecipientType.BCC, w.getAccountId()); |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     } catch (OrmException 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. |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** Returns all watches that are relevant */ |  | ||||||
|   protected final List<AccountProjectWatch> getWatches() throws OrmException { |  | ||||||
|     if (changeData == null) { |  | ||||||
|       return Collections.emptyList(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     List<AccountProjectWatch> matching = new ArrayList<AccountProjectWatch>(); |  | ||||||
|     Set<Account.Id> projectWatchers = new HashSet<Account.Id>(); |  | ||||||
|  |  | ||||||
|     for (AccountProjectWatch w : args.db.get().accountProjectWatches() |  | ||||||
|         .byProject(change.getProject())) { |  | ||||||
|       projectWatchers.add(w.getAccountId()); |  | ||||||
|       add(matching, w); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     for (AccountProjectWatch w : args.db.get().accountProjectWatches() |  | ||||||
|         .byProject(args.wildProject)) { |  | ||||||
|       if (!projectWatchers.contains(w.getAccountId())) { |  | ||||||
|         add(matching, w); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return Collections.unmodifiableList(matching); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @SuppressWarnings("unchecked") |  | ||||||
|   private void add(List<AccountProjectWatch> matching, AccountProjectWatch w) |  | ||||||
|       throws OrmException { |  | ||||||
|     IdentifiedUser user = |  | ||||||
|         args.identifiedUserFactory.create(args.db, w.getAccountId()); |  | ||||||
|     ChangeQueryBuilder qb = args.queryBuilder.create(user); |  | ||||||
|     Predicate<ChangeData> p = qb.is_visible(); |  | ||||||
|     if (w.getFilter() != null) { |  | ||||||
|       try { |  | ||||||
|         qb.setAllowFile(true); |  | ||||||
|         p = Predicate.and(qb.parse(w.getFilter()), p); |  | ||||||
|         p = args.queryRewriter.get().rewrite(p); |  | ||||||
|         if (p.match(changeData)) { |  | ||||||
|           matching.add(w); |  | ||||||
|         } |  | ||||||
|       } catch (QueryParseException e) { |  | ||||||
|         // Ignore broken filter expressions. |  | ||||||
|       } |  | ||||||
|     } else if (p.match(changeData)) { |  | ||||||
|       matching.add(w); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** Any user who has published comments on this change. */ |  | ||||||
|   protected void ccAllApprovals() { |  | ||||||
|     ccApprovals(true); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** Users who have non-zero approval codes on the change. */ |  | ||||||
|   protected void ccExistingReviewers() { |  | ||||||
|     ccApprovals(false); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private void ccApprovals(final boolean includeZero) { |  | ||||||
|     try { |  | ||||||
|       // CC anyone else who has posted an approval mark on this change |  | ||||||
|       // |  | ||||||
|       for (PatchSetApproval ap : args.db.get().patchSetApprovals().byChange( |  | ||||||
|           change.getId())) { |  | ||||||
|         if (!includeZero && ap.getValue() == 0) { |  | ||||||
|           continue; |  | ||||||
|         } |  | ||||||
|         add(RecipientType.CC, ap.getAccountId()); |  | ||||||
|       } |  | ||||||
|     } catch (OrmException err) { |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** Schedule delivery of this message to the given account. */ |   /** Schedule delivery of this message to the given account. */ | ||||||
|   protected void add(final RecipientType rt, final Account.Id to) { |   protected void add(final RecipientType rt, final Account.Id to) { | ||||||
|     if (!rcptTo.contains(to) && isVisibleTo(to)) { |     if (!rcptTo.contains(to) && isVisibleTo(to)) { | ||||||
| @@ -692,11 +319,8 @@ public abstract class OutgoingEmail { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private boolean isVisibleTo(final Account.Id to) { |   protected boolean isVisibleTo(final Account.Id to) { | ||||||
|     return projectState == null |     return true; | ||||||
|         || change == null |  | ||||||
|         || projectState.controlFor(args.identifiedUserFactory.create(to)) |  | ||||||
|             .controlFor(change).isVisible(); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** Schedule delivery of this message to the given account. */ |   /** Schedule delivery of this message to the given account. */ | ||||||
|   | |||||||
| @@ -67,7 +67,7 @@ public class ReplacePatchSetSender extends ReplyToChangeSender { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   @Override |   @Override | ||||||
|   protected void format() { |   protected void formatChange() { | ||||||
|     formatSalutation(); |     formatSalutation(); | ||||||
|     formatChangeDetail(); |     formatChangeDetail(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ package com.google.gerrit.server.mail; | |||||||
| import com.google.gerrit.reviewdb.Change; | import com.google.gerrit.reviewdb.Change; | ||||||
|  |  | ||||||
| /** Alert a user to a reply to a change, usually commentary made during review. */ | /** Alert a user to a reply to a change, usually commentary made during review. */ | ||||||
| public abstract class ReplyToChangeSender extends OutgoingEmail { | public abstract class ReplyToChangeSender extends ChangeEmail { | ||||||
|   protected ReplyToChangeSender(EmailArguments ea, Change c, String mc) { |   protected ReplyToChangeSender(EmailArguments ea, Change c, String mc) { | ||||||
|     super(ea, c, mc); |     super(ea, c, mc); | ||||||
|   } |   } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Martin Fick
					Martin Fick