| // 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.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) { | |
| 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); | |
| } | |
| } |