All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gerrit.server.restapi.account.DeleteDraftComments Maven / Gradle / Ivy

There is a newer version: 3.10.0-rc4
Show newest version
// Copyright (C) 2018 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.restapi.account;

import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.gerrit.server.CommentsUtil.setCommentRevId;

import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.api.accounts.DeleteDraftCommentsInput;
import com.google.gerrit.extensions.api.accounts.DeletedDraftCommentInfo;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Account.Id;
import com.google.gerrit.reviewdb.client.Comment;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.account.AccountResource;
import com.google.gerrit.server.change.ChangeJson;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
import com.google.gerrit.server.query.change.HasDraftByPredicate;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.restapi.change.CommentJson;
import com.google.gerrit.server.restapi.change.CommentJson.CommentFormatter;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.BatchUpdate.Factory;
import com.google.gerrit.server.update.BatchUpdateListener;
import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.ChangeContext;
import com.google.gerrit.server.update.UpdateException;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

@Singleton
public class DeleteDraftComments
    implements RestModifyView {

  private final Provider db;
  private final Provider userProvider;
  private final BatchUpdate.Factory batchUpdateFactory;
  private final Provider queryBuilderProvider;
  private final Provider queryProvider;
  private final ChangeData.Factory changeDataFactory;
  private final ChangeJson.Factory changeJsonFactory;
  private final Provider commentJsonProvider;
  private final CommentsUtil commentsUtil;
  private final PatchSetUtil psUtil;
  private final PatchListCache patchListCache;

  @Inject
  DeleteDraftComments(
      Provider db,
      Provider userProvider,
      Factory batchUpdateFactory,
      Provider queryBuilderProvider,
      Provider queryProvider,
      ChangeData.Factory changeDataFactory,
      ChangeJson.Factory changeJsonFactory,
      Provider commentJsonProvider,
      CommentsUtil commentsUtil,
      PatchSetUtil psUtil,
      PatchListCache patchListCache) {
    this.db = db;
    this.userProvider = userProvider;
    this.batchUpdateFactory = batchUpdateFactory;
    this.queryBuilderProvider = queryBuilderProvider;
    this.queryProvider = queryProvider;
    this.changeDataFactory = changeDataFactory;
    this.changeJsonFactory = changeJsonFactory;
    this.commentJsonProvider = commentJsonProvider;
    this.commentsUtil = commentsUtil;
    this.psUtil = psUtil;
    this.patchListCache = patchListCache;
  }

  @Override
  public ImmutableList apply(
      AccountResource rsrc, DeleteDraftCommentsInput input)
      throws RestApiException, OrmException, UpdateException {
    CurrentUser user = userProvider.get();
    if (!user.isIdentifiedUser()) {
      throw new AuthException("Authentication required");
    }
    if (!rsrc.getUser().hasSameAccountId(user)) {
      // Disallow even for admins or users with Modify Account. Drafts are not like preferences or
      // other account info; there is no way even for admins to read or delete another user's drafts
      // using the normal draft endpoints under the change resource, so disallow it here as well.
      // (Admins may still call this endpoint with impersonation, but in that case it would pass the
      // hasSameAccountId check.)
      throw new AuthException("Cannot delete drafts of other user");
    }

    CommentFormatter commentFormatter = commentJsonProvider.get().newCommentFormatter();
    Account.Id accountId = rsrc.getUser().getAccountId();
    Timestamp now = TimeUtil.nowTs();
    Map updates = new LinkedHashMap<>();
    List ops = new ArrayList<>();
    for (ChangeData cd :
        queryProvider
            .get()
            // Don't attempt to mutate any changes the user can't currently see.
            .enforceVisibility(true)
            .query(predicate(accountId, input))) {
      BatchUpdate update =
          updates.computeIfAbsent(
              cd.project(), p -> batchUpdateFactory.create(db.get(), p, rsrc.getUser(), now));
      Op op = new Op(commentFormatter, accountId);
      update.addOp(cd.getId(), op);
      ops.add(op);
    }

    // Currently there's no way to let some updates succeed even if others fail. Even if there were,
    // all updates from this operation only happen in All-Users and thus are fully atomic, so
    // allowing partial failure would have little value.
    batchUpdateFactory.execute(updates.values(), BatchUpdateListener.NONE, false);

    return ops.stream().map(Op::getResult).filter(Objects::nonNull).collect(toImmutableList());
  }

  private Predicate predicate(Account.Id accountId, DeleteDraftCommentsInput input)
      throws BadRequestException {
    Predicate hasDraft = new HasDraftByPredicate(accountId);
    if (CharMatcher.whitespace().trimFrom(Strings.nullToEmpty(input.query)).isEmpty()) {
      return hasDraft;
    }
    try {
      return Predicate.and(hasDraft, queryBuilderProvider.get().parse(input.query));
    } catch (QueryParseException e) {
      throw new BadRequestException("Invalid query: " + e.getMessage(), e);
    }
  }

  private class Op implements BatchUpdateOp {
    private final CommentFormatter commentFormatter;
    private final Account.Id accountId;
    private DeletedDraftCommentInfo result;

    Op(CommentFormatter commentFormatter, Id accountId) {
      this.commentFormatter = commentFormatter;
      this.accountId = accountId;
    }

    @Override
    public boolean updateChange(ChangeContext ctx)
        throws OrmException, PatchListNotAvailableException, PermissionBackendException {
      ImmutableList.Builder comments = ImmutableList.builder();
      boolean dirty = false;
      for (Comment c : commentsUtil.draftByChangeAuthor(ctx.getDb(), ctx.getNotes(), accountId)) {
        dirty = true;
        PatchSet.Id psId = new PatchSet.Id(ctx.getChange().getId(), c.key.patchSetId);
        setCommentRevId(
            c, patchListCache, ctx.getChange(), psUtil.get(ctx.getDb(), ctx.getNotes(), psId));
        commentsUtil.deleteComments(ctx.getDb(), ctx.getUpdate(psId), Collections.singleton(c));
        comments.add(commentFormatter.format(c));
      }
      if (dirty) {
        result = new DeletedDraftCommentInfo();
        result.change =
            changeJsonFactory
                .create(ListChangesOption.SKIP_MERGEABLE)
                .format(changeDataFactory.create(ctx.getDb(), ctx.getNotes()));
        result.deleted = comments.build();
      }
      return dirty;
    }

    @Nullable
    DeletedDraftCommentInfo getResult() {
      return result;
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy