| // Copyright (C) 2016 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.events; |
| |
| import com.google.common.base.Supplier; |
| import com.google.common.base.Suppliers; |
| import com.google.common.collect.Sets; |
| import com.google.gerrit.common.EventDispatcher; |
| import com.google.gerrit.common.data.LabelType; |
| import com.google.gerrit.common.data.LabelTypes; |
| import com.google.gerrit.extensions.common.AccountInfo; |
| import com.google.gerrit.extensions.common.ApprovalInfo; |
| import com.google.gerrit.extensions.common.ChangeInfo; |
| import com.google.gerrit.extensions.common.RevisionInfo; |
| import com.google.gerrit.extensions.events.AssigneeChangedListener; |
| import com.google.gerrit.extensions.events.ChangeAbandonedListener; |
| import com.google.gerrit.extensions.events.ChangeMergedListener; |
| import com.google.gerrit.extensions.events.ChangeRestoredListener; |
| import com.google.gerrit.extensions.events.CommentAddedListener; |
| import com.google.gerrit.extensions.events.DraftPublishedListener; |
| import com.google.gerrit.extensions.events.GitReferenceUpdatedListener; |
| import com.google.gerrit.extensions.events.HashtagsEditedListener; |
| import com.google.gerrit.extensions.events.NewProjectCreatedListener; |
| import com.google.gerrit.extensions.events.ReviewerAddedListener; |
| import com.google.gerrit.extensions.events.ReviewerDeletedListener; |
| import com.google.gerrit.extensions.events.RevisionCreatedListener; |
| import com.google.gerrit.extensions.events.TopicEditedListener; |
| import com.google.gerrit.extensions.events.VoteDeletedListener; |
| import com.google.gerrit.extensions.registration.DynamicItem; |
| import com.google.gerrit.extensions.registration.DynamicSet; |
| import com.google.gerrit.reviewdb.client.Account; |
| import com.google.gerrit.reviewdb.client.Branch; |
| import com.google.gerrit.reviewdb.client.Change; |
| import com.google.gerrit.reviewdb.client.PatchSet; |
| import com.google.gerrit.reviewdb.server.ReviewDb; |
| import com.google.gerrit.server.PatchSetUtil; |
| import com.google.gerrit.server.data.AccountAttribute; |
| import com.google.gerrit.server.data.ApprovalAttribute; |
| import com.google.gerrit.server.data.ChangeAttribute; |
| import com.google.gerrit.server.data.PatchSetAttribute; |
| import com.google.gerrit.server.data.RefUpdateAttribute; |
| import com.google.gerrit.server.git.GitRepositoryManager; |
| import com.google.gerrit.server.notedb.ChangeNotes; |
| import com.google.gerrit.server.project.NoSuchChangeException; |
| import com.google.gerrit.server.project.ProjectCache; |
| import com.google.gwtorm.server.OrmException; |
| import com.google.inject.AbstractModule; |
| import com.google.inject.Inject; |
| import com.google.inject.Provider; |
| import com.google.inject.Singleton; |
| import java.io.IOException; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.revwalk.RevWalk; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| @Singleton |
| public class StreamEventsApiListener |
| implements AssigneeChangedListener, |
| ChangeAbandonedListener, |
| ChangeMergedListener, |
| ChangeRestoredListener, |
| CommentAddedListener, |
| DraftPublishedListener, |
| GitReferenceUpdatedListener, |
| HashtagsEditedListener, |
| NewProjectCreatedListener, |
| ReviewerAddedListener, |
| ReviewerDeletedListener, |
| RevisionCreatedListener, |
| TopicEditedListener, |
| VoteDeletedListener { |
| private static final Logger log = LoggerFactory.getLogger(StreamEventsApiListener.class); |
| |
| public static class Module extends AbstractModule { |
| @Override |
| protected void configure() { |
| DynamicSet.bind(binder(), AssigneeChangedListener.class).to(StreamEventsApiListener.class); |
| DynamicSet.bind(binder(), ChangeAbandonedListener.class).to(StreamEventsApiListener.class); |
| DynamicSet.bind(binder(), ChangeMergedListener.class).to(StreamEventsApiListener.class); |
| DynamicSet.bind(binder(), ChangeRestoredListener.class).to(StreamEventsApiListener.class); |
| DynamicSet.bind(binder(), CommentAddedListener.class).to(StreamEventsApiListener.class); |
| DynamicSet.bind(binder(), DraftPublishedListener.class).to(StreamEventsApiListener.class); |
| DynamicSet.bind(binder(), GitReferenceUpdatedListener.class) |
| .to(StreamEventsApiListener.class); |
| DynamicSet.bind(binder(), HashtagsEditedListener.class).to(StreamEventsApiListener.class); |
| DynamicSet.bind(binder(), NewProjectCreatedListener.class).to(StreamEventsApiListener.class); |
| DynamicSet.bind(binder(), ReviewerAddedListener.class).to(StreamEventsApiListener.class); |
| DynamicSet.bind(binder(), ReviewerDeletedListener.class).to(StreamEventsApiListener.class); |
| DynamicSet.bind(binder(), RevisionCreatedListener.class).to(StreamEventsApiListener.class); |
| DynamicSet.bind(binder(), TopicEditedListener.class).to(StreamEventsApiListener.class); |
| DynamicSet.bind(binder(), VoteDeletedListener.class).to(StreamEventsApiListener.class); |
| } |
| } |
| |
| private final DynamicItem<EventDispatcher> dispatcher; |
| private final Provider<ReviewDb> db; |
| private final EventFactory eventFactory; |
| private final ProjectCache projectCache; |
| private final GitRepositoryManager repoManager; |
| private final PatchSetUtil psUtil; |
| private final ChangeNotes.Factory changeNotesFactory; |
| |
| @Inject |
| StreamEventsApiListener( |
| DynamicItem<EventDispatcher> dispatcher, |
| Provider<ReviewDb> db, |
| EventFactory eventFactory, |
| ProjectCache projectCache, |
| GitRepositoryManager repoManager, |
| PatchSetUtil psUtil, |
| ChangeNotes.Factory changeNotesFactory) { |
| this.dispatcher = dispatcher; |
| this.db = db; |
| this.eventFactory = eventFactory; |
| this.projectCache = projectCache; |
| this.repoManager = repoManager; |
| this.psUtil = psUtil; |
| this.changeNotesFactory = changeNotesFactory; |
| } |
| |
| private ChangeNotes getNotes(ChangeInfo info) throws OrmException { |
| try { |
| return changeNotesFactory.createChecked(new Change.Id(info._number)); |
| } catch (NoSuchChangeException e) { |
| throw new OrmException(e); |
| } |
| } |
| |
| private Change getChange(ChangeInfo info) throws OrmException { |
| return getNotes(info).getChange(); |
| } |
| |
| private PatchSet getPatchSet(ChangeNotes notes, RevisionInfo info) throws OrmException { |
| return psUtil.get(db.get(), notes, PatchSet.Id.fromRef(info.ref)); |
| } |
| |
| private Supplier<ChangeAttribute> changeAttributeSupplier(final Change change) { |
| return Suppliers.memoize( |
| new Supplier<ChangeAttribute>() { |
| @Override |
| public ChangeAttribute get() { |
| return eventFactory.asChangeAttribute(change); |
| } |
| }); |
| } |
| |
| private Supplier<AccountAttribute> accountAttributeSupplier(final AccountInfo account) { |
| return Suppliers.memoize( |
| new Supplier<AccountAttribute>() { |
| @Override |
| public AccountAttribute get() { |
| return account != null |
| ? eventFactory.asAccountAttribute(new Account.Id(account._accountId)) |
| : null; |
| } |
| }); |
| } |
| |
| private Supplier<PatchSetAttribute> patchSetAttributeSupplier( |
| final Change change, final PatchSet patchSet) { |
| return Suppliers.memoize( |
| new Supplier<PatchSetAttribute>() { |
| @Override |
| public PatchSetAttribute get() { |
| try (Repository repo = repoManager.openRepository(change.getProject()); |
| RevWalk revWalk = new RevWalk(repo)) { |
| return eventFactory.asPatchSetAttribute(revWalk, change, patchSet); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| }); |
| } |
| |
| private static Map<String, Short> convertApprovalsMap(Map<String, ApprovalInfo> approvals) { |
| Map<String, Short> result = new HashMap<>(); |
| for (Entry<String, ApprovalInfo> e : approvals.entrySet()) { |
| Short value = e.getValue().value == null ? null : e.getValue().value.shortValue(); |
| result.put(e.getKey(), value); |
| } |
| return result; |
| } |
| |
| private ApprovalAttribute getApprovalAttribute( |
| LabelTypes labelTypes, Entry<String, Short> approval, Map<String, Short> oldApprovals) { |
| ApprovalAttribute a = new ApprovalAttribute(); |
| a.type = approval.getKey(); |
| |
| if (oldApprovals != null && !oldApprovals.isEmpty()) { |
| if (oldApprovals.get(approval.getKey()) != null) { |
| a.oldValue = Short.toString(oldApprovals.get(approval.getKey())); |
| } |
| } |
| LabelType lt = labelTypes.byLabel(approval.getKey()); |
| if (lt != null) { |
| a.description = lt.getName(); |
| } |
| if (approval.getValue() != null) { |
| a.value = Short.toString(approval.getValue()); |
| } |
| return a; |
| } |
| |
| private Supplier<ApprovalAttribute[]> approvalsAttributeSupplier( |
| final Change change, |
| Map<String, ApprovalInfo> newApprovals, |
| final Map<String, ApprovalInfo> oldApprovals) { |
| final Map<String, Short> approvals = convertApprovalsMap(newApprovals); |
| return Suppliers.memoize( |
| new Supplier<ApprovalAttribute[]>() { |
| @Override |
| public ApprovalAttribute[] get() { |
| LabelTypes labelTypes = projectCache.get(change.getProject()).getLabelTypes(); |
| if (approvals.size() > 0) { |
| ApprovalAttribute[] r = new ApprovalAttribute[approvals.size()]; |
| int i = 0; |
| for (Map.Entry<String, Short> approval : approvals.entrySet()) { |
| r[i++] = |
| getApprovalAttribute(labelTypes, approval, convertApprovalsMap(oldApprovals)); |
| } |
| return r; |
| } |
| return null; |
| } |
| }); |
| } |
| |
| String[] hashtagArray(Collection<String> hashtags) { |
| if (hashtags != null && hashtags.size() > 0) { |
| return Sets.newHashSet(hashtags).toArray(new String[hashtags.size()]); |
| } |
| return null; |
| } |
| |
| @Override |
| public void onAssigneeChanged(AssigneeChangedListener.Event ev) { |
| try { |
| Change change = getChange(ev.getChange()); |
| AssigneeChangedEvent event = new AssigneeChangedEvent(change); |
| |
| event.change = changeAttributeSupplier(change); |
| event.changer = accountAttributeSupplier(ev.getWho()); |
| event.oldAssignee = accountAttributeSupplier(ev.getOldAssignee()); |
| |
| dispatcher.get().postEvent(change, event); |
| } catch (OrmException e) { |
| log.error("Failed to dispatch event", e); |
| } |
| } |
| |
| @Override |
| public void onTopicEdited(TopicEditedListener.Event ev) { |
| try { |
| Change change = getChange(ev.getChange()); |
| TopicChangedEvent event = new TopicChangedEvent(change); |
| |
| event.change = changeAttributeSupplier(change); |
| event.changer = accountAttributeSupplier(ev.getWho()); |
| event.oldTopic = ev.getOldTopic(); |
| |
| dispatcher.get().postEvent(change, event); |
| } catch (OrmException e) { |
| log.error("Failed to dispatch event", e); |
| } |
| } |
| |
| @Override |
| public void onRevisionCreated(RevisionCreatedListener.Event ev) { |
| try { |
| ChangeNotes notes = getNotes(ev.getChange()); |
| Change change = notes.getChange(); |
| PatchSet patchSet = getPatchSet(notes, ev.getRevision()); |
| PatchSetCreatedEvent event = new PatchSetCreatedEvent(change); |
| |
| event.change = changeAttributeSupplier(change); |
| event.patchSet = patchSetAttributeSupplier(change, patchSet); |
| event.uploader = accountAttributeSupplier(ev.getWho()); |
| |
| dispatcher.get().postEvent(change, event); |
| } catch (OrmException e) { |
| log.error("Failed to dispatch event", e); |
| } |
| } |
| |
| @Override |
| public void onReviewerDeleted(final ReviewerDeletedListener.Event ev) { |
| try { |
| ChangeNotes notes = getNotes(ev.getChange()); |
| Change change = notes.getChange(); |
| ReviewerDeletedEvent event = new ReviewerDeletedEvent(change); |
| event.change = changeAttributeSupplier(change); |
| event.patchSet = patchSetAttributeSupplier(change, psUtil.current(db.get(), notes)); |
| event.reviewer = accountAttributeSupplier(ev.getReviewer()); |
| event.remover = accountAttributeSupplier(ev.getWho()); |
| event.comment = ev.getComment(); |
| event.approvals = |
| approvalsAttributeSupplier(change, ev.getNewApprovals(), ev.getOldApprovals()); |
| |
| dispatcher.get().postEvent(change, event); |
| } catch (OrmException e) { |
| log.error("Failed to dispatch event", e); |
| } |
| } |
| |
| @Override |
| public void onReviewersAdded(ReviewerAddedListener.Event ev) { |
| try { |
| ChangeNotes notes = getNotes(ev.getChange()); |
| Change change = notes.getChange(); |
| ReviewerAddedEvent event = new ReviewerAddedEvent(change); |
| |
| event.change = changeAttributeSupplier(change); |
| event.patchSet = patchSetAttributeSupplier(change, psUtil.current(db.get(), notes)); |
| for (AccountInfo reviewer : ev.getReviewers()) { |
| event.reviewer = accountAttributeSupplier(reviewer); |
| dispatcher.get().postEvent(change, event); |
| } |
| } catch (OrmException e) { |
| log.error("Failed to dispatch event", e); |
| } |
| } |
| |
| @Override |
| public void onNewProjectCreated(NewProjectCreatedListener.Event ev) { |
| ProjectCreatedEvent event = new ProjectCreatedEvent(); |
| event.projectName = ev.getProjectName(); |
| event.headName = ev.getHeadName(); |
| |
| dispatcher.get().postEvent(event.getProjectNameKey(), event); |
| } |
| |
| @Override |
| public void onHashtagsEdited(HashtagsEditedListener.Event ev) { |
| try { |
| Change change = getChange(ev.getChange()); |
| HashtagsChangedEvent event = new HashtagsChangedEvent(change); |
| |
| event.change = changeAttributeSupplier(change); |
| event.editor = accountAttributeSupplier(ev.getWho()); |
| event.hashtags = hashtagArray(ev.getHashtags()); |
| event.added = hashtagArray(ev.getAddedHashtags()); |
| event.removed = hashtagArray(ev.getRemovedHashtags()); |
| |
| dispatcher.get().postEvent(change, event); |
| } catch (OrmException e) { |
| log.error("Failed to dispatch event", e); |
| } |
| } |
| |
| @Override |
| public void onGitReferenceUpdated(final GitReferenceUpdatedListener.Event ev) { |
| RefUpdatedEvent event = new RefUpdatedEvent(); |
| if (ev.getUpdater() != null) { |
| event.submitter = accountAttributeSupplier(ev.getUpdater()); |
| } |
| final Branch.NameKey refName = new Branch.NameKey(ev.getProjectName(), ev.getRefName()); |
| event.refUpdate = |
| Suppliers.memoize( |
| new Supplier<RefUpdateAttribute>() { |
| @Override |
| public RefUpdateAttribute get() { |
| return eventFactory.asRefUpdateAttribute( |
| ObjectId.fromString(ev.getOldObjectId()), |
| ObjectId.fromString(ev.getNewObjectId()), |
| refName); |
| } |
| }); |
| dispatcher.get().postEvent(refName, event); |
| } |
| |
| @Override |
| public void onDraftPublished(DraftPublishedListener.Event ev) { |
| try { |
| ChangeNotes notes = getNotes(ev.getChange()); |
| Change change = notes.getChange(); |
| PatchSet ps = getPatchSet(notes, ev.getRevision()); |
| DraftPublishedEvent event = new DraftPublishedEvent(change); |
| |
| event.change = changeAttributeSupplier(change); |
| event.patchSet = patchSetAttributeSupplier(change, ps); |
| event.uploader = accountAttributeSupplier(ev.getWho()); |
| |
| dispatcher.get().postEvent(change, event); |
| } catch (OrmException e) { |
| log.error("Failed to dispatch event", e); |
| } |
| } |
| |
| @Override |
| public void onCommentAdded(CommentAddedListener.Event ev) { |
| try { |
| ChangeNotes notes = getNotes(ev.getChange()); |
| Change change = notes.getChange(); |
| PatchSet ps = getPatchSet(notes, ev.getRevision()); |
| CommentAddedEvent event = new CommentAddedEvent(change); |
| |
| event.change = changeAttributeSupplier(change); |
| event.author = accountAttributeSupplier(ev.getWho()); |
| event.patchSet = patchSetAttributeSupplier(change, ps); |
| event.comment = ev.getComment(); |
| event.approvals = approvalsAttributeSupplier(change, ev.getApprovals(), ev.getOldApprovals()); |
| |
| dispatcher.get().postEvent(change, event); |
| } catch (OrmException e) { |
| log.error("Failed to dispatch event", e); |
| } |
| } |
| |
| @Override |
| public void onChangeRestored(ChangeRestoredListener.Event ev) { |
| try { |
| ChangeNotes notes = getNotes(ev.getChange()); |
| Change change = notes.getChange(); |
| ChangeRestoredEvent event = new ChangeRestoredEvent(change); |
| |
| event.change = changeAttributeSupplier(change); |
| event.restorer = accountAttributeSupplier(ev.getWho()); |
| event.patchSet = patchSetAttributeSupplier(change, psUtil.current(db.get(), notes)); |
| event.reason = ev.getReason(); |
| |
| dispatcher.get().postEvent(change, event); |
| } catch (OrmException e) { |
| log.error("Failed to dispatch event", e); |
| } |
| } |
| |
| @Override |
| public void onChangeMerged(ChangeMergedListener.Event ev) { |
| try { |
| ChangeNotes notes = getNotes(ev.getChange()); |
| Change change = notes.getChange(); |
| ChangeMergedEvent event = new ChangeMergedEvent(change); |
| |
| event.change = changeAttributeSupplier(change); |
| event.submitter = accountAttributeSupplier(ev.getWho()); |
| event.patchSet = patchSetAttributeSupplier(change, psUtil.current(db.get(), notes)); |
| event.newRev = ev.getNewRevisionId(); |
| |
| dispatcher.get().postEvent(change, event); |
| } catch (OrmException e) { |
| log.error("Failed to dispatch event", e); |
| } |
| } |
| |
| @Override |
| public void onChangeAbandoned(ChangeAbandonedListener.Event ev) { |
| try { |
| ChangeNotes notes = getNotes(ev.getChange()); |
| Change change = notes.getChange(); |
| ChangeAbandonedEvent event = new ChangeAbandonedEvent(change); |
| |
| event.change = changeAttributeSupplier(change); |
| event.abandoner = accountAttributeSupplier(ev.getWho()); |
| event.patchSet = patchSetAttributeSupplier(change, psUtil.current(db.get(), notes)); |
| event.reason = ev.getReason(); |
| |
| dispatcher.get().postEvent(change, event); |
| } catch (OrmException e) { |
| log.error("Failed to dispatch event", e); |
| } |
| } |
| |
| @Override |
| public void onVoteDeleted(VoteDeletedListener.Event ev) { |
| try { |
| ChangeNotes notes = getNotes(ev.getChange()); |
| Change change = notes.getChange(); |
| VoteDeletedEvent event = new VoteDeletedEvent(change); |
| |
| event.change = changeAttributeSupplier(change); |
| event.patchSet = patchSetAttributeSupplier(change, psUtil.current(db.get(), notes)); |
| event.comment = ev.getMessage(); |
| event.reviewer = accountAttributeSupplier(ev.getReviewer()); |
| event.remover = accountAttributeSupplier(ev.getWho()); |
| event.approvals = approvalsAttributeSupplier(change, ev.getApprovals(), ev.getOldApprovals()); |
| |
| dispatcher.get().postEvent(change, event); |
| } catch (OrmException e) { |
| log.error("Failed to dispatch event", e); |
| } |
| } |
| } |