// Copyright (C) 2008 The Android Open Source Project // Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). // // 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.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.data.PatchSetApprovalDetail; 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.CanSubmitResult; 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 { 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(); } final CanSubmitResult canSubmitResult = control.canSubmit(patch.getId()); 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.setCanSubmit(canSubmitResult == CanSubmitResult.OK); detail.setStarred(control.getCurrentUser().getStarredChanges().contains( changeId)); detail.setCanRevert(change.getStatus() == Change.Status.MERGED && control.canAddPatchSet() && (change.getTopicId() == null)); loadPatchSets(); loadMessages(); if (change.currentPatchSetId() != null) { loadCurrentPatchSet(); } load(); detail.setAccounts(aic.create()); if (change.getTopicId() != null) { detail.setTopicId(change.getTopicId().get()); } else { detail.setTopicId(-1); final CanSubmitResult canStageResult = control.canStage(patch.getId()); detail.setCanStage(canStageResult == CanSubmitResult.OK); detail.setCanUnstage(change.getStatus() == Change.Status.STAGED && control.canAbandon()); } 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 allApprovals = db.patchSetApprovals().byChange(changeId).toList(); if (detail.getChange().getStatus().isOpen()) { final FunctionState fs = functionState.create(detail.getChange(), psId, allApprovals); final Set missingApprovals = new HashSet(); for (final ApprovalType at : approvalTypes.getApprovalTypes()) { CategoryFunction.forCategory(at.getCategory()).run(at, fs); if (!fs.isValid(at)) { missingApprovals.add(at.getCategory().getId()); } } detail.setMissingApprovals(missingApprovals); } final boolean canRemoveReviewers = detail.getChange().getStatus().isOpen() // && control.getCurrentUser() instanceof IdentifiedUser; final HashMap ad = new HashMap(); for (PatchSetApproval ca : allApprovals) { PatchSetApprovalDetail d = ad.get(ca.getAccountId()); if (d == null) { d = new PatchSetApprovalDetail(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 changesToGet = new HashSet(); final List ancestorOrder = new ArrayList(); 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 descendants = new HashSet(); 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 m = db.changes().toMap(db.changes().get(changesToGet)); final ArrayList dependsOn = new ArrayList(); for (final Change.Id a : ancestorOrder) { final Change ac = m.get(a); if (ac != null) { dependsOn.add(newChangeInfo(ac)); } } final ArrayList neededBy = new ArrayList(); for (final Change.Id a : descendants) { final Change ac = m.get(a); if (ac != null) { neededBy.add(newChangeInfo(ac)); } } Collections.sort(neededBy, new Comparator() { 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()); } }