| // Copyright (C) 2008 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.httpd.rpc.changedetail; |
| |
| import com.google.gerrit.common.data.ApprovalDetail; |
| import com.google.gerrit.common.data.ApprovalType; |
| import com.google.gerrit.common.data.ApprovalTypes; |
| import com.google.gerrit.common.data.ChangeDetail; |
| import com.google.gerrit.common.data.ChangeInfo; |
| import com.google.gerrit.common.errors.NoSuchEntityException; |
| import com.google.gerrit.httpd.rpc.Handler; |
| import com.google.gerrit.reviewdb.Account; |
| import com.google.gerrit.reviewdb.ApprovalCategory; |
| import com.google.gerrit.reviewdb.Change; |
| import com.google.gerrit.reviewdb.ChangeMessage; |
| import com.google.gerrit.reviewdb.PatchSet; |
| import com.google.gerrit.reviewdb.PatchSetAncestor; |
| import com.google.gerrit.reviewdb.PatchSetApproval; |
| import com.google.gerrit.reviewdb.RevId; |
| import com.google.gerrit.reviewdb.ReviewDb; |
| import com.google.gerrit.server.IdentifiedUser; |
| import com.google.gerrit.server.account.AccountInfoCacheFactory; |
| import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException; |
| import com.google.gerrit.server.project.ChangeControl; |
| import com.google.gerrit.server.project.NoSuchChangeException; |
| import com.google.gerrit.server.workflow.CategoryFunction; |
| import com.google.gerrit.server.workflow.FunctionState; |
| import com.google.gwtorm.client.OrmException; |
| import com.google.inject.Inject; |
| import com.google.inject.assistedinject.Assisted; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** Creates a {@link ChangeDetail} from a {@link Change}. */ |
| public class ChangeDetailFactory extends Handler<ChangeDetail> { |
| public interface Factory { |
| ChangeDetailFactory create(Change.Id id); |
| } |
| |
| private final ApprovalTypes approvalTypes; |
| private final ChangeControl.Factory changeControlFactory; |
| private final FunctionState.Factory functionState; |
| private final PatchSetDetailFactory.Factory patchSetDetail; |
| private final AccountInfoCacheFactory aic; |
| private final ReviewDb db; |
| |
| private final Change.Id changeId; |
| |
| private ChangeDetail detail; |
| private ChangeControl control; |
| |
| @Inject |
| ChangeDetailFactory(final ApprovalTypes approvalTypes, |
| final FunctionState.Factory functionState, |
| final PatchSetDetailFactory.Factory patchSetDetail, final ReviewDb db, |
| final ChangeControl.Factory changeControlFactory, |
| final AccountInfoCacheFactory.Factory accountInfoCacheFactory, |
| @Assisted final Change.Id id) { |
| this.approvalTypes = approvalTypes; |
| this.functionState = functionState; |
| this.patchSetDetail = patchSetDetail; |
| this.db = db; |
| this.changeControlFactory = changeControlFactory; |
| this.aic = accountInfoCacheFactory.create(); |
| |
| this.changeId = id; |
| } |
| |
| @Override |
| public ChangeDetail call() throws OrmException, NoSuchEntityException, |
| PatchSetInfoNotAvailableException, NoSuchChangeException { |
| control = changeControlFactory.validateFor(changeId); |
| final Change change = control.getChange(); |
| final PatchSet patch = db.patchSets().get(change.currentPatchSetId()); |
| if (patch == null) { |
| throw new NoSuchEntityException(); |
| } |
| |
| aic.want(change.getOwner()); |
| |
| detail = new ChangeDetail(); |
| detail.setChange(change); |
| detail.setAllowsAnonymous(control.forAnonymousUser().isVisible()); |
| |
| detail.setCanAbandon(change.getStatus().isOpen() && control.canAbandon()); |
| detail.setCanRestore(change.getStatus() == Change.Status.ABANDONED && control.canRestore()); |
| detail.setStarred(control.getCurrentUser().getStarredChanges().contains( |
| changeId)); |
| |
| detail.setCanRevert(change.getStatus() == Change.Status.MERGED && control.canAddPatchSet()); |
| |
| loadPatchSets(); |
| loadMessages(); |
| if (change.currentPatchSetId() != null) { |
| loadCurrentPatchSet(); |
| } |
| load(); |
| detail.setAccounts(aic.create()); |
| return detail; |
| } |
| |
| private void loadPatchSets() throws OrmException { |
| detail.setPatchSets(db.patchSets().byChange(changeId).toList()); |
| } |
| |
| private void loadMessages() throws OrmException { |
| detail.setMessages(db.changeMessages().byChange(changeId).toList()); |
| for (final ChangeMessage m : detail.getMessages()) { |
| aic.want(m.getAuthor()); |
| } |
| } |
| |
| private void load() throws OrmException { |
| final PatchSet.Id psId = detail.getChange().currentPatchSetId(); |
| final List<PatchSetApproval> allApprovals = |
| db.patchSetApprovals().byChange(changeId).toList(); |
| |
| if (detail.getChange().getStatus().isOpen()) { |
| final FunctionState fs = |
| functionState.create(detail.getChange(), psId, allApprovals); |
| |
| final Set<ApprovalCategory.Id> missingApprovals = |
| new HashSet<ApprovalCategory.Id>(); |
| |
| final Set<ApprovalCategory.Id> currentActions = |
| new HashSet<ApprovalCategory.Id>(); |
| |
| for (final ApprovalType at : approvalTypes.getApprovalTypes()) { |
| CategoryFunction.forCategory(at.getCategory()).run(at, fs); |
| if (!fs.isValid(at)) { |
| missingApprovals.add(at.getCategory().getId()); |
| } |
| } |
| for (final ApprovalType at : approvalTypes.getActionTypes()) { |
| if (CategoryFunction.forCategory(at.getCategory()).isValid( |
| control.getCurrentUser(), at, fs)) { |
| currentActions.add(at.getCategory().getId()); |
| } |
| } |
| detail.setMissingApprovals(missingApprovals); |
| detail.setCurrentActions(currentActions); |
| } |
| |
| final boolean canRemoveReviewers = detail.getChange().getStatus().isOpen() // |
| && control.getCurrentUser() instanceof IdentifiedUser; |
| final HashMap<Account.Id, ApprovalDetail> ad = |
| new HashMap<Account.Id, ApprovalDetail>(); |
| for (PatchSetApproval ca : allApprovals) { |
| ApprovalDetail d = ad.get(ca.getAccountId()); |
| if (d == null) { |
| d = new ApprovalDetail(ca.getAccountId()); |
| d.setCanRemove(canRemoveReviewers); |
| ad.put(d.getAccount(), d); |
| } |
| if (d.canRemove()) { |
| d.setCanRemove(control.canRemoveReviewer(ca)); |
| } |
| if (ca.getPatchSetId().equals(psId)) { |
| d.add(ca); |
| } |
| } |
| |
| final Account.Id owner = detail.getChange().getOwner(); |
| if (ad.containsKey(owner)) { |
| // Ensure the owner always sorts to the top of the table |
| // |
| ad.get(owner).sortFirst(); |
| } |
| |
| aic.want(ad.keySet()); |
| detail.setApprovals(ad.values()); |
| } |
| |
| private void loadCurrentPatchSet() throws OrmException, |
| NoSuchEntityException, PatchSetInfoNotAvailableException, |
| NoSuchChangeException { |
| final PatchSet.Id psId = detail.getChange().currentPatchSetId(); |
| final PatchSetDetailFactory loader = patchSetDetail.create(null, psId, null); |
| loader.patchSet = detail.getCurrentPatchSet(); |
| loader.control = control; |
| detail.setCurrentPatchSetDetail(loader.call()); |
| |
| final HashSet<Change.Id> changesToGet = new HashSet<Change.Id>(); |
| final List<Change.Id> ancestorOrder = new ArrayList<Change.Id>(); |
| for (PatchSetAncestor a : db.patchSetAncestors().ancestorsOf(psId)) { |
| for (PatchSet p : db.patchSets().byRevision(a.getAncestorRevision())) { |
| final Change.Id ck = p.getId().getParentKey(); |
| if (changesToGet.add(ck)) { |
| ancestorOrder.add(ck); |
| } |
| } |
| } |
| |
| final RevId cprev = loader.patchSet.getRevision(); |
| final Set<Change.Id> descendants = new HashSet<Change.Id>(); |
| if (cprev != null) { |
| for (PatchSetAncestor a : db.patchSetAncestors().descendantsOf(cprev)) { |
| final Change.Id ck = a.getPatchSet().getParentKey(); |
| if (descendants.add(ck)) { |
| changesToGet.add(a.getPatchSet().getParentKey()); |
| } |
| } |
| } |
| final Map<Change.Id, Change> m = |
| db.changes().toMap(db.changes().get(changesToGet)); |
| |
| final ArrayList<ChangeInfo> dependsOn = new ArrayList<ChangeInfo>(); |
| for (final Change.Id a : ancestorOrder) { |
| final Change ac = m.get(a); |
| if (ac != null) { |
| dependsOn.add(newChangeInfo(ac)); |
| } |
| } |
| |
| final ArrayList<ChangeInfo> neededBy = new ArrayList<ChangeInfo>(); |
| for (final Change.Id a : descendants) { |
| final Change ac = m.get(a); |
| if (ac != null) { |
| neededBy.add(newChangeInfo(ac)); |
| } |
| } |
| |
| Collections.sort(neededBy, new Comparator<ChangeInfo>() { |
| public int compare(final ChangeInfo o1, final ChangeInfo o2) { |
| return o1.getId().get() - o2.getId().get(); |
| } |
| }); |
| |
| detail.setDependsOn(dependsOn); |
| detail.setNeededBy(neededBy); |
| } |
| |
| private ChangeInfo newChangeInfo(final Change ac) { |
| aic.want(ac.getOwner()); |
| ChangeInfo ci = new ChangeInfo(ac); |
| ci.setStarred(isStarred(ac)); |
| return ci; |
| } |
| |
| private boolean isStarred(final Change ac) { |
| return control.getCurrentUser().getStarredChanges().contains(ac.getId()); |
| } |
| } |