IndexValue markAsPermanent(StoreKey id, FileSpan fileSpan, MessageInfo info, long operationTimeMs, short lifeVersion)
      throws StoreException {
    validateFileSpan(fileSpan, true);
    boolean hasLifeVersion = IndexValue.hasLifeVersion(lifeVersion);
    IndexValue value = findKey(id);
    if (value == null && info == null) {
      throw new StoreException("Id " + id + " not present in index " + dataDir, StoreErrorCodes.IDNotFound);
    }
    short retrievedLifeVersion = value == null ? info.getLifeVersion() : value.getLifeVersion();
    if (value != null && value.isDelete()) {
      throw new StoreException("Id " + id + " deleted in index " + dataDir, StoreErrorCodes.IDDeleted);
    } else if (value != null && value.isTtlUpdate()) {
      throw new StoreException("TTL of " + id + " already updated in index" + dataDir, StoreErrorCodes.AlreadyUpdated);
    } else if (hasLifeVersion && retrievedLifeVersion > lifeVersion) {
      throw new StoreException("LifeVersion conflict in index. Id " + id + " LifeVersion: " + retrievedLifeVersion
          + " Undelete LifeVersion: " + lifeVersion, StoreErrorCodes.LifeVersionConflict);
    }
    long size = fileSpan.getEndOffset().getOffset() - fileSpan.getStartOffset().getOffset();
    IndexValue newValue;

    if (value == null) {
      // It is possible that the PUT has been cleaned by compaction
      // but the TTL update is going to still be placed?
      if (!hasLifeVersion) {
        throw new StoreException("MessageInfo of ttlUpdate carries invalid lifeVersion",
            StoreErrorCodes.InitializationError);
      }
      newValue =
          new IndexValue(size, fileSpan.getStartOffset(), IndexValue.FLAGS_DEFAULT_VALUE, info.getExpirationTimeInMs(),
              info.getOperationTimeMs(), info.getAccountId(), info.getContainerId(), lifeVersion);
      newValue.clearOriginalMessageOffset();
    } else {
      lifeVersion = hasLifeVersion ? lifeVersion : value.getLifeVersion();
      newValue =
          new IndexValue(value.getSize(), value.getOffset(), value.getFlags(), Utils.Infinite_Time, operationTimeMs,
              value.getAccountId(), value.getContainerId(), lifeVersion);
      newValue.setNewOffset(fileSpan.getStartOffset());
      newValue.setNewSize(size);
    }
    newValue.clearFlag(IndexValue.Flags.Undelete_Index);
    newValue.setFlag(IndexValue.Flags.Ttl_Update_Index);
    addToIndex(new IndexEntry(id, newValue, null), fileSpan);
    return newValue;
  }
Marks a blob as permanent
@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 ttlUpdate record.
@return the {@link IndexValue} of the ttl update record
@throws StoreException if there is any problem writing the index record
ambry-store/src/main/java/com/github/ambry/store/PersistentIndex.java