LCOV - code coverage report
Current view: top level - server - ExceptionHookImpl.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 30 34 88.2 %
Date: 2022-11-19 15:00:39 Functions: 15 15 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2019 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;
      16             : 
      17             : import com.google.common.base.Predicate;
      18             : import com.google.common.base.Throwables;
      19             : import com.google.common.collect.ImmutableList;
      20             : import com.google.gerrit.common.Nullable;
      21             : import com.google.gerrit.exceptions.MergeUpdateException;
      22             : import com.google.gerrit.git.LockFailureException;
      23             : import com.google.gerrit.server.project.ProjectConfig;
      24             : import java.util.Optional;
      25             : import org.eclipse.jgit.errors.MissingObjectException;
      26             : import org.eclipse.jgit.lib.RefUpdate;
      27             : 
      28             : /**
      29             :  * Class to detect and handle exceptions that are caused by temporary errors, and hence should cause
      30             :  * a retry of the failed operation.
      31             :  */
      32          39 : public class ExceptionHookImpl implements ExceptionHook {
      33             :   private static final String LOCK_FAILURE_USER_MESSAGE =
      34             :       "Updating a ref failed with LOCK_FAILURE.\n"
      35             :           + "This may be a temporary issue due to concurrent updates.\n"
      36             :           + "Please retry later.";
      37             :   private static final String INVALID_PROJECT_CONFIG_USER_MESSAGE =
      38             :       "Invalid " + ProjectConfig.PROJECT_CONFIG + " file.";
      39             :   private static final String CONTACT_PROJECT_OWNER_USER_MESSAGE =
      40             :       "Please contact the project owner.";
      41             : 
      42             :   @Override
      43             :   public boolean shouldRetry(String actionType, String actionName, Throwable throwable) {
      44          38 :     return isLockFailure(throwable);
      45             :   }
      46             : 
      47             :   @Override
      48             :   public boolean skipRetryWithTrace(String actionType, String actionName, Throwable throwable) {
      49           1 :     return isInvalidProjectConfig(throwable);
      50             :   }
      51             : 
      52             :   @Override
      53             :   public Optional<String> formatCause(Throwable throwable) {
      54          30 :     if (isLockFailure(throwable)) {
      55          11 :       return Optional.of(RefUpdate.Result.LOCK_FAILURE.name());
      56             :     }
      57          21 :     if (isMissingObjectException(throwable)) {
      58           0 :       return Optional.of("missing_object");
      59             :     }
      60          21 :     if (isInvalidProjectConfig(throwable)) {
      61           1 :       return Optional.of("invalid_project_config");
      62             :     }
      63          20 :     return Optional.empty();
      64             :   }
      65             : 
      66             :   @Override
      67             :   public ImmutableList<String> getUserMessages(Throwable throwable, @Nullable String traceId) {
      68           2 :     if (isLockFailure(throwable)) {
      69           0 :       return ImmutableList.of(LOCK_FAILURE_USER_MESSAGE);
      70             :     }
      71           2 :     if (isInvalidProjectConfig(throwable)) {
      72           1 :       return ImmutableList.of(
      73           1 :           getInvalidConfigMessage(throwable).orElse(INVALID_PROJECT_CONFIG_USER_MESSAGE)
      74             :               + "\n"
      75             :               + CONTACT_PROJECT_OWNER_USER_MESSAGE);
      76             :     }
      77           1 :     if (throwable instanceof MergeUpdateException) {
      78           0 :       return ImmutableList.of(throwable.getMessage());
      79             :     }
      80           1 :     return ImmutableList.of();
      81             :   }
      82             : 
      83             :   @Override
      84             :   public Optional<Status> getStatus(Throwable throwable) {
      85           5 :     if (isLockFailure(throwable)) {
      86           0 :       return Optional.of(Status.create(503, "Lock failure"));
      87             :     }
      88           5 :     if (isInvalidProjectConfig(throwable)) {
      89           1 :       return Optional.of(Status.create(409, "Conflict"));
      90             :     }
      91           4 :     return Optional.empty();
      92             :   }
      93             : 
      94             :   private static boolean isLockFailure(Throwable throwable) {
      95          39 :     return isMatching(throwable, t -> t instanceof LockFailureException);
      96             :   }
      97             : 
      98             :   private static boolean isMissingObjectException(Throwable throwable) {
      99          21 :     return isMatching(throwable, t -> t instanceof MissingObjectException);
     100             :   }
     101             : 
     102             :   private static boolean isInvalidProjectConfig(Throwable throwable) {
     103          21 :     return isMatching(
     104             :         throwable,
     105             :         t ->
     106          21 :             t instanceof InvalidConfigFileException
     107          21 :                 && ProjectConfig.PROJECT_CONFIG.equals(
     108           1 :                     ((InvalidConfigFileException) t).getFileName()));
     109             :   }
     110             : 
     111             :   private Optional<String> getInvalidConfigMessage(Throwable throwable) {
     112           1 :     return Throwables.getCausalChain(throwable).stream()
     113           1 :         .filter(InvalidConfigFileException.class::isInstance)
     114           1 :         .map(ex -> ex.getMessage())
     115           1 :         .findFirst();
     116             :   }
     117             : 
     118             :   /**
     119             :    * Check whether the given exception or any of its causes matches the given predicate.
     120             :    *
     121             :    * @param throwable Exception that should be tested
     122             :    * @param predicate predicate to check if a throwable matches
     123             :    * @return {@code true} if the given exception or any of its causes matches the given predicate
     124             :    */
     125             :   private static boolean isMatching(Throwable throwable, Predicate<Throwable> predicate) {
     126          39 :     return Throwables.getCausalChain(throwable).stream().anyMatch(predicate);
     127             :   }
     128             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750