Line data Source code
1 : // Copyright (C) 2012 The Android Open Source Project
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : package com.google.gerrit.server.change;
16 :
17 : import static com.google.gerrit.server.CommentsUtil.COMMENT_ORDER;
18 :
19 : import com.google.common.collect.ImmutableList;
20 : import com.google.common.flogger.FluentLogger;
21 : import com.google.gerrit.common.Nullable;
22 : import com.google.gerrit.entities.Change;
23 : import com.google.gerrit.entities.Comment;
24 : import com.google.gerrit.entities.PatchSet;
25 : import com.google.gerrit.entities.Project;
26 : import com.google.gerrit.entities.SubmitRequirement;
27 : import com.google.gerrit.entities.SubmitRequirementResult;
28 : import com.google.gerrit.server.CurrentUser;
29 : import com.google.gerrit.server.IdentifiedUser;
30 : import com.google.gerrit.server.config.SendEmailExecutor;
31 : import com.google.gerrit.server.mail.send.CommentSender;
32 : import com.google.gerrit.server.mail.send.MessageIdGenerator;
33 : import com.google.gerrit.server.mail.send.MessageIdGenerator.MessageId;
34 : import com.google.gerrit.server.patch.PatchSetInfoFactory;
35 : import com.google.gerrit.server.update.PostUpdateContext;
36 : import com.google.gerrit.server.util.LabelVote;
37 : import com.google.gerrit.server.util.RequestContext;
38 : import com.google.gerrit.server.util.ThreadLocalRequestContext;
39 : import com.google.inject.Inject;
40 : import com.google.inject.assistedinject.Assisted;
41 : import java.io.IOException;
42 : import java.io.UncheckedIOException;
43 : import java.time.Instant;
44 : import java.util.List;
45 : import java.util.Map;
46 : import java.util.concurrent.ExecutorService;
47 : import java.util.concurrent.Future;
48 : import org.eclipse.jgit.lib.ObjectId;
49 :
50 : public class EmailReviewComments {
51 65 : private static final FluentLogger logger = FluentLogger.forEnclosingClass();
52 :
53 : public interface Factory {
54 : // TODO(dborowitz/wyatta): Rationalize these arguments so HTML and text templates are operating
55 : // on the same set of inputs.
56 : /**
57 : * Creates handle for sending email
58 : *
59 : * @param postUpdateContext the post update context from the calling BatchUpdateOp
60 : * @param patchSet patch set corresponding to the top-level op
61 : * @param preUpdateMetaId the SHA1 to which the notes branch pointed before the update
62 : * @param message used by text template only. The contents of this message typically include the
63 : * "Patch set N" header and "(M comments)".
64 : * @param comments inline comments.
65 : * @param patchSetComment used by HTML template only: some quasi-human-generated text. The
66 : * contents should *not* include a "Patch set N" header or "(M comments)" footer, as these
67 : * will be added automatically in soy in a structured way.
68 : * @param labels labels applied as part of this review operation.
69 : */
70 : EmailReviewComments create(
71 : PostUpdateContext postUpdateContext,
72 : PatchSet patchSet,
73 : ObjectId preUpdateMetaId,
74 : @Assisted("message") String message,
75 : List<? extends Comment> comments,
76 : @Nullable @Assisted("patchSetComment") String patchSetComment,
77 : List<LabelVote> labels);
78 : }
79 :
80 : private final ExecutorService sendEmailsExecutor;
81 : private final AsyncSender asyncSender;
82 :
83 : @Inject
84 : EmailReviewComments(
85 : @SendEmailExecutor ExecutorService executor,
86 : PatchSetInfoFactory patchSetInfoFactory,
87 : CommentSender.Factory commentSenderFactory,
88 : ThreadLocalRequestContext requestContext,
89 : MessageIdGenerator messageIdGenerator,
90 : @Assisted PostUpdateContext postUpdateContext,
91 : @Assisted PatchSet patchSet,
92 : @Assisted ObjectId preUpdateMetaId,
93 : @Assisted("message") String message,
94 : @Assisted List<? extends Comment> comments,
95 : @Nullable @Assisted("patchSetComment") String patchSetComment,
96 65 : @Assisted List<LabelVote> labels) {
97 65 : this.sendEmailsExecutor = executor;
98 :
99 : MessageId messageId;
100 : try {
101 65 : messageId =
102 65 : messageIdGenerator.fromChangeUpdateAndReason(
103 65 : postUpdateContext.getRepoView(), patchSet.id(), "EmailReviewComments");
104 0 : } catch (IOException e) {
105 0 : throw new UncheckedIOException(e);
106 65 : }
107 :
108 65 : Change.Id changeId = patchSet.id().changeId();
109 :
110 : // Getting the change data from PostUpdateContext retrieves a cached ChangeData
111 : // instance. This ChangeData instance has been created when the change was (re)indexed
112 : // due to the update, and hence has submit requirement results already cached (since
113 : // (re)indexing triggers the evaluation of the submit requirements).
114 65 : Map<SubmitRequirement, SubmitRequirementResult> postUpdateSubmitRequirementResults =
115 : postUpdateContext
116 65 : .getChangeData(postUpdateContext.getProject(), changeId)
117 65 : .submitRequirementsIncludingLegacy();
118 65 : this.asyncSender =
119 : new AsyncSender(
120 : requestContext,
121 : commentSenderFactory,
122 : patchSetInfoFactory,
123 65 : postUpdateContext.getUser().asIdentifiedUser(),
124 : messageId,
125 65 : postUpdateContext.getNotify(changeId),
126 65 : postUpdateContext.getProject(),
127 : changeId,
128 : patchSet,
129 : preUpdateMetaId,
130 : message,
131 65 : postUpdateContext.getWhen(),
132 65 : ImmutableList.copyOf(COMMENT_ORDER.sortedCopy(comments)),
133 : patchSetComment,
134 65 : ImmutableList.copyOf(labels),
135 : postUpdateSubmitRequirementResults);
136 65 : }
137 :
138 : public void sendAsync() {
139 : @SuppressWarnings("unused")
140 65 : Future<?> possiblyIgnoredError = sendEmailsExecutor.submit(asyncSender);
141 65 : }
142 :
143 : /**
144 : * {@link Runnable} that sends the email asynchonously.
145 : *
146 : * <p>Only pass objects into this class that are thread-safe (e.g. immutable) so that they can be
147 : * safely accessed from the background thread.
148 : */
149 : // TODO: The passed in Comment class is not thread-safe, replace it with an AutoValue type.
150 : private static class AsyncSender implements Runnable, RequestContext {
151 : private final ThreadLocalRequestContext requestContext;
152 : private final CommentSender.Factory commentSenderFactory;
153 : private final PatchSetInfoFactory patchSetInfoFactory;
154 : private final IdentifiedUser user;
155 : private final MessageId messageId;
156 : private final NotifyResolver.Result notify;
157 : private final Project.NameKey projectName;
158 : private final Change.Id changeId;
159 : private final PatchSet patchSet;
160 : private final ObjectId preUpdateMetaId;
161 : private final String message;
162 : private final Instant timestamp;
163 : private final ImmutableList<? extends Comment> comments;
164 : @Nullable private final String patchSetComment;
165 : private final ImmutableList<LabelVote> labels;
166 : private final Map<SubmitRequirement, SubmitRequirementResult>
167 : postUpdateSubmitRequirementResults;
168 :
169 : AsyncSender(
170 : ThreadLocalRequestContext requestContext,
171 : CommentSender.Factory commentSenderFactory,
172 : PatchSetInfoFactory patchSetInfoFactory,
173 : IdentifiedUser user,
174 : MessageId messageId,
175 : NotifyResolver.Result notify,
176 : Project.NameKey projectName,
177 : Change.Id changeId,
178 : PatchSet patchSet,
179 : ObjectId preUpdateMetaId,
180 : String message,
181 : Instant timestamp,
182 : ImmutableList<? extends Comment> comments,
183 : @Nullable String patchSetComment,
184 : ImmutableList<LabelVote> labels,
185 65 : Map<SubmitRequirement, SubmitRequirementResult> postUpdateSubmitRequirementResults) {
186 65 : this.requestContext = requestContext;
187 65 : this.commentSenderFactory = commentSenderFactory;
188 65 : this.patchSetInfoFactory = patchSetInfoFactory;
189 65 : this.user = user;
190 65 : this.messageId = messageId;
191 65 : this.notify = notify;
192 65 : this.projectName = projectName;
193 65 : this.changeId = changeId;
194 65 : this.patchSet = patchSet;
195 65 : this.preUpdateMetaId = preUpdateMetaId;
196 65 : this.message = message;
197 65 : this.timestamp = timestamp;
198 65 : this.comments = comments;
199 65 : this.patchSetComment = patchSetComment;
200 65 : this.labels = labels;
201 65 : this.postUpdateSubmitRequirementResults = postUpdateSubmitRequirementResults;
202 65 : }
203 :
204 : @Override
205 : public void run() {
206 65 : RequestContext old = requestContext.setContext(this);
207 : try {
208 65 : CommentSender emailSender =
209 65 : commentSenderFactory.create(
210 : projectName, changeId, preUpdateMetaId, postUpdateSubmitRequirementResults);
211 65 : emailSender.setFrom(user.getAccountId());
212 65 : emailSender.setPatchSet(patchSet, patchSetInfoFactory.get(projectName, patchSet));
213 65 : emailSender.setChangeMessage(message, timestamp);
214 65 : emailSender.setComments(comments);
215 65 : emailSender.setPatchSetComment(patchSetComment);
216 65 : emailSender.setLabels(labels);
217 65 : emailSender.setNotify(notify);
218 65 : emailSender.setMessageId(messageId);
219 65 : emailSender.send();
220 0 : } catch (Exception e) {
221 0 : logger.atSevere().withCause(e).log("Cannot email comments for %s", patchSet.id());
222 : } finally {
223 65 : requestContext.setContext(old);
224 : }
225 65 : }
226 :
227 : @Override
228 : public String toString() {
229 0 : return "send-email comments";
230 : }
231 :
232 : @Override
233 : public CurrentUser getUser() {
234 65 : return user.getRealUser();
235 : }
236 : }
237 : }
|