Line data Source code
1 : // Copyright (C) 2010 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.events;
16 :
17 : import static java.util.Comparator.comparing;
18 : import static java.util.Objects.requireNonNull;
19 :
20 : import com.google.common.collect.ListMultimap;
21 : import com.google.common.collect.Lists;
22 : import com.google.common.flogger.FluentLogger;
23 : import com.google.gerrit.common.Nullable;
24 : import com.google.gerrit.entities.Account;
25 : import com.google.gerrit.entities.BranchNameKey;
26 : import com.google.gerrit.entities.Change;
27 : import com.google.gerrit.entities.ChangeMessage;
28 : import com.google.gerrit.entities.HumanComment;
29 : import com.google.gerrit.entities.LabelType;
30 : import com.google.gerrit.entities.LabelTypes;
31 : import com.google.gerrit.entities.LegacySubmitRequirement;
32 : import com.google.gerrit.entities.PatchSet;
33 : import com.google.gerrit.entities.PatchSetApproval;
34 : import com.google.gerrit.entities.SubmitRecord;
35 : import com.google.gerrit.entities.UserIdentity;
36 : import com.google.gerrit.exceptions.StorageException;
37 : import com.google.gerrit.extensions.registration.DynamicItem;
38 : import com.google.gerrit.index.IndexConfig;
39 : import com.google.gerrit.server.GerritPersonIdent;
40 : import com.google.gerrit.server.account.AccountAttributeLoader;
41 : import com.google.gerrit.server.account.AccountCache;
42 : import com.google.gerrit.server.account.AccountState;
43 : import com.google.gerrit.server.account.Emails;
44 : import com.google.gerrit.server.approval.ApprovalsUtil;
45 : import com.google.gerrit.server.change.ChangeKindCache;
46 : import com.google.gerrit.server.config.UrlFormatter;
47 : import com.google.gerrit.server.data.AccountAttribute;
48 : import com.google.gerrit.server.data.ApprovalAttribute;
49 : import com.google.gerrit.server.data.ChangeAttribute;
50 : import com.google.gerrit.server.data.DependencyAttribute;
51 : import com.google.gerrit.server.data.MessageAttribute;
52 : import com.google.gerrit.server.data.PatchAttribute;
53 : import com.google.gerrit.server.data.PatchSetAttribute;
54 : import com.google.gerrit.server.data.PatchSetCommentAttribute;
55 : import com.google.gerrit.server.data.RefUpdateAttribute;
56 : import com.google.gerrit.server.data.SubmitLabelAttribute;
57 : import com.google.gerrit.server.data.SubmitRecordAttribute;
58 : import com.google.gerrit.server.data.SubmitRequirementAttribute;
59 : import com.google.gerrit.server.data.TrackingIdAttribute;
60 : import com.google.gerrit.server.notedb.ChangeNotes;
61 : import com.google.gerrit.server.patch.DiffNotAvailableException;
62 : import com.google.gerrit.server.patch.DiffOperations;
63 : import com.google.gerrit.server.patch.DiffOptions;
64 : import com.google.gerrit.server.patch.FilePathAdapter;
65 : import com.google.gerrit.server.patch.filediff.FileDiffOutput;
66 : import com.google.gerrit.server.query.change.ChangeData;
67 : import com.google.gerrit.server.query.change.InternalChangeQuery;
68 : import com.google.gerrit.server.util.AccountTemplateUtil;
69 : import com.google.inject.Inject;
70 : import com.google.inject.Provider;
71 : import com.google.inject.Singleton;
72 : import java.io.IOException;
73 : import java.util.ArrayList;
74 : import java.util.Collection;
75 : import java.util.List;
76 : import java.util.Map;
77 : import java.util.Optional;
78 : import java.util.Set;
79 : import org.eclipse.jgit.lib.ObjectId;
80 : import org.eclipse.jgit.lib.PersonIdent;
81 : import org.eclipse.jgit.revwalk.RevCommit;
82 : import org.eclipse.jgit.revwalk.RevWalk;
83 :
84 : @Singleton
85 : public class EventFactory {
86 138 : private static final FluentLogger logger = FluentLogger.forEnclosingClass();
87 :
88 : private final AccountCache accountCache;
89 : private final DynamicItem<UrlFormatter> urlFormatter;
90 : private final DiffOperations diffOperations;
91 : private final Emails emails;
92 : private final Provider<PersonIdent> myIdent;
93 : private final ChangeData.Factory changeDataFactory;
94 : private final ApprovalsUtil approvalsUtil;
95 : private final ChangeKindCache changeKindCache;
96 : private final Provider<InternalChangeQuery> queryProvider;
97 : private final IndexConfig indexConfig;
98 : private final AccountTemplateUtil accountTemplateUtil;
99 :
100 : @Inject
101 : EventFactory(
102 : AccountCache accountCache,
103 : Emails emails,
104 : DynamicItem<UrlFormatter> urlFormatter,
105 : DiffOperations diffOperations,
106 : @GerritPersonIdent Provider<PersonIdent> myIdent,
107 : ChangeData.Factory changeDataFactory,
108 : ApprovalsUtil approvalsUtil,
109 : ChangeKindCache changeKindCache,
110 : Provider<InternalChangeQuery> queryProvider,
111 : IndexConfig indexConfig,
112 138 : AccountTemplateUtil accountTemplateUtil) {
113 138 : this.accountCache = accountCache;
114 138 : this.urlFormatter = urlFormatter;
115 138 : this.emails = emails;
116 138 : this.diffOperations = diffOperations;
117 138 : this.myIdent = myIdent;
118 138 : this.changeDataFactory = changeDataFactory;
119 138 : this.approvalsUtil = approvalsUtil;
120 138 : this.changeKindCache = changeKindCache;
121 138 : this.queryProvider = queryProvider;
122 138 : this.indexConfig = indexConfig;
123 138 : this.accountTemplateUtil = accountTemplateUtil;
124 138 : }
125 :
126 : public ChangeAttribute asChangeAttribute(Change change, AccountAttributeLoader accountLoader) {
127 10 : ChangeAttribute a = new ChangeAttribute();
128 10 : a.project = change.getProject().get();
129 10 : a.branch = change.getDest().shortName();
130 10 : a.topic = change.getTopic();
131 10 : a.id = change.getKey().get();
132 10 : a.number = change.getId().get();
133 10 : a.subject = change.getSubject();
134 10 : a.url = getChangeUrl(change);
135 10 : a.owner = asAccountAttribute(change.getOwner(), accountLoader);
136 10 : a.assignee = asAccountAttribute(change.getAssignee(), accountLoader);
137 10 : a.status = change.getStatus();
138 10 : a.createdOn = change.getCreatedOn().getEpochSecond();
139 10 : a.wip = change.isWorkInProgress() ? true : null;
140 10 : a.isPrivate = change.isPrivate() ? true : null;
141 10 : a.cherryPickOfChange =
142 10 : change.getCherryPickOf() != null ? change.getCherryPickOf().changeId().get() : null;
143 10 : a.cherryPickOfPatchSet =
144 10 : change.getCherryPickOf() != null ? change.getCherryPickOf().get() : null;
145 10 : return a;
146 : }
147 :
148 : /** Create a {@link ChangeAttribute} instance from the specified change. */
149 : public ChangeAttribute asChangeAttribute(Change change, ChangeNotes notes) {
150 8 : ChangeAttribute a = asChangeAttribute(change, (AccountAttributeLoader) null);
151 8 : addHashTags(a, notes);
152 8 : addCommitMessage(a, notes);
153 8 : return a;
154 : }
155 : /**
156 : * Create a {@link RefUpdateAttribute} for the given old ObjectId, new ObjectId, and branch that
157 : * is suitable for serialization to JSON.
158 : */
159 : public RefUpdateAttribute asRefUpdateAttribute(
160 : ObjectId oldId, ObjectId newId, BranchNameKey refName) {
161 115 : RefUpdateAttribute ru = new RefUpdateAttribute();
162 115 : ru.newRev = newId != null ? newId.getName() : ObjectId.zeroId().getName();
163 115 : ru.oldRev = oldId != null ? oldId.getName() : ObjectId.zeroId().getName();
164 115 : ru.project = refName.project().get();
165 115 : ru.refName = refName.branch();
166 115 : return ru;
167 : }
168 :
169 : /** Extend the existing {@link ChangeAttribute} with additional fields. */
170 : public void extend(ChangeAttribute a, Change change) {
171 3 : a.lastUpdated = change.getLastUpdatedOn().getEpochSecond();
172 3 : a.open = change.isNew();
173 3 : }
174 :
175 : /** Add allReviewers to an existing {@link ChangeAttribute}. */
176 : public void addAllReviewers(
177 : ChangeAttribute a, ChangeNotes notes, AccountAttributeLoader accountLoader) {
178 1 : Collection<Account.Id> reviewers = approvalsUtil.getReviewers(notes).all();
179 1 : if (!reviewers.isEmpty()) {
180 1 : a.allReviewers = Lists.newArrayListWithCapacity(reviewers.size());
181 1 : for (Account.Id id : reviewers) {
182 1 : a.allReviewers.add(asAccountAttribute(id, accountLoader));
183 1 : }
184 : }
185 1 : }
186 :
187 : /** Add submitRecords to an existing {@link ChangeAttribute}. */
188 : public void addSubmitRecords(
189 : ChangeAttribute ca, List<SubmitRecord> submitRecords, AccountAttributeLoader accountLoader) {
190 1 : ca.submitRecords = new ArrayList<>();
191 :
192 1 : for (SubmitRecord submitRecord : submitRecords) {
193 1 : SubmitRecordAttribute sa = new SubmitRecordAttribute();
194 1 : sa.status = submitRecord.status.name();
195 1 : if (submitRecord.status != SubmitRecord.Status.RULE_ERROR) {
196 1 : addSubmitRecordLabels(submitRecord, sa, accountLoader);
197 1 : addSubmitRecordRequirements(submitRecord, sa);
198 : }
199 1 : ca.submitRecords.add(sa);
200 1 : }
201 : // Remove empty lists so a confusing label won't be displayed in the output.
202 1 : if (ca.submitRecords.isEmpty()) {
203 0 : ca.submitRecords = null;
204 : }
205 1 : }
206 :
207 : private void addSubmitRecordLabels(
208 : SubmitRecord submitRecord, SubmitRecordAttribute sa, AccountAttributeLoader accountLoader) {
209 1 : if (submitRecord.labels != null && !submitRecord.labels.isEmpty()) {
210 1 : sa.labels = new ArrayList<>();
211 1 : for (SubmitRecord.Label lbl : submitRecord.labels) {
212 1 : SubmitLabelAttribute la = new SubmitLabelAttribute();
213 1 : la.label = lbl.label;
214 1 : la.status = lbl.status.name();
215 1 : if (lbl.appliedBy != null) {
216 0 : la.by = asAccountAttribute(lbl.appliedBy, accountLoader);
217 : }
218 1 : sa.labels.add(la);
219 1 : }
220 : }
221 1 : }
222 :
223 : private void addSubmitRecordRequirements(SubmitRecord submitRecord, SubmitRecordAttribute sa) {
224 1 : if (submitRecord.requirements != null && !submitRecord.requirements.isEmpty()) {
225 0 : sa.requirements = new ArrayList<>();
226 0 : for (LegacySubmitRequirement req : submitRecord.requirements) {
227 0 : SubmitRequirementAttribute re = new SubmitRequirementAttribute();
228 0 : re.fallbackText = req.fallbackText();
229 0 : re.type = req.type();
230 0 : sa.requirements.add(re);
231 0 : }
232 : }
233 1 : }
234 :
235 : public void addDependencies(RevWalk rw, ChangeAttribute ca, Change change, PatchSet currentPs) {
236 1 : if (change == null || currentPs == null) {
237 0 : return;
238 : }
239 1 : ca.dependsOn = new ArrayList<>();
240 1 : ca.neededBy = new ArrayList<>();
241 : try {
242 1 : addDependsOn(rw, ca, change, currentPs);
243 1 : addNeededBy(rw, ca, change, currentPs);
244 0 : } catch (StorageException | IOException e) {
245 : // Squash DB exceptions and leave dependency lists partially filled.
246 1 : }
247 : // Remove empty lists so a confusing label won't be displayed in the output.
248 1 : if (ca.dependsOn.isEmpty()) {
249 1 : ca.dependsOn = null;
250 : }
251 1 : if (ca.neededBy.isEmpty()) {
252 1 : ca.neededBy = null;
253 : }
254 1 : }
255 :
256 : private void addDependsOn(RevWalk rw, ChangeAttribute ca, Change change, PatchSet currentPs)
257 : throws IOException {
258 1 : RevCommit commit = rw.parseCommit(currentPs.commitId());
259 1 : final List<String> parentNames = new ArrayList<>(commit.getParentCount());
260 1 : for (RevCommit p : commit.getParents()) {
261 1 : parentNames.add(p.name());
262 : }
263 :
264 : // Find changes in this project having a patch set matching any parent of
265 : // this patch set's revision.
266 1 : for (ChangeData cd : queryProvider.get().byProjectCommits(change.getProject(), parentNames)) {
267 1 : for (PatchSet ps : cd.patchSets()) {
268 1 : for (String p : parentNames) {
269 1 : if (!ps.commitId().name().equals(p)) {
270 0 : continue;
271 : }
272 1 : ca.dependsOn.add(newDependsOn(requireNonNull(cd.change()), ps));
273 1 : }
274 1 : }
275 1 : }
276 : // Sort by original parent order.
277 1 : ca.dependsOn.sort(
278 1 : comparing(
279 : d -> {
280 0 : for (int i = 0; i < parentNames.size(); i++) {
281 0 : if (parentNames.get(i).equals(d.revision)) {
282 0 : return i;
283 : }
284 : }
285 0 : return parentNames.size() + 1;
286 : }));
287 1 : }
288 :
289 : private void addNeededBy(RevWalk rw, ChangeAttribute ca, Change change, PatchSet currentPs)
290 : throws IOException {
291 1 : if (currentPs.groups().isEmpty()) {
292 0 : return;
293 : }
294 1 : String rev = currentPs.commitId().name();
295 : // Find changes in the same related group as this patch set, having a patch
296 : // set whose parent matches this patch set's revision.
297 : for (ChangeData cd :
298 1 : InternalChangeQuery.byProjectGroups(
299 1 : queryProvider, indexConfig, change.getProject(), currentPs.groups())) {
300 : PATCH_SETS:
301 1 : for (PatchSet ps : cd.patchSets()) {
302 1 : RevCommit commit = rw.parseCommit(ps.commitId());
303 1 : for (RevCommit p : commit.getParents()) {
304 1 : if (!p.name().equals(rev)) {
305 1 : continue;
306 : }
307 1 : ca.neededBy.add(newNeededBy(requireNonNull(cd.change()), ps));
308 1 : continue PATCH_SETS;
309 : }
310 1 : }
311 1 : }
312 1 : }
313 :
314 : private DependencyAttribute newDependsOn(Change c, PatchSet ps) {
315 1 : DependencyAttribute d = newDependencyAttribute(c, ps);
316 1 : d.isCurrentPatchSet = ps.id().equals(c.currentPatchSetId());
317 1 : return d;
318 : }
319 :
320 : private DependencyAttribute newNeededBy(Change c, PatchSet ps) {
321 1 : return newDependencyAttribute(c, ps);
322 : }
323 :
324 : private DependencyAttribute newDependencyAttribute(Change c, PatchSet ps) {
325 1 : DependencyAttribute d = new DependencyAttribute();
326 1 : d.number = c.getId().get();
327 1 : d.id = c.getKey().toString();
328 1 : d.revision = ps.commitId().name();
329 1 : d.ref = ps.refName();
330 1 : return d;
331 : }
332 :
333 : public void addTrackingIds(ChangeAttribute a, ListMultimap<String, String> set) {
334 0 : if (!set.isEmpty()) {
335 0 : a.trackingIds = new ArrayList<>(set.size());
336 0 : for (Map.Entry<String, Collection<String>> e : set.asMap().entrySet()) {
337 0 : for (String id : e.getValue()) {
338 0 : TrackingIdAttribute t = new TrackingIdAttribute();
339 0 : t.system = e.getKey();
340 0 : t.id = id;
341 0 : a.trackingIds.add(t);
342 0 : }
343 0 : }
344 : }
345 0 : }
346 :
347 : public void addCommitMessage(ChangeAttribute a, String commitMessage) {
348 8 : a.commitMessage = commitMessage;
349 8 : }
350 :
351 : private void addCommitMessage(ChangeAttribute changeAttribute, ChangeNotes notes) {
352 : try {
353 8 : addCommitMessage(changeAttribute, changeDataFactory.create(notes).commitMessage());
354 0 : } catch (Exception e) {
355 0 : logger.atSevere().withCause(e).log(
356 : "Error while getting full commit message for change %d", changeAttribute.number);
357 8 : }
358 8 : }
359 :
360 : public void addPatchSets(
361 : RevWalk revWalk,
362 : ChangeAttribute ca,
363 : Collection<PatchSet> ps,
364 : Map<PatchSet.Id, Collection<PatchSetApproval>> approvals,
365 : LabelTypes labelTypes,
366 : AccountAttributeLoader accountLoader) {
367 0 : addPatchSets(revWalk, ca, ps, approvals, false, null, labelTypes, accountLoader);
368 0 : }
369 :
370 : public void addPatchSets(
371 : RevWalk revWalk,
372 : ChangeAttribute ca,
373 : Collection<PatchSet> ps,
374 : Map<PatchSet.Id, Collection<PatchSetApproval>> approvals,
375 : boolean includeFiles,
376 : Change change,
377 : LabelTypes labelTypes,
378 : AccountAttributeLoader accountLoader) {
379 1 : if (!ps.isEmpty()) {
380 1 : ca.patchSets = new ArrayList<>(ps.size());
381 1 : for (PatchSet p : ps) {
382 1 : PatchSetAttribute psa = asPatchSetAttribute(revWalk, change, p, accountLoader);
383 1 : if (approvals != null) {
384 1 : addApprovals(psa, p.id(), approvals, labelTypes, accountLoader);
385 : }
386 1 : ca.patchSets.add(psa);
387 1 : if (includeFiles) {
388 1 : addPatchSetFileNames(psa, change, p);
389 : }
390 1 : }
391 : }
392 1 : }
393 :
394 : public void addPatchSetComments(
395 : PatchSetAttribute patchSetAttribute,
396 : Collection<HumanComment> comments,
397 : AccountAttributeLoader accountLoader) {
398 1 : for (HumanComment comment : comments) {
399 1 : if (comment.key.patchSetId == patchSetAttribute.number) {
400 1 : if (patchSetAttribute.comments == null) {
401 1 : patchSetAttribute.comments = new ArrayList<>();
402 : }
403 1 : patchSetAttribute.comments.add(asPatchSetLineAttribute(comment, accountLoader));
404 : }
405 1 : }
406 1 : }
407 :
408 : public void addPatchSetFileNames(
409 : PatchSetAttribute patchSetAttribute, Change change, PatchSet patchSet) {
410 : try {
411 1 : Map<String, FileDiffOutput> modifiedFiles =
412 1 : diffOperations.listModifiedFilesAgainstParent(
413 1 : change.getProject(), patchSet.commitId(), /* parentNum= */ 0, DiffOptions.DEFAULTS);
414 :
415 1 : for (FileDiffOutput diff : modifiedFiles.values()) {
416 1 : if (patchSetAttribute.files == null) {
417 1 : patchSetAttribute.files = new ArrayList<>();
418 : }
419 :
420 1 : PatchAttribute p = new PatchAttribute();
421 1 : p.file = FilePathAdapter.getNewPath(diff.oldPath(), diff.newPath(), diff.changeType());
422 1 : p.fileOld = FilePathAdapter.getOldPath(diff.oldPath(), diff.changeType());
423 1 : p.type = diff.changeType();
424 1 : p.deletions -= diff.deletions();
425 1 : p.insertions = diff.insertions();
426 1 : patchSetAttribute.files.add(p);
427 1 : }
428 0 : } catch (DiffNotAvailableException e) {
429 0 : logger.atSevere().withCause(e).log("Cannot get patch list");
430 1 : }
431 1 : }
432 :
433 : public void addComments(
434 : ChangeAttribute ca,
435 : Collection<ChangeMessage> messages,
436 : AccountAttributeLoader accountLoader) {
437 1 : if (!messages.isEmpty()) {
438 1 : ca.comments = new ArrayList<>();
439 1 : for (ChangeMessage message : messages) {
440 1 : ca.comments.add(asMessageAttribute(message, accountLoader));
441 1 : }
442 : }
443 1 : }
444 :
445 : public PatchSetAttribute asPatchSetAttribute(RevWalk revWalk, Change change, PatchSet patchSet) {
446 1 : return asPatchSetAttribute(revWalk, change, patchSet, null);
447 : }
448 :
449 : /** Create a PatchSetAttribute for the given patchset suitable for serialization to JSON. */
450 : public PatchSetAttribute asPatchSetAttribute(
451 : RevWalk revWalk, Change change, PatchSet patchSet, AccountAttributeLoader accountLoader) {
452 1 : PatchSetAttribute p = new PatchSetAttribute();
453 1 : p.revision = patchSet.commitId().name();
454 1 : p.number = patchSet.number();
455 1 : p.ref = patchSet.refName();
456 1 : p.uploader = asAccountAttribute(patchSet.uploader(), accountLoader);
457 1 : p.createdOn = patchSet.createdOn().getEpochSecond();
458 1 : PatchSet.Id pId = patchSet.id();
459 : try {
460 1 : p.parents = new ArrayList<>();
461 1 : RevCommit c = revWalk.parseCommit(ObjectId.fromString(p.revision));
462 1 : for (RevCommit parent : c.getParents()) {
463 1 : p.parents.add(parent.name());
464 : }
465 :
466 1 : UserIdentity author = emails.toUserIdentity(c.getAuthorIdent());
467 1 : if (author.getAccount() == null) {
468 0 : p.author = new AccountAttribute();
469 0 : p.author.email = author.getEmail();
470 0 : p.author.name = author.getName();
471 0 : p.author.username = "";
472 : } else {
473 1 : p.author = asAccountAttribute(author.getAccount(), accountLoader);
474 : }
475 :
476 1 : Map<String, FileDiffOutput> modifiedFiles =
477 1 : diffOperations.listModifiedFilesAgainstParent(
478 1 : change.getProject(), patchSet.commitId(), /* parentNum= */ 0, DiffOptions.DEFAULTS);
479 1 : for (FileDiffOutput fileDiff : modifiedFiles.values()) {
480 1 : p.sizeDeletions += fileDiff.deletions();
481 1 : p.sizeInsertions += fileDiff.insertions();
482 1 : }
483 1 : p.kind = changeKindCache.getChangeKind(change, patchSet);
484 0 : } catch (IOException | StorageException e) {
485 0 : logger.atSevere().withCause(e).log("Cannot load patch set data for %s", patchSet.id());
486 0 : } catch (DiffNotAvailableException e) {
487 0 : logger.atSevere().withCause(e).log("Cannot get size information for %s.", pId);
488 1 : }
489 1 : return p;
490 : }
491 :
492 : public void addApprovals(
493 : PatchSetAttribute p,
494 : PatchSet.Id id,
495 : Map<PatchSet.Id, Collection<PatchSetApproval>> all,
496 : LabelTypes labelTypes,
497 : AccountAttributeLoader accountLoader) {
498 1 : Collection<PatchSetApproval> list = all.get(id);
499 1 : if (list != null) {
500 1 : addApprovals(p, list, labelTypes, accountLoader);
501 : }
502 1 : }
503 :
504 : public void addApprovals(
505 : PatchSetAttribute p,
506 : Collection<PatchSetApproval> list,
507 : LabelTypes labelTypes,
508 : AccountAttributeLoader accountLoader) {
509 1 : if (!list.isEmpty()) {
510 1 : p.approvals = new ArrayList<>(list.size());
511 1 : for (PatchSetApproval a : list) {
512 1 : if (a.value() != 0) {
513 1 : p.approvals.add(asApprovalAttribute(a, labelTypes, accountLoader));
514 : }
515 1 : }
516 1 : if (p.approvals.isEmpty()) {
517 0 : p.approvals = null;
518 : }
519 : }
520 1 : }
521 :
522 : public AccountAttribute asAccountAttribute(Account.Id id, AccountAttributeLoader accountLoader) {
523 10 : return accountLoader != null ? accountLoader.get(id) : asAccountAttribute(id);
524 : }
525 :
526 : /** Create an AuthorAttribute for the given account suitable for serialization to JSON. */
527 : @Nullable
528 : public AccountAttribute asAccountAttribute(Account.Id id) {
529 8 : if (id == null) {
530 8 : return null;
531 : }
532 8 : return accountCache.get(id).map(this::asAccountAttribute).orElse(null);
533 : }
534 :
535 : /** Create an AuthorAttribute for the given account suitable for serialization to JSON. */
536 : public AccountAttribute asAccountAttribute(AccountState accountState) {
537 8 : AccountAttribute who = new AccountAttribute();
538 8 : who.name = accountState.account().fullName();
539 8 : who.email = accountState.account().preferredEmail();
540 8 : who.username = accountState.userName().orElse(null);
541 8 : return who;
542 : }
543 :
544 : /** Create an AuthorAttribute for the given person ident suitable for serialization to JSON. */
545 : public AccountAttribute asAccountAttribute(PersonIdent ident) {
546 0 : AccountAttribute who = new AccountAttribute();
547 0 : who.name = ident.getName();
548 0 : who.email = ident.getEmailAddress();
549 0 : return who;
550 : }
551 :
552 : /**
553 : * Create an ApprovalAttribute for the given approval suitable for serialization to JSON.
554 : *
555 : * @param labelTypes label types for the containing project
556 : * @return object suitable for serialization to JSON
557 : */
558 : public ApprovalAttribute asApprovalAttribute(
559 : PatchSetApproval approval, LabelTypes labelTypes, AccountAttributeLoader accountLoader) {
560 1 : ApprovalAttribute a = new ApprovalAttribute();
561 1 : a.type = approval.labelId().get();
562 1 : a.value = Short.toString(approval.value());
563 1 : a.by = asAccountAttribute(approval.accountId(), accountLoader);
564 1 : a.grantedOn = approval.granted().getEpochSecond();
565 1 : a.oldValue = null;
566 :
567 1 : Optional<LabelType> lt = labelTypes.byLabel(approval.labelId());
568 1 : lt.ifPresent(l -> a.description = l.getName());
569 1 : return a;
570 : }
571 :
572 : public MessageAttribute asMessageAttribute(
573 : ChangeMessage message, AccountAttributeLoader accountLoader) {
574 1 : MessageAttribute a = new MessageAttribute();
575 1 : a.timestamp = message.getWrittenOn().getEpochSecond();
576 1 : a.reviewer =
577 1 : message.getAuthor() != null
578 1 : ? asAccountAttribute(message.getAuthor(), accountLoader)
579 1 : : asAccountAttribute(myIdent.get());
580 1 : a.message = accountTemplateUtil.replaceTemplates(message.getMessage());
581 1 : return a;
582 : }
583 :
584 : public PatchSetCommentAttribute asPatchSetLineAttribute(
585 : HumanComment c, AccountAttributeLoader accountLoader) {
586 1 : PatchSetCommentAttribute a = new PatchSetCommentAttribute();
587 1 : a.reviewer = asAccountAttribute(c.author.getId(), accountLoader);
588 1 : a.file = c.key.filename;
589 1 : a.line = c.lineNbr;
590 1 : a.message = c.message;
591 1 : return a;
592 : }
593 :
594 : /** Get a link to the change; null if the server doesn't know its own address. */
595 : @Nullable
596 : private String getChangeUrl(Change change) {
597 10 : if (change != null) {
598 10 : return urlFormatter.get().getChangeViewUrl(change.getProject(), change.getId()).orElse(null);
599 : }
600 0 : return null;
601 : }
602 :
603 : private void addHashTags(ChangeAttribute changeAttribute, ChangeNotes notes) {
604 8 : Set<String> hashtags = notes.load().getHashtags();
605 8 : if (!hashtags.isEmpty()) {
606 0 : changeAttribute.hashtags = new ArrayList<>(hashtags.size());
607 0 : changeAttribute.hashtags.addAll(hashtags);
608 : }
609 8 : }
610 : }
|