blob: 950d39056f17271c8526cf97bbca67f70386691d [file] [log] [blame]
// Copyright (C) 2015 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.change;
import static java.util.Objects.requireNonNull;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.api.changes.ActionVisitor;
import com.google.gerrit.extensions.common.ActionInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.webui.PrivateInternals_UiActionDescription;
import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.extensions.webui.UiActions;
import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Singleton
public class ActionJson {
private final DynamicMap<RestView<RevisionResource>> revisionViews;
private final ChangeJson.Factory changeJsonFactory;
private final ChangeResource.Factory changeResourceFactory;
private final UiActions uiActions;
private final DynamicMap<RestView<ChangeResource>> changeViews;
private final DynamicSet<ActionVisitor> visitorSet;
private final Provider<CurrentUser> userProvider;
@Inject
ActionJson(
DynamicMap<RestView<RevisionResource>> views,
ChangeJson.Factory changeJsonFactory,
ChangeResource.Factory changeResourceFactory,
UiActions uiActions,
DynamicMap<RestView<ChangeResource>> changeViews,
DynamicSet<ActionVisitor> visitorSet,
Provider<CurrentUser> userProvider) {
this.revisionViews = views;
this.changeJsonFactory = changeJsonFactory;
this.changeResourceFactory = changeResourceFactory;
this.uiActions = uiActions;
this.changeViews = changeViews;
this.visitorSet = visitorSet;
this.userProvider = userProvider;
}
public Map<String, ActionInfo> format(RevisionResource rsrc) {
ChangeInfo changeInfo = null;
RevisionInfo revisionInfo = null;
List<ActionVisitor> visitors = visitors();
if (!visitors.isEmpty()) {
changeInfo = changeJson().format(rsrc);
revisionInfo = requireNonNull(Iterables.getOnlyElement(changeInfo.revisions.values()));
changeInfo.revisions = null;
}
return toActionMap(rsrc, visitors, changeInfo, revisionInfo);
}
private ChangeJson changeJson() {
return changeJsonFactory.noOptions();
}
private ArrayList<ActionVisitor> visitors() {
return Lists.newArrayList(visitorSet);
}
void addChangeActions(ChangeInfo to, ChangeData changeData) {
List<ActionVisitor> visitors = visitors();
to.actions = toActionMap(changeData, visitors, copy(visitors, to));
}
void addRevisionActions(@Nullable ChangeInfo changeInfo, RevisionInfo to, RevisionResource rsrc) {
List<ActionVisitor> visitors = visitors();
if (!visitors.isEmpty()) {
if (changeInfo != null) {
changeInfo = copy(visitors, changeInfo);
} else {
changeInfo = changeJson().format(rsrc);
}
}
to.actions = toActionMap(rsrc, visitors, changeInfo, copy(visitors, to));
}
@Nullable
private ChangeInfo copy(List<ActionVisitor> visitors, ChangeInfo changeInfo) {
if (visitors.isEmpty()) {
return null;
}
// Include all fields from ChangeJson#toChangeInfoImpl that are not protected by any
// ListChangesOptions.
ChangeInfo copy = new ChangeInfo();
copy.project = changeInfo.project;
copy.branch = changeInfo.branch;
copy.topic = changeInfo.topic;
copy.attentionSet =
changeInfo.attentionSet == null ? null : ImmutableMap.copyOf(changeInfo.attentionSet);
copy.removedFromAttentionSet =
changeInfo.removedFromAttentionSet == null
? null
: ImmutableMap.copyOf(changeInfo.removedFromAttentionSet);
copy.customKeyedValues =
changeInfo.customKeyedValues == null
? null
: ImmutableMap.copyOf(changeInfo.customKeyedValues);
copy.hashtags = changeInfo.hashtags;
copy.changeId = changeInfo.changeId;
copy.submitType = changeInfo.submitType;
copy.mergeable = changeInfo.mergeable;
copy.insertions = changeInfo.insertions;
copy.deletions = changeInfo.deletions;
copy.hasReviewStarted = changeInfo.hasReviewStarted;
copy.isPrivate = changeInfo.isPrivate;
copy.subject = changeInfo.subject;
copy.status = changeInfo.status;
copy.owner = changeInfo.owner;
copy.created = changeInfo.created;
copy.updated = changeInfo.updated;
copy._number = changeInfo._number;
copy.requirements = changeInfo.requirements;
copy.revertOf = changeInfo.revertOf;
copy.submissionId = changeInfo.submissionId;
copy.starred = changeInfo.starred;
copy.submitted = changeInfo.submitted;
copy.submitter = changeInfo.submitter;
copy.unresolvedCommentCount = changeInfo.unresolvedCommentCount;
copy.workInProgress = changeInfo.workInProgress;
copy.id = changeInfo.id;
copy.tripletId = changeInfo.tripletId;
copy.cherryPickOfChange = changeInfo.cherryPickOfChange;
copy.cherryPickOfPatchSet = changeInfo.cherryPickOfPatchSet;
return copy;
}
@Nullable
private RevisionInfo copy(List<ActionVisitor> visitors, RevisionInfo revisionInfo) {
if (visitors.isEmpty()) {
return null;
}
// Include all fields from RevisionJson#toRevisionInfo that are not protected by any
// ListChangesOptions.
RevisionInfo copy = new RevisionInfo();
copy.isCurrent = revisionInfo.isCurrent;
copy._number = revisionInfo._number;
copy.ref = revisionInfo.ref;
copy.branch = revisionInfo.branch;
copy.created = revisionInfo.created;
copy.uploader = revisionInfo.uploader;
copy.realUploader = revisionInfo.realUploader;
copy.fetch = revisionInfo.fetch;
copy.kind = revisionInfo.kind;
copy.description = revisionInfo.description;
return copy;
}
private Map<String, ActionInfo> toActionMap(
ChangeData changeData, List<ActionVisitor> visitors, ChangeInfo changeInfo) {
try (TraceTimer timer =
TraceContext.newTimer(
"Get actions",
Metadata.builder().changeId(changeData.change().getId().get()).build())) {
CurrentUser user = userProvider.get();
Map<String, ActionInfo> out = new LinkedHashMap<>();
if (!user.isIdentifiedUser()) {
return out;
}
Iterable<UiAction.Description> descs =
uiActions.from(changeViews, changeResourceFactory.create(changeData, user));
// The followup action is a client-side only operation that does not
// have a server side handler. It must be manually registered into the
// resulting action map.
if (!changeData.change().isAbandoned()) {
UiAction.Description descr = new UiAction.Description();
PrivateInternals_UiActionDescription.setId(descr, "followup");
PrivateInternals_UiActionDescription.setMethod(descr, "POST");
descr.setTitle("Create follow-up change");
descr.setLabel("Follow-Up");
descs = Iterables.concat(descs, Collections.singleton(descr));
}
ACTION:
for (UiAction.Description d : descs) {
ActionInfo actionInfo = new ActionInfo(d);
for (ActionVisitor visitor : visitors) {
if (!visitor.visit(d.getId(), actionInfo, changeInfo)) {
continue ACTION;
}
}
out.put(d.getId(), actionInfo);
}
return out;
}
}
private ImmutableMap<String, ActionInfo> toActionMap(
RevisionResource rsrc,
List<ActionVisitor> visitors,
ChangeInfo changeInfo,
RevisionInfo revisionInfo) {
if (!rsrc.getUser().isIdentifiedUser()) {
return ImmutableMap.of();
}
Map<String, ActionInfo> out = new LinkedHashMap<>();
ACTION:
for (UiAction.Description d : uiActions.from(revisionViews, rsrc)) {
ActionInfo actionInfo = new ActionInfo(d);
for (ActionVisitor visitor : visitors) {
if (!visitor.visit(d.getId(), actionInfo, changeInfo, revisionInfo)) {
continue ACTION;
}
}
out.put(d.getId(), actionInfo);
}
return ImmutableMap.copyOf(out);
}
}