@Override
  public Future<String> stitchBlob(BlobProperties blobProperties, byte[] userMetadata, List<ChunkInfo> chunksToStitch,
      PutBlobOptions options, Callback<String> callback, QuotaChargeCallback quotaChargeCallback) {
    if (blobProperties == null || chunksToStitch == null) {
      throw new IllegalArgumentException("blobProperties or chunksToStitch must not be null");
    }
    if (userMetadata == null) {
      userMetadata = new byte[0];
    }
    currentOperationsCount.incrementAndGet();
    if (blobProperties.isEncrypted()) {
      routerMetrics.stitchEncryptedBlobOperationRate.mark();
    } else {
      routerMetrics.stitchBlobOperationRate.mark();
    }
    routerMetrics.operationQueuingRate.mark();
    FutureResult<String> futureResult = new FutureResult<>();
    if (isOpen.get()) {
      getOperationController().stitchBlob(blobProperties, userMetadata, chunksToStitch, options, futureResult, callback,
          quotaChargeCallback);
    } else {
      RouterException routerException =
          new RouterException("Cannot accept operation because Router is closed", RouterErrorCode.RouterClosed);
      routerMetrics.operationDequeuingRate.mark();
      routerMetrics.onPutBlobError(routerException, blobProperties.isEncrypted(), true);
      completeOperation(futureResult, callback, null, routerException);
    }
    return futureResult;
  }
Requests for a new blob to be replicated on-demand asynchronously and invokes the {@link Callback} when the request completes.
@param blobId The ID of the blob to be replicated.
@param serviceId The service ID of the service replicating the blob. This can be null if unknown.
@param sourceDataNode The source {@link DataNodeId} to get the blob from.
@param callback The {@link Callback} which will be invoked on the completion of the request.
@return A future that would contain information about whether the replicateBlob succeeded or not, eventually.
ambry-router/src/main/java/com/github/ambry/router/NonBlockingRouter.java