Line data Source code
1 : // Copyright (C) 2012 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.restapi.change; 16 : 17 : import static com.google.gerrit.server.project.ProjectCache.illegalState; 18 : 19 : import com.google.common.base.Strings; 20 : import com.google.common.flogger.FluentLogger; 21 : import com.google.gerrit.entities.Change; 22 : import com.google.gerrit.entities.Change.Status; 23 : import com.google.gerrit.entities.PatchSet; 24 : import com.google.gerrit.extensions.api.changes.RestoreInput; 25 : import com.google.gerrit.extensions.common.ChangeInfo; 26 : import com.google.gerrit.extensions.restapi.ResourceConflictException; 27 : import com.google.gerrit.extensions.restapi.Response; 28 : import com.google.gerrit.extensions.restapi.RestApiException; 29 : import com.google.gerrit.extensions.restapi.RestModifyView; 30 : import com.google.gerrit.extensions.webui.UiAction; 31 : import com.google.gerrit.server.ChangeMessagesUtil; 32 : import com.google.gerrit.server.ChangeUtil; 33 : import com.google.gerrit.server.PatchSetUtil; 34 : import com.google.gerrit.server.change.ChangeJson; 35 : import com.google.gerrit.server.change.ChangeResource; 36 : import com.google.gerrit.server.extensions.events.ChangeRestored; 37 : import com.google.gerrit.server.mail.send.MessageIdGenerator; 38 : import com.google.gerrit.server.mail.send.ReplyToChangeSender; 39 : import com.google.gerrit.server.mail.send.RestoredSender; 40 : import com.google.gerrit.server.notedb.ChangeUpdate; 41 : import com.google.gerrit.server.permissions.ChangePermission; 42 : import com.google.gerrit.server.permissions.PermissionBackendException; 43 : import com.google.gerrit.server.project.ProjectCache; 44 : import com.google.gerrit.server.project.ProjectState; 45 : import com.google.gerrit.server.update.BatchUpdate; 46 : import com.google.gerrit.server.update.BatchUpdateOp; 47 : import com.google.gerrit.server.update.ChangeContext; 48 : import com.google.gerrit.server.update.PostUpdateContext; 49 : import com.google.gerrit.server.update.UpdateException; 50 : import com.google.gerrit.server.util.time.TimeUtil; 51 : import com.google.inject.Inject; 52 : import com.google.inject.Singleton; 53 : import java.io.IOException; 54 : 55 : @Singleton 56 : public class Restore 57 : implements RestModifyView<ChangeResource, RestoreInput>, UiAction<ChangeResource> { 58 145 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 59 : 60 : private final BatchUpdate.Factory updateFactory; 61 : private final RestoredSender.Factory restoredSenderFactory; 62 : private final ChangeJson.Factory json; 63 : private final ChangeMessagesUtil cmUtil; 64 : private final PatchSetUtil psUtil; 65 : private final ChangeRestored changeRestored; 66 : private final ProjectCache projectCache; 67 : private final MessageIdGenerator messageIdGenerator; 68 : 69 : @Inject 70 : Restore( 71 : BatchUpdate.Factory updateFactory, 72 : RestoredSender.Factory restoredSenderFactory, 73 : ChangeJson.Factory json, 74 : ChangeMessagesUtil cmUtil, 75 : PatchSetUtil psUtil, 76 : ChangeRestored changeRestored, 77 : ProjectCache projectCache, 78 145 : MessageIdGenerator messageIdGenerator) { 79 145 : this.updateFactory = updateFactory; 80 145 : this.restoredSenderFactory = restoredSenderFactory; 81 145 : this.json = json; 82 145 : this.cmUtil = cmUtil; 83 145 : this.psUtil = psUtil; 84 145 : this.changeRestored = changeRestored; 85 145 : this.projectCache = projectCache; 86 145 : this.messageIdGenerator = messageIdGenerator; 87 145 : } 88 : 89 : @Override 90 : public Response<ChangeInfo> apply(ChangeResource rsrc, RestoreInput input) 91 : throws RestApiException, UpdateException, PermissionBackendException, IOException { 92 : // Not allowed to restore if the current patch set is locked. 93 7 : psUtil.checkPatchSetNotLocked(rsrc.getNotes()); 94 : 95 7 : rsrc.permissions().check(ChangePermission.RESTORE); 96 7 : projectCache 97 7 : .get(rsrc.getProject()) 98 7 : .orElseThrow(illegalState(rsrc.getProject())) 99 7 : .checkStatePermitsWrite(); 100 : 101 7 : Op op = new Op(input); 102 7 : try (BatchUpdate u = 103 7 : updateFactory.create(rsrc.getChange().getProject(), rsrc.getUser(), TimeUtil.now())) { 104 7 : u.addOp(rsrc.getId(), op).execute(); 105 : } 106 7 : return Response.ok(json.noOptions().format(op.change)); 107 : } 108 : 109 : private class Op implements BatchUpdateOp { 110 : private final RestoreInput input; 111 : 112 : private Change change; 113 : private PatchSet patchSet; 114 : private String mailMessage; 115 : 116 7 : private Op(RestoreInput input) { 117 7 : this.input = input; 118 7 : } 119 : 120 : @Override 121 : public boolean updateChange(ChangeContext ctx) throws ResourceConflictException { 122 7 : change = ctx.getChange(); 123 7 : if (change == null || !change.isAbandoned()) { 124 1 : throw new ResourceConflictException("change is " + ChangeUtil.status(change)); 125 : } 126 7 : PatchSet.Id psId = change.currentPatchSetId(); 127 7 : ChangeUpdate update = ctx.getUpdate(psId); 128 7 : patchSet = psUtil.get(ctx.getNotes(), psId); 129 7 : change.setStatus(Status.NEW); 130 7 : change.setLastUpdatedOn(ctx.getWhen()); 131 7 : update.setStatus(change.getStatus()); 132 : 133 7 : mailMessage = cmUtil.setChangeMessage(ctx, commentMessage(), ChangeMessagesUtil.TAG_RESTORE); 134 7 : return true; 135 : } 136 : 137 : private String commentMessage() { 138 7 : StringBuilder msg = new StringBuilder(); 139 7 : msg.append("Restored"); 140 7 : if (!Strings.nullToEmpty(input.message).trim().isEmpty()) { 141 1 : msg.append("\n\n"); 142 1 : msg.append(input.message.trim()); 143 : } 144 7 : return msg.toString(); 145 : } 146 : 147 : @Override 148 : public void postUpdate(PostUpdateContext ctx) { 149 : try { 150 7 : ReplyToChangeSender emailSender = 151 7 : restoredSenderFactory.create(ctx.getProject(), change.getId()); 152 7 : emailSender.setFrom(ctx.getAccountId()); 153 7 : emailSender.setChangeMessage(mailMessage, ctx.getWhen()); 154 7 : emailSender.setMessageId( 155 7 : messageIdGenerator.fromChangeUpdate(ctx.getRepoView(), change.currentPatchSetId())); 156 7 : emailSender.send(); 157 0 : } catch (Exception e) { 158 0 : logger.atSevere().withCause(e).log("Cannot email update for change %s", change.getId()); 159 7 : } 160 7 : changeRestored.fire( 161 7 : ctx.getChangeData(change), 162 : patchSet, 163 7 : ctx.getAccount(), 164 7 : Strings.emptyToNull(input.message), 165 7 : ctx.getWhen()); 166 7 : } 167 : } 168 : 169 : @Override 170 : public UiAction.Description getDescription(ChangeResource rsrc) throws IOException { 171 57 : UiAction.Description description = 172 : new UiAction.Description() 173 57 : .setLabel("Restore") 174 57 : .setTitle("Restore the change") 175 57 : .setVisible(false); 176 : 177 57 : Change change = rsrc.getChange(); 178 57 : if (!change.isAbandoned()) { 179 57 : return description; 180 : } 181 8 : if (!projectCache.get(rsrc.getProject()).map(ProjectState::statePermitsRead).orElse(false)) { 182 0 : return description; 183 : } 184 8 : if (psUtil.isPatchSetLocked(rsrc.getNotes())) { 185 0 : return description; 186 : } 187 8 : boolean visible = rsrc.permissions().testOrFalse(ChangePermission.RESTORE); 188 8 : return description.setVisible(visible); 189 : } 190 : }