private Callback<String> idConverterCallback(BlobInfo blobInfo, String blobId) {
      return buildCallback(frontendMetrics.putIdConversionMetrics, convertedBlobId -> {
        restResponseChannel.setHeader(RestUtils.Headers.LOCATION, convertedBlobId);
        if (blobInfo.getBlobProperties().getTimeToLiveInSeconds() == Utils.Infinite_Time) {
          // Do ttl update with retryExecutor. Use the blob ID returned from the router instead of the converted ID
          // since the converted ID may be changed by the ID converter.
          String serviceId = blobInfo.getBlobProperties().getServiceId();
          retryExecutor.runWithRetries(retryPolicy,
              callback -> router.updateBlobTtl(null, blobId, serviceId, Utils.Infinite_Time, callback,
                  QuotaUtils.buildQuotaChargeCallback(restRequest, quotaManager, false)), this::isRetriable,
              routerTtlUpdateCallbackForStitch(blobInfo, blobId));
        } else {
          if (RestUtils.isDatasetVersionQueryEnabled(restRequest.getArgs())) {
            //Make sure to process response after delete finished
            updateVersionStateAndDeleteDatasetVersionOutOfRetentionCount(
                deleteDatasetVersionOutOfRetentionCallback(blobInfo));
          } else {
            securityService.processResponse(restRequest, restResponseChannel, blobInfo,
                securityProcessResponseCallback());
          }
        }
      }, uri, LOGGER, deleteDatasetCallback);
    }
After {@link IdConverter#convert} finishes, call {@link SecurityService#postProcessRequest} to perform
request time security checks that rely on the request being fully parsed and any additional arguments set.
@param blobInfo the {@link BlobInfo} to use for security checks.
@return a {@link Callback} to be used with {@link IdConverter#convert}.
ambry-frontend/src/main/java/com/github/ambry/frontend/NamedBlobPutHandler.java