blob: edd192804191f3960f17f8e60bd55fb85b6444a7 [file] [log] [blame]
// 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 static com.google.gerrit.server.project.ProjectCache.illegalState;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.LabelTypes;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
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.ChangeDeletedListener;
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.GitReferenceUpdatedListener;
import com.google.gerrit.extensions.events.HashtagsEditedListener;
import com.google.gerrit.extensions.events.NewProjectCreatedListener;
import com.google.gerrit.extensions.events.PrivateStateChangedListener;
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.events.WorkInProgressStateChangedListener;
import com.google.gerrit.extensions.registration.DynamicSet;
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.git.GitRepositoryManager;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.plugincontext.PluginItemContext;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevWalk;
@Singleton
public class StreamEventsApiListener
implements AssigneeChangedListener,
ChangeAbandonedListener,
ChangeDeletedListener,
ChangeMergedListener,
ChangeRestoredListener,
WorkInProgressStateChangedListener,
PrivateStateChangedListener,
CommentAddedListener,
GitReferenceUpdatedListener,
HashtagsEditedListener,
NewProjectCreatedListener,
ReviewerAddedListener,
ReviewerDeletedListener,
RevisionCreatedListener,
TopicEditedListener,
VoteDeletedListener {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
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(), ChangeDeletedListener.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(), GitReferenceUpdatedListener.class)
.to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), HashtagsEditedListener.class).to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), NewProjectCreatedListener.class).to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), PrivateStateChangedListener.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);
DynamicSet.bind(binder(), WorkInProgressStateChangedListener.class)
.to(StreamEventsApiListener.class);
}
}
private final PluginItemContext<EventDispatcher> dispatcher;
private final EventFactory eventFactory;
private final ProjectCache projectCache;
private final GitRepositoryManager repoManager;
private final PatchSetUtil psUtil;
private final ChangeNotes.Factory changeNotesFactory;
@Inject
StreamEventsApiListener(
PluginItemContext<EventDispatcher> dispatcher,
EventFactory eventFactory,
ProjectCache projectCache,
GitRepositoryManager repoManager,
PatchSetUtil psUtil,
ChangeNotes.Factory changeNotesFactory) {
this.dispatcher = dispatcher;
this.eventFactory = eventFactory;
this.projectCache = projectCache;
this.repoManager = repoManager;
this.psUtil = psUtil;
this.changeNotesFactory = changeNotesFactory;
}
private ChangeNotes getNotes(ChangeInfo info) {
try {
return changeNotesFactory.createChecked(
Project.nameKey(info.project), Change.id(info._number));
} catch (NoSuchChangeException e) {
throw new StorageException(e);
}
}
private PatchSet getPatchSet(ChangeNotes notes, RevisionInfo info) {
return psUtil.get(notes, PatchSet.Id.fromRef(info.ref));
}
private Supplier<ChangeAttribute> changeAttributeSupplier(Change change, ChangeNotes notes) {
return Suppliers.memoize(
() -> {
try {
return eventFactory.asChangeAttribute(change, notes);
} catch (StorageException e) {
throw new RuntimeException(e);
}
});
}
private Supplier<AccountAttribute> accountAttributeSupplier(AccountInfo account) {
return Suppliers.memoize(
() ->
account != null
? eventFactory.asAccountAttribute(Account.id(account._accountId))
: null);
}
private Supplier<PatchSetAttribute> patchSetAttributeSupplier(
final Change change, PatchSet patchSet) {
return Suppliers.memoize(
() -> {
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 (Map.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, Map.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()));
}
}
labelTypes.byLabel(approval.getKey()).ifPresent(lt -> 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(
() -> {
Project.NameKey nameKey = change.getProject();
LabelTypes labelTypes =
projectCache.get(nameKey).orElseThrow(illegalState(nameKey)).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.isEmpty()) {
return Sets.newHashSet(hashtags).toArray(new String[hashtags.size()]);
}
return null;
}
@Override
public void onAssigneeChanged(AssigneeChangedListener.Event ev) {
try {
ChangeNotes notes = getNotes(ev.getChange());
Change change = notes.getChange();
AssigneeChangedEvent event = new AssigneeChangedEvent(change);
event.change = changeAttributeSupplier(change, notes);
event.changer = accountAttributeSupplier(ev.getWho());
event.oldAssignee = accountAttributeSupplier(ev.getOldAssignee());
dispatcher.run(d -> d.postEvent(change, event));
} catch (StorageException e) {
logger.atSevere().withCause(e).log("Failed to dispatch event");
}
}
@Override
public void onTopicEdited(TopicEditedListener.Event ev) {
try {
ChangeNotes notes = getNotes(ev.getChange());
Change change = notes.getChange();
TopicChangedEvent event = new TopicChangedEvent(change);
event.change = changeAttributeSupplier(change, notes);
event.changer = accountAttributeSupplier(ev.getWho());
event.oldTopic = ev.getOldTopic();
dispatcher.run(d -> d.postEvent(change, event));
} catch (StorageException e) {
logger.atSevere().withCause(e).log("Failed to dispatch event");
}
}
@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, notes);
event.patchSet = patchSetAttributeSupplier(change, patchSet);
event.uploader = accountAttributeSupplier(ev.getWho());
dispatcher.run(d -> d.postEvent(change, event));
} catch (StorageException e) {
logger.atSevere().withCause(e).log("Failed to dispatch event");
}
}
@Override
public void onReviewerDeleted(ReviewerDeletedListener.Event ev) {
try {
ChangeNotes notes = getNotes(ev.getChange());
Change change = notes.getChange();
ReviewerDeletedEvent event = new ReviewerDeletedEvent(change);
event.change = changeAttributeSupplier(change, notes);
event.patchSet = patchSetAttributeSupplier(change, psUtil.current(notes));
event.reviewer = accountAttributeSupplier(ev.getReviewer());
event.remover = accountAttributeSupplier(ev.getWho());
event.comment = ev.getComment();
event.approvals =
approvalsAttributeSupplier(change, ev.getNewApprovals(), ev.getOldApprovals());
dispatcher.run(d -> d.postEvent(change, event));
} catch (StorageException e) {
logger.atSevere().withCause(e).log("Failed to dispatch event");
}
}
@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, notes);
event.patchSet = patchSetAttributeSupplier(change, psUtil.current(notes));
event.adder = accountAttributeSupplier(ev.getWho());
for (AccountInfo reviewer : ev.getReviewers()) {
event.reviewer = accountAttributeSupplier(reviewer);
dispatcher.run(d -> d.postEvent(event));
}
} catch (StorageException e) {
logger.atSevere().withCause(e).log("Failed to dispatch event");
}
}
@Override
public void onNewProjectCreated(NewProjectCreatedListener.Event ev) {
ProjectCreatedEvent event = new ProjectCreatedEvent();
event.projectName = ev.getProjectName();
event.headName = ev.getHeadName();
dispatcher.run(d -> d.postEvent(event.getProjectNameKey(), event));
}
@Override
public void onHashtagsEdited(HashtagsEditedListener.Event ev) {
try {
ChangeNotes notes = getNotes(ev.getChange());
Change change = notes.getChange();
HashtagsChangedEvent event = new HashtagsChangedEvent(change);
event.change = changeAttributeSupplier(change, notes);
event.editor = accountAttributeSupplier(ev.getWho());
event.hashtags = hashtagArray(ev.getHashtags());
event.added = hashtagArray(ev.getAddedHashtags());
event.removed = hashtagArray(ev.getRemovedHashtags());
dispatcher.run(d -> d.postEvent(change, event));
} catch (StorageException e) {
logger.atSevere().withCause(e).log("Failed to dispatch event");
}
}
@Override
public void onGitReferenceUpdated(GitReferenceUpdatedListener.Event ev) {
RefUpdatedEvent event = new RefUpdatedEvent();
if (ev.getUpdater() != null) {
event.submitter = accountAttributeSupplier(ev.getUpdater());
}
final BranchNameKey refName = BranchNameKey.create(ev.getProjectName(), ev.getRefName());
event.refUpdate =
Suppliers.memoize(
() ->
eventFactory.asRefUpdateAttribute(
ObjectId.fromString(ev.getOldObjectId()),
ObjectId.fromString(ev.getNewObjectId()),
refName));
dispatcher.run(d -> d.postEvent(refName, event));
}
@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, notes);
event.author = accountAttributeSupplier(ev.getWho());
event.patchSet = patchSetAttributeSupplier(change, ps);
event.comment = ev.getComment();
event.approvals = approvalsAttributeSupplier(change, ev.getApprovals(), ev.getOldApprovals());
dispatcher.run(d -> d.postEvent(change, event));
} catch (StorageException e) {
logger.atSevere().withCause(e).log("Failed to dispatch event");
}
}
@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, notes);
event.restorer = accountAttributeSupplier(ev.getWho());
event.patchSet = patchSetAttributeSupplier(change, psUtil.current(notes));
event.reason = ev.getReason();
dispatcher.run(d -> d.postEvent(change, event));
} catch (StorageException e) {
logger.atSevere().withCause(e).log("Failed to dispatch event");
}
}
@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, notes);
event.submitter = accountAttributeSupplier(ev.getWho());
event.patchSet = patchSetAttributeSupplier(change, psUtil.current(notes));
event.newRev = ev.getNewRevisionId();
dispatcher.run(d -> d.postEvent(change, event));
} catch (StorageException e) {
logger.atSevere().withCause(e).log("Failed to dispatch event");
}
}
@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, notes);
event.abandoner = accountAttributeSupplier(ev.getWho());
event.patchSet = patchSetAttributeSupplier(change, psUtil.current(notes));
event.reason = ev.getReason();
dispatcher.run(d -> d.postEvent(change, event));
} catch (StorageException e) {
logger.atSevere().withCause(e).log("Failed to dispatch event");
}
}
@Override
public void onWorkInProgressStateChanged(WorkInProgressStateChangedListener.Event ev) {
try {
ChangeNotes notes = getNotes(ev.getChange());
Change change = notes.getChange();
PatchSet patchSet = getPatchSet(notes, ev.getRevision());
WorkInProgressStateChangedEvent event = new WorkInProgressStateChangedEvent(change);
event.change = changeAttributeSupplier(change, notes);
event.changer = accountAttributeSupplier(ev.getWho());
event.patchSet = patchSetAttributeSupplier(change, patchSet);
dispatcher.run(d -> d.postEvent(change, event));
} catch (StorageException e) {
logger.atSevere().withCause(e).log("Failed to dispatch event");
}
}
@Override
public void onPrivateStateChanged(PrivateStateChangedListener.Event ev) {
try {
ChangeNotes notes = getNotes(ev.getChange());
Change change = notes.getChange();
PatchSet patchSet = getPatchSet(notes, ev.getRevision());
PrivateStateChangedEvent event = new PrivateStateChangedEvent(change);
event.change = changeAttributeSupplier(change, notes);
event.changer = accountAttributeSupplier(ev.getWho());
event.patchSet = patchSetAttributeSupplier(change, patchSet);
dispatcher.run(d -> d.postEvent(change, event));
} catch (StorageException e) {
logger.atSevere().withCause(e).log("Failed to dispatch event");
}
}
@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, notes);
event.patchSet = patchSetAttributeSupplier(change, psUtil.current(notes));
event.comment = ev.getMessage();
event.reviewer = accountAttributeSupplier(ev.getReviewer());
event.remover = accountAttributeSupplier(ev.getWho());
event.approvals = approvalsAttributeSupplier(change, ev.getApprovals(), ev.getOldApprovals());
dispatcher.run(d -> d.postEvent(change, event));
} catch (StorageException e) {
logger.atSevere().withCause(e).log("Failed to dispatch event");
}
}
@Override
public void onChangeDeleted(ChangeDeletedListener.Event ev) {
try {
ChangeNotes notes = getNotes(ev.getChange());
Change change = notes.getChange();
ChangeDeletedEvent event = new ChangeDeletedEvent(change);
event.change = changeAttributeSupplier(change, notes);
event.deleter = accountAttributeSupplier(ev.getWho());
dispatcher.run(d -> d.postEvent(change, event));
} catch (StorageException e) {
logger.atSevere().withCause(e).log("Failed to dispatch event");
}
}
}