IndexValue markAsUndeleted(StoreKey id, FileSpan fileSpan, MessageInfo info, long operationTimeMs, short lifeVersion)
      throws StoreException {
    boolean hasLifeVersion = IndexValue.hasLifeVersion(lifeVersion);
    validateFileSpan(fileSpan, true);
    long size = fileSpan.getEndOffset().getOffset() - fileSpan.getStartOffset().getOffset();
    List<IndexValue> values =
        findAllIndexValuesForKey(id, null, EnumSet.allOf(IndexEntryType.class), validIndexSegments);
    IndexValue newValue;
    if (info != null) {
      // This is from recovery. In recovery, we don't need to do any sanity check because
      // 1. we already know the IndexValue has it's source in the log.
      // 2. some sanity check will fail.
      //    assume we have P, D, U, D in the log, then a compaction cycle compacted P and first D,
      //    then we only have U and second D. U in this case, will have no prior records.
      if (!hasLifeVersion) {
        throw new StoreException("MessageInfo of undelete carries invalid lifeVersion " + lifeVersion,
            StoreErrorCodes.InitializationError);
      }
      newValue =
          new IndexValue(size, fileSpan.getStartOffset(), IndexValue.FLAGS_DEFAULT_VALUE, info.getExpirationTimeInMs(),
              info.getOperationTimeMs(), info.getAccountId(), info.getContainerId(), lifeVersion);
      if (info.isTtlUpdated() || (values != null && values.get(0).isTtlUpdate())) {
        newValue.setFlag(IndexValue.Flags.Ttl_Update_Index);
      }
    } else {
      validateSanityForUndelete(id, values, lifeVersion);
      IndexValue value = values.get(0);
      lifeVersion = hasLifeVersion ? lifeVersion : (short) (value.getLifeVersion() + 1);
      newValue =
          new IndexValue(value.getSize(), value.getOffset(), value.getFlags(), value.getExpiresAtMs(), operationTimeMs,
              value.getAccountId(), value.getContainerId(), lifeVersion);
      newValue.setNewOffset(fileSpan.getStartOffset());
      newValue.setNewSize(size);
    }
    newValue.setFlag(IndexValue.Flags.Undelete_Index);
    newValue.clearFlag(IndexValue.Flags.Delete_Index);
    newValue.clearOriginalMessageOffset();
    addToIndex(new IndexEntry(id, newValue, null), fileSpan);
    return newValue;
  }
Marks a blob as undeleted
@param id the {@link StoreKey} of the blob
@param fileSpan the file span represented by this entry in the log
@param operationTimeMs the time of the update operation
@param info this needs to be non-null in the case of recovery. Can be {@code null} otherwise. Used if the PUT
            record could not be found
@param lifeVersion lifeVersion of this undelete record.
@return the {@link IndexValue} of the undelete record
@throws StoreException if there is any problem writing the index record
ambry-store/src/main/java/com/github/ambry/store/PersistentIndex.java