void handleResponse(ResponseInfo responseInfo, UndeleteResponse undeleteResponse) {
    UndeleteRequest undeleteRequest = (UndeleteRequest) responseInfo.getRequestInfo().getRequest();
    RequestInfo undeleteRequestInfo = undeleteRequestInfos.remove(undeleteRequest.getCorrelationId());
    // undeleteRequestInfo can be null if this request was timed out before this response is received. No
    // metric is updated here, as corresponding metrics have been updated when the request was timed out.
    if (undeleteRequestInfo == null) {
      return;
    }
    ReplicaId replica = undeleteRequestInfo.getReplicaId();
    if (responseInfo.isQuotaRejected()) {
      processQuotaRejectedResponse(undeleteRequest.getCorrelationId(), replica);
      return;
    }
    // Track the over all time taken for the response since the creation of the request.
    long requestLatencyMs = time.milliseconds() - undeleteRequestInfo.getRequestCreateTime();
    routerMetrics.routerRequestLatencyMs.update(requestLatencyMs);
    routerMetrics.getDataNodeBasedMetrics(replica.getDataNodeId()).undeleteRequestLatencyMs.update(requestLatencyMs);
    // Check the error code from NetworkClient.
    if (responseInfo.getError() != null) {
      LOGGER.trace("UndeleteRequest with response correlationId {} timed out for replica {} ",
          undeleteRequest.getCorrelationId(), replica.getDataNodeId());
      onErrorResponse(replica, new RouterException(
          "Operation timed out because of " + responseInfo.getError() + " at DataNode " + responseInfo.getDataNode(),
          RouterErrorCode.OperationTimedOut));
    } else {
      if (undeleteResponse == null) {
        LOGGER.trace(
            "UndeleteRequest with response correlationId {} received UnexpectedInternalError on response deserialization for replica {} ",
            undeleteRequest.getCorrelationId(), replica.getDataNodeId());
        onErrorResponse(replica, new RouterException("Response deserialization received an unexpected error",
            RouterErrorCode.UnexpectedInternalError));
      } else {
        // The true case below should not really happen. This means a response has been received
        // not for its original request. We will immediately fail this operation.
        if (undeleteResponse.getCorrelationId() != undeleteRequest.getCorrelationId()) {
          LOGGER.error(
              "The correlation id in the DeleteResponse {} is not the same as the correlation id in the associated DeleteRequest: {}",
              undeleteResponse.getCorrelationId(), undeleteRequest.getCorrelationId());
          routerMetrics.unknownReplicaResponseError.inc();
          onErrorResponse(replica,
              new RouterException("Received wrong response that is not for the corresponding request.",
                  RouterErrorCode.UnexpectedInternalError));
        } else {
          ServerErrorCode serverError = undeleteResponse.getError();
          if (serverError == ServerErrorCode.NoError || (serverError == ServerErrorCode.BlobAlreadyUndeleted
              // LIFE_VERSION_FROM_FRONTEND is an invalid lifeVersion
              && undeleteResponse.getLifeVersion() != MessageInfo.LIFE_VERSION_FROM_FRONTEND)) {
            if (RouterUtils.isRemoteReplica(routerConfig, replica)) {
              LOGGER.trace("Cross colo request successful for remote replica {} in {} ", replica.getDataNodeId(),
                  replica.getDataNodeId().getDatacenterName());
              routerMetrics.crossColoSuccessCount.inc();
            }
            if (lifeVersion == null) {
              // This is first successful response.
              lifeVersion = undeleteResponse.getLifeVersion();
              firstResponseReplicaId = replica;
              operationTracker.onResponse(replica, TrackedRequestFinalState.SUCCESS);
            } else {
              if (verifyLifeVersion && lifeVersion != undeleteResponse.getLifeVersion()) {
                String message = "LifeVersion of " + blobId + " from Replica " + firstResponseReplicaId
                    + " is different than the lifeVersion from replica " + replica + " " + lifeVersion + " != "
                    + undeleteResponse.getLifeVersion();
                LOGGER.error(message);
                // this is a successful response and one that completes the operation regardless of whether the
                // success target has been reached or not.
                operationCompleted = true;
                onErrorResponse(replica, new RouterException(message, RouterErrorCode.LifeVersionConflict));
              } else {
                operationTracker.onResponse(replica, TrackedRequestFinalState.SUCCESS);
              }
            }
          } else if (serverError == ServerErrorCode.BlobNotDeleted && !verifyLifeVersion) {
            LOGGER.trace(
                "Replica {} returned Blob_Not_Deleted for an undelete request with correlationId : {}, don't verify life version",
                replica, undeleteRequest.getCorrelationId());
            if (RouterUtils.isRemoteReplica(routerConfig, replica)) {
              LOGGER.trace("Cross colo request successful for remote replica {} in {} ", replica.getDataNodeId(),
                  replica.getDataNodeId().getDatacenterName());
              routerMetrics.crossColoSuccessCount.inc();
            }
            operationTracker.onResponse(replica, TrackedRequestFinalState.SUCCESS);
          } else if (serverError == ServerErrorCode.DiskUnavailable) {
            LOGGER.trace("Replica {} returned Disk_Unavailable for an undelete request with correlationId : {} ",
                replica, undeleteRequest.getCorrelationId());
            operationTracker.onResponse(replica, TrackedRequestFinalState.DISK_DOWN);
            setOperationException(
                new RouterException("Server returned: " + serverError, RouterErrorCode.AmbryUnavailable));
            routerMetrics.routerRequestErrorCount.inc();
            routerMetrics.getDataNodeBasedMetrics(replica.getDataNodeId()).undeleteRequestErrorCount.inc();
          } else {
            LOGGER.trace("Replica {} returned an error {} for an undelete request with response correlationId : {} ",
                replica, serverError, undeleteRequest.getCorrelationId());
            RouterErrorCode routerErrorCode = processServerError(serverError);
            if (serverError == ServerErrorCode.BlobAuthorizationFailure) {
              // this is a successful response and one that completes the operation regardless of whether the
              // success target has been reached or not.
              operationCompleted = true;
            }
            // any server error code that is not equal to ServerErrorCode.No_Error, the onErrorResponse should be invoked
            onErrorResponse(replica, new RouterException("Server returned: " + serverError, routerErrorCode));
          }
        }
      }
    }
    checkAndMaybeComplete();
  }
Handles a response for a Undelete operation. It determines whether the request was successful and updates
operation tracker. For the same operation, it is possible that different {@link ServerErrorCode} are received from
different replicas. These error codes are eventually resolved to a single {@link RouterErrorCode}.
@param responseInfo The {@link ResponseInfo} to be handled.
@param undeleteResponse The {@link UndeleteResponse} associated with this response.
ambry-router/src/main/java/com/github/ambry/router/UndeleteOperation.java