Line data Source code
1 : // Copyright (C) 2022 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 com.google.common.collect.ImmutableSet;
18 : import com.google.common.flogger.FluentLogger;
19 : import com.google.gerrit.entities.Account;
20 : import com.google.gerrit.entities.Change;
21 : import com.google.gerrit.entities.PatchSet;
22 : import com.google.gerrit.entities.PatchSetApproval;
23 : import com.google.gerrit.entities.Project;
24 : import com.google.gerrit.entities.SubmitRequirement;
25 : import com.google.gerrit.entities.SubmitRequirementResult;
26 : import com.google.gerrit.extensions.client.ChangeKind;
27 : import com.google.gerrit.server.CurrentUser;
28 : import com.google.gerrit.server.IdentifiedUser;
29 : import com.google.gerrit.server.config.SendEmailExecutor;
30 : import com.google.gerrit.server.mail.send.MessageIdGenerator;
31 : import com.google.gerrit.server.mail.send.MessageIdGenerator.MessageId;
32 : import com.google.gerrit.server.mail.send.ReplacePatchSetSender;
33 : import com.google.gerrit.server.patch.PatchSetInfoFactory;
34 : import com.google.gerrit.server.update.PostUpdateContext;
35 : import com.google.gerrit.server.util.RequestContext;
36 : import com.google.gerrit.server.util.RequestScopePropagator;
37 : import com.google.gerrit.server.util.ThreadLocalRequestContext;
38 : import com.google.inject.Inject;
39 : import com.google.inject.assistedinject.Assisted;
40 : import java.io.IOException;
41 : import java.io.UncheckedIOException;
42 : import java.time.Instant;
43 : import java.util.Map;
44 : import java.util.concurrent.ExecutorService;
45 : import java.util.concurrent.Future;
46 : import org.eclipse.jgit.lib.ObjectId;
47 :
48 : public class EmailNewPatchSet {
49 50 : private static final FluentLogger logger = FluentLogger.forEnclosingClass();
50 :
51 : public interface Factory {
52 : EmailNewPatchSet create(
53 : PostUpdateContext postUpdateContext,
54 : PatchSet patchSet,
55 : String message,
56 : ImmutableSet<PatchSetApproval> outdatedApprovals,
57 : @Assisted("reviewers") ImmutableSet<Account.Id> reviewers,
58 : @Assisted("extraCcs") ImmutableSet<Account.Id> extraCcs,
59 : ChangeKind changeKind,
60 : ObjectId preUpdateMetaId);
61 : }
62 :
63 : private final ExecutorService sendEmailExecutor;
64 : private final ThreadLocalRequestContext threadLocalRequestContext;
65 : private final AsyncSender asyncSender;
66 :
67 : private RequestScopePropagator requestScopePropagator;
68 :
69 : @Inject
70 : EmailNewPatchSet(
71 : @SendEmailExecutor ExecutorService sendEmailExecutor,
72 : ThreadLocalRequestContext threadLocalRequestContext,
73 : ReplacePatchSetSender.Factory replacePatchSetFactory,
74 : PatchSetInfoFactory patchSetInfoFactory,
75 : MessageIdGenerator messageIdGenerator,
76 : @Assisted PostUpdateContext postUpdateContext,
77 : @Assisted PatchSet patchSet,
78 : @Assisted String message,
79 : @Assisted ImmutableSet<PatchSetApproval> outdatedApprovals,
80 : @Assisted("reviewers") ImmutableSet<Account.Id> reviewers,
81 : @Assisted("extraCcs") ImmutableSet<Account.Id> extraCcs,
82 : @Assisted ChangeKind changeKind,
83 50 : @Assisted ObjectId preUpdateMetaId) {
84 50 : this.sendEmailExecutor = sendEmailExecutor;
85 50 : this.threadLocalRequestContext = threadLocalRequestContext;
86 :
87 : MessageId messageId;
88 : try {
89 50 : messageId =
90 50 : messageIdGenerator.fromChangeUpdateAndReason(
91 50 : postUpdateContext.getRepoView(), patchSet.id(), "EmailReplacePatchSet");
92 0 : } catch (IOException e) {
93 0 : throw new UncheckedIOException(e);
94 50 : }
95 :
96 50 : Change.Id changeId = patchSet.id().changeId();
97 :
98 : // Getting the change data from PostUpdateContext retrieves a cached ChangeData
99 : // instance. This ChangeData instance has been created when the change was (re)indexed
100 : // due to the update, and hence has submit requirement results already cached (since
101 : // (re)indexing triggers the evaluation of the submit requirements).
102 50 : Map<SubmitRequirement, SubmitRequirementResult> postUpdateSubmitRequirementResults =
103 : postUpdateContext
104 50 : .getChangeData(postUpdateContext.getProject(), changeId)
105 50 : .submitRequirementsIncludingLegacy();
106 50 : this.asyncSender =
107 : new AsyncSender(
108 50 : postUpdateContext.getIdentifiedUser(),
109 : replacePatchSetFactory,
110 : patchSetInfoFactory,
111 : messageId,
112 50 : postUpdateContext.getNotify(changeId),
113 50 : postUpdateContext.getProject(),
114 : changeId,
115 : patchSet,
116 : message,
117 50 : postUpdateContext.getWhen(),
118 : outdatedApprovals,
119 : reviewers,
120 : extraCcs,
121 : changeKind,
122 : preUpdateMetaId,
123 : postUpdateSubmitRequirementResults);
124 50 : }
125 :
126 : public EmailNewPatchSet setRequestScopePropagator(RequestScopePropagator requestScopePropagator) {
127 37 : this.requestScopePropagator = requestScopePropagator;
128 37 : return this;
129 : }
130 :
131 : public void sendAsync() {
132 : @SuppressWarnings("unused")
133 50 : Future<?> possiblyIgnoredError =
134 50 : sendEmailExecutor.submit(
135 50 : requestScopePropagator != null
136 37 : ? requestScopePropagator.wrap(asyncSender)
137 28 : : () -> {
138 28 : RequestContext old = threadLocalRequestContext.setContext(asyncSender);
139 : try {
140 28 : asyncSender.run();
141 : } finally {
142 28 : threadLocalRequestContext.setContext(old);
143 : }
144 28 : });
145 50 : }
146 :
147 : /**
148 : * {@link Runnable} that sends the email asynchonously.
149 : *
150 : * <p>Only pass objects into this class that are thread-safe (e.g. immutable) so that they can be
151 : * safely accessed from the background thread.
152 : */
153 : private static class AsyncSender implements Runnable, RequestContext {
154 : private final IdentifiedUser user;
155 : private final ReplacePatchSetSender.Factory replacePatchSetFactory;
156 : private final PatchSetInfoFactory patchSetInfoFactory;
157 : private final MessageId messageId;
158 : private final NotifyResolver.Result notify;
159 : private final Project.NameKey projectName;
160 : private final Change.Id changeId;
161 : private final PatchSet patchSet;
162 : private final String message;
163 : private final Instant timestamp;
164 : private final ImmutableSet<PatchSetApproval> outdatedApprovals;
165 : private final ImmutableSet<Account.Id> reviewers;
166 : private final ImmutableSet<Account.Id> extraCcs;
167 : private final ChangeKind changeKind;
168 : private final ObjectId preUpdateMetaId;
169 : private final Map<SubmitRequirement, SubmitRequirementResult>
170 : postUpdateSubmitRequirementResults;
171 :
172 : AsyncSender(
173 : IdentifiedUser user,
174 : ReplacePatchSetSender.Factory replacePatchSetFactory,
175 : PatchSetInfoFactory patchSetInfoFactory,
176 : MessageId messageId,
177 : NotifyResolver.Result notify,
178 : Project.NameKey projectName,
179 : Change.Id changeId,
180 : PatchSet patchSet,
181 : String message,
182 : Instant timestamp,
183 : ImmutableSet<PatchSetApproval> outdatedApprovals,
184 : ImmutableSet<Account.Id> reviewers,
185 : ImmutableSet<Account.Id> extraCcs,
186 : ChangeKind changeKind,
187 : ObjectId preUpdateMetaId,
188 50 : Map<SubmitRequirement, SubmitRequirementResult> postUpdateSubmitRequirementResults) {
189 50 : this.user = user;
190 50 : this.replacePatchSetFactory = replacePatchSetFactory;
191 50 : this.patchSetInfoFactory = patchSetInfoFactory;
192 50 : this.messageId = messageId;
193 50 : this.notify = notify;
194 50 : this.projectName = projectName;
195 50 : this.changeId = changeId;
196 50 : this.patchSet = patchSet;
197 50 : this.message = message;
198 50 : this.timestamp = timestamp;
199 50 : this.outdatedApprovals = outdatedApprovals;
200 50 : this.reviewers = reviewers;
201 50 : this.extraCcs = extraCcs;
202 50 : this.changeKind = changeKind;
203 50 : this.preUpdateMetaId = preUpdateMetaId;
204 50 : this.postUpdateSubmitRequirementResults = postUpdateSubmitRequirementResults;
205 50 : }
206 :
207 : @Override
208 : public void run() {
209 : try {
210 50 : ReplacePatchSetSender emailSender =
211 50 : replacePatchSetFactory.create(
212 : projectName,
213 : changeId,
214 : changeKind,
215 : preUpdateMetaId,
216 : postUpdateSubmitRequirementResults);
217 50 : emailSender.setFrom(user.getAccountId());
218 50 : emailSender.setPatchSet(patchSet, patchSetInfoFactory.get(projectName, patchSet));
219 50 : emailSender.setChangeMessage(message, timestamp);
220 50 : emailSender.setNotify(notify);
221 50 : emailSender.addReviewers(reviewers);
222 50 : emailSender.addExtraCC(extraCcs);
223 50 : emailSender.addOutdatedApproval(outdatedApprovals);
224 50 : emailSender.setMessageId(messageId);
225 50 : emailSender.send();
226 0 : } catch (Exception e) {
227 0 : logger.atSevere().withCause(e).log("Cannot send email for new patch set %s", patchSet.id());
228 50 : }
229 50 : }
230 :
231 : @Override
232 : public String toString() {
233 0 : return "send-email newpatchset";
234 : }
235 :
236 : @Override
237 : public CurrentUser getUser() {
238 28 : return user.getRealUser();
239 : }
240 : }
241 : }
|