@Override
    protected void maybeProcessCallbacks() {
      if (!isOperationComplete() && progressTracker.isCryptoJobRequired()
          && decryptCallbackResultInfo.decryptJobComplete) {
        decryptJobMetricsTracker.onJobResultProcessingStart();
        if (decryptCallbackResultInfo.exception != null) {
          decryptJobMetricsTracker.incrementOperationError();
          logger.trace("Decryption job callback invoked with exception for {} blob {} ", blobType, blobId,
              decryptCallbackResultInfo.exception);
          setOperationException(
              new RouterException("Exception thrown on decrypting content for " + blobType + " blob " + blobId,
                  decryptCallbackResultInfo.exception, RouterErrorCode.UnexpectedInternalError));
          progressTracker.setCryptoJobFailed();
        } else {
          // in case of Metadata blob, only user-metadata needs decryption if the blob is encrypted
          if (blobType == BlobType.MetadataBlob) {
            logger.trace("Processing stored decryption callback result for Metadata blob {}", blobId);
            initializeDataChunks();
            blobInfo = new BlobInfo(serverBlobProperties,
                decryptCallbackResultInfo.result.get().getDecryptedUserMetadata().array(), lifeVersion);
            progressTracker.setCryptoJobSuccess();
            logger.trace("BlobContent available to process for Metadata blob {}", blobId);
          } else {
            logger.trace("Processing stored decryption callback result for simple blob {}", blobId);
            // In case of simple blobs, user-metadata may or may not be passed into decryption job based on GetOptions flag.
            // Only in-case of GetBlobInfo and GetBlobAll, user-metadata is required to be decrypted
            DecryptJob.DecryptJobResult result = decryptCallbackResultInfo.result.getAndSet(null);
            if (result != null) {
              if (result.getDecryptedUserMetadata() != null) {
                blobInfo = new BlobInfo(serverBlobProperties, result.getDecryptedUserMetadata().array(), lifeVersion);
              }
              ByteBuf decryptedBlobContent = result.getDecryptedBlobContent();
              routerMetrics.getFirstDataChunkLatencyMs.update(System.currentTimeMillis() - submissionTimeMs);
              routerMetrics.getDataChunkLatencyMs.update(System.currentTimeMillis() - initializedTimeMs);
              ByteBuf decompressedContent = decompressContent(decryptedBlobContent);
              if (decompressedContent == null) {
                progressTracker.setCryptoJobFailed();
              } else {
                totalSize = decompressedContent.readableBytes();
                if (!resolveRange(totalSize)) {
                  chunkIndexToBuf.put(0, filterChunkToRange(decompressedContent));
                  numChunksRetrieved.set(1);
                  progressTracker.setCryptoJobSuccess();
                  logger.trace("BlobContent available to process for simple blob {}", blobId);
                } else {
                  ReferenceCountUtil.safeRelease(decompressedContent);
                  progressTracker.setCryptoJobFailed();
                }
              }
            } else {
              // only in maybeReleaseDecryptionResultBuffer() will result be set to null, this means the bytebuf has been
              // released, don't have to release it again here.
            }
          }
        }
        decryptJobMetricsTracker.onJobResultProcessingComplete();
      }
    }
Return the {@link MessageFormatFlags} to associate with the first getBlob chunk operation.
@return {@link MessageFormatFlags#Blob} for {@link GetBlobOptions.OperationType#Data}, or
        {@link MessageFormatFlags#All} by default.
ambry-router/src/main/java/com/github/ambry/router/GetBlobOperation.java