private void encryptionCallback(EncryptJob.EncryptJobResult result, Exception exception) {
      logger.trace("{}: Processing encrypt job callback for chunk at index {}", loggingContext, chunkIndex);

      if (exception == null && !verifyCRC()) {
        // Original content is corruption. Complete the operation
        logger.error("CRC of the chunk {} is different before and after encryption", chunkBlobId);
        releaseBlobContent();
        if (result != null) {
          result.release();
        }
        setOperationExceptionAndComplete(
            new RouterException("CRC of chunk {} is different before and after encryption" + chunkBlobId, exception,
                RouterErrorCode.BlobCorrupted));
        routerMetrics.encryptTimeMs.update(time.milliseconds() - chunkEncryptReadyAtMs);
        routerCallback.onPollReady();
        return;
      }

      if (!isMetadataChunk()) {
        // If this is a data blob, then release the content with or without exception.
        // When there is no exception, then the encrypted data will be used.
        // When there is an exception, then PutOperation will be terminated, and there is no use for this blob anymore.
        releaseBlobContent();
      }
      encryptJobMetricsTracker.onJobResultProcessingStart();
      if (exception == null && !isOperationComplete()) {
        if (!isMetadataChunk()) {
          buf = result.getEncryptedBlobContent();
          if (routerConfig.routerVerifyCrcForPutRequests) {
            for (ByteBuffer byteBuffer : buf.nioBuffers()) {
              chunkCrc32.update(byteBuffer);
            }
          }
        }
        encryptedPerBlobKey = result.getEncryptedKey();
        chunkUserMetadata = result.getEncryptedUserMetadata().array();
        logger.trace("{}: Completing encrypt job result for chunk at index {}", loggingContext, chunkIndex);
        prepareForSending();
        chunkReadyAtMs = time.milliseconds();
      } else {
        encryptJobMetricsTracker.incrementOperationError();
        if (!isOperationComplete()) {
          logger.trace("{}: Setting exception from encrypt of chunk at index {} ", loggingContext, chunkIndex,
              exception);
          // If we are here, then the result is null. no need to release it.
          setOperationExceptionAndComplete(
              new RouterException("Exception thrown on encrypting the content for chunk at index " + chunkIndex,
                  exception, RouterErrorCode.UnexpectedInternalError));
        } else {
          logger.trace(
              "{}: Ignoring exception from encrypt job for chunk at index {} as operation exception {} is set already",
              loggingContext, chunkIndex, getOperationException(), exception);
          // If we are here, then the operation is completed and the exception could be null, in this case,
          // we have to release the content in the result.
          if (result != null) {
            result.release();
          }
        }
      }
      routerMetrics.encryptTimeMs.update(time.milliseconds() - chunkEncryptReadyAtMs);
      encryptJobMetricsTracker.onJobResultProcessingComplete();
      routerCallback.onPollReady();
      // double check if the operation is not completed. If so, we have to release the buf here, since in
      // main thread, chunk might already be released.
      if (isOperationComplete()) {
        logger.info("{}: Release blob content for put chunk in encryption callback since operation is completed",
            loggingContext);
        releaseBlobContent();
      }
    }
The callback method to handle the result and exception from an encryption job.
@param result The result of Encryption job.
@param exception The exception of Encryption job.
ambry-router/src/main/java/com/github/ambry/router/PutOperation.java