blob: a5fe0a6224226d458e26a20a3fd5560ad10a0a0f [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 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.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.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 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;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
@Singleton
public class StreamEventsApiListener implements
ChangeAbandonedListener,
ChangeMergedListener,
ChangeRestoredListener,
CommentAddedListener,
DraftPublishedListener,
GitReferenceUpdatedListener,
HashtagsEditedListener,
NewProjectCreatedListener,
ReviewerAddedListener,
ReviewerDeletedListener,
RevisionCreatedListener,
TopicEditedListener {
private static final Logger log =
LoggerFactory.getLogger(StreamEventsApiListener.class);
public static class Module extends AbstractModule {
@Override
protected void configure() {
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);
}
}
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 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.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 onReviewerAdded(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));
event.reviewer = accountAttributeSupplier(ev.getReviewer());
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);
}
}
}