Line data Source code
1 : // Copyright (C) 2016 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.notedb;
16 :
17 : import static com.google.common.base.Preconditions.checkArgument;
18 : import static com.google.common.base.Preconditions.checkState;
19 : import static com.google.common.collect.ImmutableList.toImmutableList;
20 : import static com.google.common.collect.ImmutableListMultimap.toImmutableListMultimap;
21 : import static java.util.Objects.requireNonNull;
22 :
23 : import com.google.auto.value.AutoValue;
24 : import com.google.common.annotations.VisibleForTesting;
25 : import com.google.common.base.Converter;
26 : import com.google.common.base.Enums;
27 : import com.google.common.base.Strings;
28 : import com.google.common.collect.ImmutableList;
29 : import com.google.common.collect.ImmutableListMultimap;
30 : import com.google.common.collect.ImmutableSet;
31 : import com.google.common.collect.ImmutableTable;
32 : import com.google.common.collect.ListMultimap;
33 : import com.google.common.collect.Maps;
34 : import com.google.common.collect.Table;
35 : import com.google.gerrit.common.Nullable;
36 : import com.google.gerrit.entities.Account;
37 : import com.google.gerrit.entities.Address;
38 : import com.google.gerrit.entities.AttentionSetUpdate;
39 : import com.google.gerrit.entities.BranchNameKey;
40 : import com.google.gerrit.entities.Change;
41 : import com.google.gerrit.entities.ChangeMessage;
42 : import com.google.gerrit.entities.HumanComment;
43 : import com.google.gerrit.entities.PatchSet;
44 : import com.google.gerrit.entities.PatchSetApproval;
45 : import com.google.gerrit.entities.Project;
46 : import com.google.gerrit.entities.SubmitRecord;
47 : import com.google.gerrit.entities.SubmitRequirementResult;
48 : import com.google.gerrit.entities.converter.ChangeMessageProtoConverter;
49 : import com.google.gerrit.entities.converter.PatchSetApprovalProtoConverter;
50 : import com.google.gerrit.entities.converter.PatchSetProtoConverter;
51 : import com.google.gerrit.json.OutputFormat;
52 : import com.google.gerrit.proto.Protos;
53 : import com.google.gerrit.server.AssigneeStatusUpdate;
54 : import com.google.gerrit.server.ReviewerByEmailSet;
55 : import com.google.gerrit.server.ReviewerSet;
56 : import com.google.gerrit.server.ReviewerStatusUpdate;
57 : import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto;
58 : import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.AssigneeStatusUpdateProto;
59 : import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.AttentionSetUpdateProto;
60 : import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ChangeColumnsProto;
61 : import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerByEmailSetEntryProto;
62 : import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerSetEntryProto;
63 : import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerStatusUpdateProto;
64 : import com.google.gerrit.server.cache.serialize.CacheSerializer;
65 : import com.google.gerrit.server.cache.serialize.ObjectIdConverter;
66 : import com.google.gerrit.server.index.change.ChangeField.StoredSubmitRecord;
67 : import com.google.gson.Gson;
68 : import java.time.Instant;
69 : import java.util.List;
70 : import java.util.Map;
71 : import java.util.Optional;
72 : import java.util.Set;
73 : import org.eclipse.jgit.lib.ObjectId;
74 :
75 : /**
76 : * Immutable state associated with a change meta ref at a given commit.
77 : *
78 : * <p>One instance is the output of a single {@link ChangeNotesParser}, and contains types required
79 : * to support public methods on {@link ChangeNotes}. It is intended to be cached in-process.
80 : *
81 : * <p>When new fields are added to the {@link ChangeNotesState}, {@link
82 : * ChangeNotesCache.Weigher#weigh} should be updated.
83 : *
84 : * <p>Note that {@link ChangeNotes} contains more than just a single {@code ChangeNoteState}, such
85 : * as per-draft information, so that class is not cached directly.
86 : */
87 : // TODO(paiking): This class should be refactored to get rid of potentially duplicate or unneeded
88 : // variables, such as allAttentionSetUpdates, reviewerUpdates, and others.
89 :
90 : @AutoValue
91 103 : public abstract class ChangeNotesState {
92 :
93 : static ChangeNotesState empty(Change change) {
94 103 : return Builder.empty(change.getId()).build();
95 : }
96 :
97 : private static Builder builder() {
98 103 : return new AutoValue_ChangeNotesState.Builder();
99 : }
100 :
101 : static ChangeNotesState create(
102 : ObjectId metaId,
103 : Change.Id changeId,
104 : Change.Key changeKey,
105 : Instant createdOn,
106 : Instant lastUpdatedOn,
107 : Account.Id owner,
108 : String serverId,
109 : String branch,
110 : @Nullable PatchSet.Id currentPatchSetId,
111 : String subject,
112 : @Nullable String topic,
113 : @Nullable String originalSubject,
114 : @Nullable String submissionId,
115 : @Nullable Change.Status status,
116 : Set<String> hashtags,
117 : Map<PatchSet.Id, PatchSet> patchSets,
118 : ListMultimap<PatchSet.Id, PatchSetApproval> approvals,
119 : ReviewerSet reviewers,
120 : ReviewerByEmailSet reviewersByEmail,
121 : ReviewerSet pendingReviewers,
122 : ReviewerByEmailSet pendingReviewersByEmail,
123 : List<Account.Id> allPastReviewers,
124 : List<ReviewerStatusUpdate> reviewerUpdates,
125 : Set<AttentionSetUpdate> attentionSetUpdates,
126 : List<AttentionSetUpdate> allAttentionSetUpdates,
127 : List<AssigneeStatusUpdate> assigneeUpdates,
128 : List<SubmitRecord> submitRecords,
129 : List<ChangeMessage> changeMessages,
130 : ListMultimap<ObjectId, HumanComment> publishedComments,
131 : List<SubmitRequirementResult> submitRequirementResults,
132 : boolean isPrivate,
133 : boolean workInProgress,
134 : boolean reviewStarted,
135 : @Nullable Change.Id revertOf,
136 : @Nullable PatchSet.Id cherryPickOf,
137 : int updateCount,
138 : @Nullable Instant mergedOn) {
139 103 : requireNonNull(
140 : metaId,
141 : () ->
142 0 : String.format(
143 : "metaId is required when passing arguments to create(...)."
144 : + " To create an empty %s without"
145 : + " NoteDb data, use empty(...) instead",
146 0 : ChangeNotesState.class.getSimpleName()));
147 103 : return builder()
148 103 : .metaId(metaId)
149 103 : .changeId(changeId)
150 103 : .columns(
151 103 : ChangeColumns.builder()
152 103 : .changeKey(changeKey)
153 103 : .createdOn(createdOn)
154 103 : .lastUpdatedOn(lastUpdatedOn)
155 103 : .owner(owner)
156 103 : .branch(branch)
157 103 : .status(status)
158 103 : .currentPatchSetId(currentPatchSetId)
159 103 : .subject(subject)
160 103 : .topic(topic)
161 103 : .originalSubject(originalSubject)
162 103 : .submissionId(submissionId)
163 103 : .isPrivate(isPrivate)
164 103 : .workInProgress(workInProgress)
165 103 : .reviewStarted(reviewStarted)
166 103 : .revertOf(revertOf)
167 103 : .cherryPickOf(cherryPickOf)
168 103 : .build())
169 103 : .hashtags(hashtags)
170 103 : .serverId(serverId)
171 103 : .patchSets(patchSets.entrySet())
172 103 : .approvals(approvals.entries())
173 103 : .reviewers(reviewers)
174 103 : .reviewersByEmail(reviewersByEmail)
175 103 : .pendingReviewers(pendingReviewers)
176 103 : .pendingReviewersByEmail(pendingReviewersByEmail)
177 103 : .allPastReviewers(allPastReviewers)
178 103 : .reviewerUpdates(reviewerUpdates)
179 103 : .attentionSet(attentionSetUpdates)
180 103 : .allAttentionSetUpdates(allAttentionSetUpdates)
181 103 : .assigneeUpdates(assigneeUpdates)
182 103 : .submitRecords(submitRecords)
183 103 : .changeMessages(changeMessages)
184 103 : .publishedComments(publishedComments)
185 103 : .submitRequirementsResult(submitRequirementResults)
186 103 : .updateCount(updateCount)
187 103 : .mergedOn(mergedOn)
188 103 : .build();
189 : }
190 :
191 : /**
192 : * Subset of Change columns that can be represented in NoteDb.
193 : *
194 : * <p>Fields should match the column names in {@link Change}, and are in listed column order.
195 : */
196 : @AutoValue
197 103 : abstract static class ChangeColumns {
198 :
199 : static Builder builder() {
200 103 : return new AutoValue_ChangeNotesState_ChangeColumns.Builder();
201 : }
202 :
203 : abstract Change.Key changeKey();
204 :
205 : abstract Instant createdOn();
206 :
207 : abstract Instant lastUpdatedOn();
208 :
209 : abstract Account.Id owner();
210 :
211 : // Project not included, as it's not stored anywhere in the meta ref.
212 : abstract String branch();
213 :
214 : // TODO(dborowitz): Use a sensible default other than null
215 : @Nullable
216 : abstract Change.Status status();
217 :
218 : @Nullable
219 : abstract PatchSet.Id currentPatchSetId();
220 :
221 : abstract String subject();
222 :
223 : @Nullable
224 : abstract String topic();
225 :
226 : @Nullable
227 : abstract String originalSubject();
228 :
229 : @Nullable
230 : abstract String submissionId();
231 :
232 : abstract boolean isPrivate();
233 :
234 : abstract boolean workInProgress();
235 :
236 : abstract boolean reviewStarted();
237 :
238 : @Nullable
239 : abstract Change.Id revertOf();
240 :
241 : @Nullable
242 : abstract PatchSet.Id cherryPickOf();
243 :
244 : abstract Builder toBuilder();
245 :
246 : @AutoValue.Builder
247 103 : abstract static class Builder {
248 :
249 : abstract Builder changeKey(Change.Key changeKey);
250 :
251 : abstract Builder createdOn(Instant createdOn);
252 :
253 : abstract Builder lastUpdatedOn(Instant lastUpdatedOn);
254 :
255 : abstract Builder owner(Account.Id owner);
256 :
257 : abstract Builder branch(String branch);
258 :
259 : abstract Builder currentPatchSetId(@Nullable PatchSet.Id currentPatchSetId);
260 :
261 : abstract Builder subject(String subject);
262 :
263 : abstract Builder topic(@Nullable String topic);
264 :
265 : abstract Builder originalSubject(@Nullable String originalSubject);
266 :
267 : abstract Builder submissionId(@Nullable String submissionId);
268 :
269 : abstract Builder status(@Nullable Change.Status status);
270 :
271 : abstract Builder isPrivate(boolean isPrivate);
272 :
273 : abstract Builder workInProgress(boolean workInProgress);
274 :
275 : abstract Builder reviewStarted(boolean reviewStarted);
276 :
277 : abstract Builder revertOf(@Nullable Change.Id revertOf);
278 :
279 : abstract Builder cherryPickOf(@Nullable PatchSet.Id cherryPickOf);
280 :
281 : abstract ChangeColumns build();
282 : }
283 : }
284 :
285 : // Only null if NoteDb is disabled.
286 : @Nullable
287 : abstract ObjectId metaId();
288 :
289 : abstract Change.Id changeId();
290 :
291 : // Only null if NoteDb is disabled.
292 : @Nullable
293 : abstract ChangeColumns columns();
294 :
295 : // Other related to this Change.
296 : abstract ImmutableSet<String> hashtags();
297 :
298 : @Nullable
299 : abstract String serverId();
300 :
301 : abstract ImmutableList<Map.Entry<PatchSet.Id, PatchSet>> patchSets();
302 :
303 : abstract ImmutableList<Map.Entry<PatchSet.Id, PatchSetApproval>> approvals();
304 :
305 : abstract ReviewerSet reviewers();
306 :
307 : abstract ReviewerByEmailSet reviewersByEmail();
308 :
309 : abstract ReviewerSet pendingReviewers();
310 :
311 : abstract ReviewerByEmailSet pendingReviewersByEmail();
312 :
313 : abstract ImmutableList<Account.Id> allPastReviewers();
314 :
315 : abstract ImmutableList<ReviewerStatusUpdate> reviewerUpdates();
316 :
317 : /** Returns the most recent update (i.e. current status) per user. */
318 : abstract ImmutableSet<AttentionSetUpdate> attentionSet();
319 :
320 : /** Returns all attention set updates. */
321 : abstract ImmutableList<AttentionSetUpdate> allAttentionSetUpdates();
322 :
323 : abstract ImmutableList<AssigneeStatusUpdate> assigneeUpdates();
324 :
325 : abstract ImmutableList<SubmitRecord> submitRecords();
326 :
327 : abstract ImmutableList<ChangeMessage> changeMessages();
328 :
329 : abstract ImmutableListMultimap<ObjectId, HumanComment> publishedComments();
330 :
331 : abstract ImmutableList<SubmitRequirementResult> submitRequirementsResult();
332 :
333 : abstract int updateCount();
334 :
335 : @Nullable
336 : abstract Instant mergedOn();
337 :
338 : Change newChange(Project.NameKey project) {
339 0 : ChangeColumns c = requireNonNull(columns(), "columns are required");
340 0 : Change change =
341 : new Change(
342 0 : c.changeKey(),
343 0 : changeId(),
344 0 : c.owner(),
345 0 : BranchNameKey.create(project, c.branch()),
346 0 : c.createdOn());
347 0 : copyNonConstructorColumnsTo(change);
348 0 : return change;
349 : }
350 :
351 : void copyColumnsTo(Change change) {
352 103 : ChangeColumns c = columns();
353 103 : checkState(
354 103 : c != null && metaId() != null,
355 : "missing columns or metaId in ChangeNotesState; is NoteDb enabled? %s",
356 : this);
357 103 : change.setKey(c.changeKey());
358 103 : change.setOwner(c.owner());
359 103 : change.setDest(BranchNameKey.create(change.getProject(), c.branch()));
360 103 : change.setCreatedOn(c.createdOn());
361 103 : copyNonConstructorColumnsTo(change);
362 103 : }
363 :
364 : private void copyNonConstructorColumnsTo(Change change) {
365 103 : ChangeColumns c = requireNonNull(columns(), "columns are required");
366 103 : if (c.status() != null) {
367 103 : change.setStatus(c.status());
368 : }
369 103 : change.setTopic(Strings.emptyToNull(c.topic()));
370 103 : change.setLastUpdatedOn(c.lastUpdatedOn());
371 103 : change.setSubmissionId(c.submissionId());
372 103 : if (!assigneeUpdates().isEmpty()) {
373 7 : change.setAssignee(assigneeUpdates().get(0).currentAssignee().orElse(null));
374 : }
375 103 : change.setPrivate(c.isPrivate());
376 103 : change.setWorkInProgress(c.workInProgress());
377 103 : change.setReviewStarted(c.reviewStarted());
378 103 : change.setRevertOf(c.revertOf());
379 103 : change.setCherryPickOf(c.cherryPickOf());
380 :
381 103 : if (!patchSets().isEmpty()) {
382 103 : change.setCurrentPatchSet(c.currentPatchSetId(), c.subject(), c.originalSubject());
383 : } else {
384 : // TODO(dborowitz): This should be an error, but for now it's required for
385 : // some tests to pass.
386 0 : change.clearCurrentPatchSet();
387 : }
388 103 : }
389 :
390 : @AutoValue.Builder
391 103 : abstract static class Builder {
392 :
393 : static Builder empty(Change.Id changeId) {
394 103 : return new AutoValue_ChangeNotesState.Builder()
395 103 : .changeId(changeId)
396 103 : .hashtags(ImmutableSet.of())
397 103 : .patchSets(ImmutableList.of())
398 103 : .approvals(ImmutableList.of())
399 103 : .reviewers(ReviewerSet.empty())
400 103 : .reviewersByEmail(ReviewerByEmailSet.empty())
401 103 : .pendingReviewers(ReviewerSet.empty())
402 103 : .pendingReviewersByEmail(ReviewerByEmailSet.empty())
403 103 : .allPastReviewers(ImmutableList.of())
404 103 : .reviewerUpdates(ImmutableList.of())
405 103 : .attentionSet(ImmutableSet.of())
406 103 : .allAttentionSetUpdates(ImmutableList.of())
407 103 : .assigneeUpdates(ImmutableList.of())
408 103 : .submitRecords(ImmutableList.of())
409 103 : .changeMessages(ImmutableList.of())
410 103 : .publishedComments(ImmutableListMultimap.of())
411 103 : .submitRequirementsResult(ImmutableList.of())
412 103 : .updateCount(0);
413 : }
414 :
415 : abstract Builder metaId(ObjectId metaId);
416 :
417 : abstract Builder changeId(Change.Id changeId);
418 :
419 : abstract Builder columns(ChangeColumns columns);
420 :
421 : abstract Builder serverId(String serverId);
422 :
423 : abstract Builder hashtags(Iterable<String> hashtags);
424 :
425 : abstract Builder patchSets(Iterable<Map.Entry<PatchSet.Id, PatchSet>> patchSets);
426 :
427 : abstract Builder approvals(Iterable<Map.Entry<PatchSet.Id, PatchSetApproval>> approvals);
428 :
429 : abstract Builder reviewers(ReviewerSet reviewers);
430 :
431 : abstract Builder reviewersByEmail(ReviewerByEmailSet reviewersByEmail);
432 :
433 : abstract Builder pendingReviewers(ReviewerSet pendingReviewers);
434 :
435 : abstract Builder pendingReviewersByEmail(ReviewerByEmailSet pendingReviewersByEmail);
436 :
437 : abstract Builder allPastReviewers(List<Account.Id> allPastReviewers);
438 :
439 : abstract Builder reviewerUpdates(List<ReviewerStatusUpdate> reviewerUpdates);
440 :
441 : abstract Builder attentionSet(Set<AttentionSetUpdate> attentionSetUpdates);
442 :
443 : abstract Builder allAttentionSetUpdates(List<AttentionSetUpdate> attentionSetUpdates);
444 :
445 : abstract Builder assigneeUpdates(List<AssigneeStatusUpdate> assigneeUpdates);
446 :
447 : abstract Builder submitRecords(List<SubmitRecord> submitRecords);
448 :
449 : abstract Builder changeMessages(List<ChangeMessage> changeMessages);
450 :
451 : abstract Builder publishedComments(ListMultimap<ObjectId, HumanComment> publishedComments);
452 :
453 : abstract Builder submitRequirementsResult(
454 : List<SubmitRequirementResult> submitRequirementsResult);
455 :
456 : abstract Builder updateCount(int updateCount);
457 :
458 : abstract Builder mergedOn(Instant mergedOn);
459 :
460 : abstract ChangeNotesState build();
461 : }
462 :
463 : /**
464 : * Convert ChangeNotesState (which is AutoValue based) to byte[] and back, using protocol buffers.
465 : */
466 152 : enum Serializer implements CacheSerializer<ChangeNotesState> {
467 152 : INSTANCE;
468 :
469 152 : @VisibleForTesting static final Gson GSON = OutputFormat.JSON_COMPACT.newGson();
470 :
471 152 : private static final Converter<String, Change.Status> STATUS_CONVERTER =
472 152 : Enums.stringConverter(Change.Status.class);
473 152 : private static final Converter<String, ReviewerStateInternal> REVIEWER_STATE_CONVERTER =
474 152 : Enums.stringConverter(ReviewerStateInternal.class);
475 :
476 : @Override
477 : public byte[] serialize(ChangeNotesState object) {
478 1 : checkArgument(object.metaId() != null, "meta ID is required in: %s", object);
479 1 : checkArgument(object.columns() != null, "ChangeColumns is required in: %s", object);
480 1 : ChangeNotesStateProto.Builder b = ChangeNotesStateProto.newBuilder();
481 :
482 1 : b.setMetaId(ObjectIdConverter.create().toByteString(object.metaId()))
483 1 : .setChangeId(object.changeId().get())
484 1 : .setColumns(toChangeColumnsProto(object.columns()));
485 :
486 1 : if (object.serverId() != null) {
487 1 : b.setServerId(object.serverId());
488 1 : b.setHasServerId(true);
489 : }
490 1 : object.hashtags().forEach(b::addHashtag);
491 1 : object
492 1 : .patchSets()
493 1 : .forEach(e -> b.addPatchSet(PatchSetProtoConverter.INSTANCE.toProto(e.getValue())));
494 1 : object
495 1 : .approvals()
496 1 : .forEach(
497 1 : e -> b.addApproval(PatchSetApprovalProtoConverter.INSTANCE.toProto(e.getValue())));
498 :
499 1 : object.reviewers().asTable().cellSet().forEach(c -> b.addReviewer(toReviewerSetEntry(c)));
500 1 : object
501 1 : .reviewersByEmail()
502 1 : .asTable()
503 1 : .cellSet()
504 1 : .forEach(c -> b.addReviewerByEmail(toReviewerByEmailSetEntry(c)));
505 1 : object
506 1 : .pendingReviewers()
507 1 : .asTable()
508 1 : .cellSet()
509 1 : .forEach(c -> b.addPendingReviewer(toReviewerSetEntry(c)));
510 1 : object
511 1 : .pendingReviewersByEmail()
512 1 : .asTable()
513 1 : .cellSet()
514 1 : .forEach(c -> b.addPendingReviewerByEmail(toReviewerByEmailSetEntry(c)));
515 :
516 1 : object.allPastReviewers().forEach(a -> b.addPastReviewer(a.get()));
517 1 : object.reviewerUpdates().forEach(u -> b.addReviewerUpdate(toReviewerStatusUpdateProto(u)));
518 1 : object.attentionSet().forEach(u -> b.addAttentionSetUpdate(toAttentionSetUpdateProto(u)));
519 1 : object
520 1 : .allAttentionSetUpdates()
521 1 : .forEach(u -> b.addAllAttentionSetUpdate(toAttentionSetUpdateProto(u)));
522 1 : object.assigneeUpdates().forEach(u -> b.addAssigneeUpdate(toAssigneeStatusUpdateProto(u)));
523 1 : object
524 1 : .submitRecords()
525 1 : .forEach(r -> b.addSubmitRecord(GSON.toJson(new StoredSubmitRecord(r))));
526 1 : object
527 1 : .changeMessages()
528 1 : .forEach(m -> b.addChangeMessage(ChangeMessageProtoConverter.INSTANCE.toProto(m)));
529 1 : object.publishedComments().values().forEach(c -> b.addPublishedComment(GSON.toJson(c)));
530 1 : object
531 1 : .submitRequirementsResult()
532 1 : .forEach(
533 : sr ->
534 1 : b.addSubmitRequirementResult(
535 1 : SubmitRequirementProtoConverter.INSTANCE.toProto(sr)));
536 1 : b.setUpdateCount(object.updateCount());
537 1 : if (object.mergedOn() != null) {
538 1 : b.setMergedOnMillis(object.mergedOn().toEpochMilli());
539 1 : b.setHasMergedOn(true);
540 : }
541 :
542 1 : return Protos.toByteArray(b.build());
543 : }
544 :
545 : private static ChangeColumnsProto toChangeColumnsProto(ChangeColumns cols) {
546 : ChangeColumnsProto.Builder b =
547 1 : ChangeColumnsProto.newBuilder()
548 1 : .setChangeKey(cols.changeKey().get())
549 1 : .setCreatedOnMillis(cols.createdOn().toEpochMilli())
550 1 : .setLastUpdatedOnMillis(cols.lastUpdatedOn().toEpochMilli())
551 1 : .setOwner(cols.owner().get())
552 1 : .setBranch(cols.branch());
553 1 : if (cols.currentPatchSetId() != null) {
554 1 : b.setCurrentPatchSetId(cols.currentPatchSetId().get()).setHasCurrentPatchSetId(true);
555 : }
556 1 : b.setSubject(cols.subject());
557 1 : if (cols.topic() != null) {
558 1 : b.setTopic(cols.topic()).setHasTopic(true);
559 : }
560 1 : if (cols.originalSubject() != null) {
561 1 : b.setOriginalSubject(cols.originalSubject()).setHasOriginalSubject(true);
562 : }
563 1 : if (cols.submissionId() != null) {
564 1 : b.setSubmissionId(cols.submissionId()).setHasSubmissionId(true);
565 : }
566 1 : if (cols.status() != null) {
567 1 : b.setStatus(STATUS_CONVERTER.reverse().convert(cols.status())).setHasStatus(true);
568 : }
569 1 : b.setIsPrivate(cols.isPrivate())
570 1 : .setWorkInProgress(cols.workInProgress())
571 1 : .setReviewStarted(cols.reviewStarted());
572 1 : if (cols.revertOf() != null) {
573 1 : b.setRevertOf(cols.revertOf().get()).setHasRevertOf(true);
574 : }
575 1 : if (cols.cherryPickOf() != null) {
576 0 : b.setCherryPickOf(cols.cherryPickOf().getCommaSeparatedChangeAndPatchSetId())
577 0 : .setHasCherryPickOf(true);
578 : }
579 1 : return b.build();
580 : }
581 :
582 : private static ReviewerSetEntryProto toReviewerSetEntry(
583 : Table.Cell<ReviewerStateInternal, Account.Id, Instant> c) {
584 1 : return ReviewerSetEntryProto.newBuilder()
585 1 : .setState(REVIEWER_STATE_CONVERTER.reverse().convert(c.getRowKey()))
586 1 : .setAccountId(c.getColumnKey().get())
587 1 : .setTimestampMillis(c.getValue().toEpochMilli())
588 1 : .build();
589 : }
590 :
591 : private static ReviewerByEmailSetEntryProto toReviewerByEmailSetEntry(
592 : Table.Cell<ReviewerStateInternal, Address, Instant> c) {
593 1 : return ReviewerByEmailSetEntryProto.newBuilder()
594 1 : .setState(REVIEWER_STATE_CONVERTER.reverse().convert(c.getRowKey()))
595 1 : .setAddress(c.getColumnKey().toHeaderString())
596 1 : .setTimestampMillis(c.getValue().toEpochMilli())
597 1 : .build();
598 : }
599 :
600 : private static ReviewerStatusUpdateProto toReviewerStatusUpdateProto(ReviewerStatusUpdate u) {
601 1 : return ReviewerStatusUpdateProto.newBuilder()
602 1 : .setTimestampMillis(u.date().toEpochMilli())
603 1 : .setUpdatedBy(u.updatedBy().get())
604 1 : .setReviewer(u.reviewer().get())
605 1 : .setState(REVIEWER_STATE_CONVERTER.reverse().convert(u.state()))
606 1 : .build();
607 : }
608 :
609 : private static AttentionSetUpdateProto toAttentionSetUpdateProto(
610 : AttentionSetUpdate attentionSetUpdate) {
611 1 : return AttentionSetUpdateProto.newBuilder()
612 1 : .setTimestampMillis(attentionSetUpdate.timestamp().toEpochMilli())
613 1 : .setAccount(attentionSetUpdate.account().get())
614 1 : .setOperation(attentionSetUpdate.operation().name())
615 1 : .setReason(attentionSetUpdate.reason())
616 1 : .build();
617 : }
618 :
619 : private static AssigneeStatusUpdateProto toAssigneeStatusUpdateProto(AssigneeStatusUpdate u) {
620 : AssigneeStatusUpdateProto.Builder builder =
621 1 : AssigneeStatusUpdateProto.newBuilder()
622 1 : .setTimestampMillis(u.date().toEpochMilli())
623 1 : .setUpdatedBy(u.updatedBy().get())
624 1 : .setHasCurrentAssignee(u.currentAssignee().isPresent());
625 :
626 1 : u.currentAssignee().ifPresent(assignee -> builder.setCurrentAssignee(assignee.get()));
627 1 : return builder.build();
628 : }
629 :
630 : @Override
631 : public ChangeNotesState deserialize(byte[] in) {
632 1 : ChangeNotesStateProto proto = Protos.parseUnchecked(ChangeNotesStateProto.parser(), in);
633 1 : Change.Id changeId = Change.id(proto.getChangeId());
634 :
635 : ChangeNotesState.Builder b =
636 1 : builder()
637 1 : .metaId(ObjectIdConverter.create().fromByteString(proto.getMetaId()))
638 1 : .changeId(changeId)
639 1 : .columns(toChangeColumns(changeId, proto.getColumns()))
640 1 : .serverId(proto.getHasServerId() ? proto.getServerId() : null)
641 1 : .hashtags(proto.getHashtagList())
642 1 : .patchSets(
643 1 : proto.getPatchSetList().stream()
644 1 : .map(msg -> PatchSetProtoConverter.INSTANCE.fromProto(msg))
645 1 : .map(ps -> Maps.immutableEntry(ps.id(), ps))
646 1 : .collect(toImmutableList()))
647 1 : .approvals(
648 1 : proto.getApprovalList().stream()
649 1 : .map(msg -> PatchSetApprovalProtoConverter.INSTANCE.fromProto(msg))
650 1 : .map(a -> Maps.immutableEntry(a.patchSetId(), a))
651 1 : .collect(toImmutableList()))
652 1 : .reviewers(toReviewerSet(proto.getReviewerList()))
653 1 : .reviewersByEmail(toReviewerByEmailSet(proto.getReviewerByEmailList()))
654 1 : .pendingReviewers(toReviewerSet(proto.getPendingReviewerList()))
655 1 : .pendingReviewersByEmail(toReviewerByEmailSet(proto.getPendingReviewerByEmailList()))
656 1 : .allPastReviewers(
657 1 : proto.getPastReviewerList().stream().map(Account::id).collect(toImmutableList()))
658 1 : .reviewerUpdates(toReviewerStatusUpdateList(proto.getReviewerUpdateList()))
659 1 : .attentionSet(toAttentionSetUpdates(proto.getAttentionSetUpdateList()))
660 1 : .allAttentionSetUpdates(
661 1 : toAllAttentionSetUpdates(proto.getAllAttentionSetUpdateList()))
662 1 : .assigneeUpdates(toAssigneeStatusUpdateList(proto.getAssigneeUpdateList()))
663 1 : .submitRecords(
664 1 : proto.getSubmitRecordList().stream()
665 1 : .map(r -> GSON.fromJson(r, StoredSubmitRecord.class).toSubmitRecord())
666 1 : .collect(toImmutableList()))
667 1 : .changeMessages(
668 1 : proto.getChangeMessageList().stream()
669 1 : .map(msg -> ChangeMessageProtoConverter.INSTANCE.fromProto(msg))
670 1 : .collect(toImmutableList()))
671 1 : .publishedComments(
672 1 : proto.getPublishedCommentList().stream()
673 1 : .map(r -> GSON.fromJson(r, HumanComment.class))
674 1 : .collect(toImmutableListMultimap(HumanComment::getCommitId, c -> c)))
675 1 : .submitRequirementsResult(
676 1 : proto.getSubmitRequirementResultList().stream()
677 1 : .map(sr -> SubmitRequirementProtoConverter.INSTANCE.fromProto(sr))
678 1 : .collect(toImmutableList()))
679 1 : .updateCount(proto.getUpdateCount())
680 1 : .mergedOn(
681 1 : proto.getHasMergedOn() ? Instant.ofEpochMilli(proto.getMergedOnMillis()) : null);
682 1 : return b.build();
683 : }
684 :
685 : private static ChangeColumns toChangeColumns(Change.Id changeId, ChangeColumnsProto proto) {
686 : ChangeColumns.Builder b =
687 1 : ChangeColumns.builder()
688 1 : .changeKey(Change.key(proto.getChangeKey()))
689 1 : .createdOn(Instant.ofEpochMilli(proto.getCreatedOnMillis()))
690 1 : .lastUpdatedOn(Instant.ofEpochMilli(proto.getLastUpdatedOnMillis()))
691 1 : .owner(Account.id(proto.getOwner()))
692 1 : .branch(proto.getBranch());
693 1 : if (proto.getHasCurrentPatchSetId()) {
694 1 : b.currentPatchSetId(PatchSet.id(changeId, proto.getCurrentPatchSetId()));
695 : }
696 1 : b.subject(proto.getSubject());
697 1 : if (proto.getHasTopic()) {
698 1 : b.topic(proto.getTopic());
699 : }
700 1 : if (proto.getHasOriginalSubject()) {
701 1 : b.originalSubject(proto.getOriginalSubject());
702 : }
703 1 : if (proto.getHasSubmissionId()) {
704 1 : b.submissionId(proto.getSubmissionId());
705 : }
706 1 : if (proto.getHasStatus()) {
707 1 : b.status(STATUS_CONVERTER.convert(proto.getStatus()));
708 : }
709 1 : b.isPrivate(proto.getIsPrivate())
710 1 : .workInProgress(proto.getWorkInProgress())
711 1 : .reviewStarted(proto.getReviewStarted());
712 1 : if (proto.getHasRevertOf()) {
713 1 : b.revertOf(Change.id(proto.getRevertOf()));
714 : }
715 1 : if (proto.getHasCherryPickOf()) {
716 0 : b.cherryPickOf(PatchSet.Id.parse(proto.getCherryPickOf()));
717 : }
718 1 : return b.build();
719 : }
720 :
721 : private static ReviewerSet toReviewerSet(List<ReviewerSetEntryProto> protos) {
722 : ImmutableTable.Builder<ReviewerStateInternal, Account.Id, Instant> b =
723 1 : ImmutableTable.builder();
724 1 : for (ReviewerSetEntryProto e : protos) {
725 1 : b.put(
726 1 : REVIEWER_STATE_CONVERTER.convert(e.getState()),
727 1 : Account.id(e.getAccountId()),
728 1 : Instant.ofEpochMilli(e.getTimestampMillis()));
729 1 : }
730 1 : return ReviewerSet.fromTable(b.build());
731 : }
732 :
733 : private static ReviewerByEmailSet toReviewerByEmailSet(
734 : List<ReviewerByEmailSetEntryProto> protos) {
735 1 : ImmutableTable.Builder<ReviewerStateInternal, Address, Instant> b = ImmutableTable.builder();
736 1 : for (ReviewerByEmailSetEntryProto e : protos) {
737 1 : b.put(
738 1 : REVIEWER_STATE_CONVERTER.convert(e.getState()),
739 1 : Address.parse(e.getAddress()),
740 1 : Instant.ofEpochMilli(e.getTimestampMillis()));
741 1 : }
742 1 : return ReviewerByEmailSet.fromTable(b.build());
743 : }
744 :
745 : private static ImmutableList<ReviewerStatusUpdate> toReviewerStatusUpdateList(
746 : List<ReviewerStatusUpdateProto> protos) {
747 1 : ImmutableList.Builder<ReviewerStatusUpdate> b = ImmutableList.builder();
748 1 : for (ReviewerStatusUpdateProto proto : protos) {
749 1 : b.add(
750 1 : ReviewerStatusUpdate.create(
751 1 : Instant.ofEpochMilli(proto.getTimestampMillis()),
752 1 : Account.id(proto.getUpdatedBy()),
753 1 : Account.id(proto.getReviewer()),
754 1 : REVIEWER_STATE_CONVERTER.convert(proto.getState())));
755 1 : }
756 1 : return b.build();
757 : }
758 :
759 : private static ImmutableSet<AttentionSetUpdate> toAttentionSetUpdates(
760 : List<AttentionSetUpdateProto> protos) {
761 1 : ImmutableSet.Builder<AttentionSetUpdate> b = ImmutableSet.builder();
762 1 : for (AttentionSetUpdateProto proto : protos) {
763 1 : b.add(
764 1 : AttentionSetUpdate.createFromRead(
765 1 : Instant.ofEpochMilli(proto.getTimestampMillis()),
766 1 : Account.id(proto.getAccount()),
767 1 : AttentionSetUpdate.Operation.valueOf(proto.getOperation()),
768 1 : proto.getReason()));
769 1 : }
770 1 : return b.build();
771 : }
772 :
773 : private static ImmutableList<AttentionSetUpdate> toAllAttentionSetUpdates(
774 : List<AttentionSetUpdateProto> protos) {
775 1 : ImmutableList.Builder<AttentionSetUpdate> b = ImmutableList.builder();
776 1 : for (AttentionSetUpdateProto proto : protos) {
777 1 : b.add(
778 1 : AttentionSetUpdate.createFromRead(
779 1 : Instant.ofEpochMilli(proto.getTimestampMillis()),
780 1 : Account.id(proto.getAccount()),
781 1 : AttentionSetUpdate.Operation.valueOf(proto.getOperation()),
782 1 : proto.getReason()));
783 1 : }
784 1 : return b.build();
785 : }
786 :
787 : private static ImmutableList<AssigneeStatusUpdate> toAssigneeStatusUpdateList(
788 : List<AssigneeStatusUpdateProto> protos) {
789 1 : ImmutableList.Builder<AssigneeStatusUpdate> b = ImmutableList.builder();
790 1 : for (AssigneeStatusUpdateProto proto : protos) {
791 1 : b.add(
792 1 : AssigneeStatusUpdate.create(
793 1 : Instant.ofEpochMilli(proto.getTimestampMillis()),
794 1 : Account.id(proto.getUpdatedBy()),
795 1 : proto.getHasCurrentAssignee()
796 1 : ? Optional.of(Account.id(proto.getCurrentAssignee()))
797 1 : : Optional.empty()));
798 1 : }
799 1 : return b.build();
800 : }
801 : }
802 : }
|