/**
   * Returns a request that has the content as input stream wrapped with a cipher, and configured
   * with some meta data and user metadata.
   */
  protected final PutObjectRequest wrapWithCipher(
      PutObjectRequest request, ContentCryptoMaterial cekMaterial) {
    // Create a new metadata object if there is no metadata already.
    ObjectMetadata metadata = request.getMetadata();
    if (metadata == null) {
      metadata = new ObjectMetadata();
    }

    // Record the original Content MD5, if present, for the unencrypted data
    if (metadata.getContentMD5() != null) {
      metadata.addUserMetadata(Headers.UNENCRYPTED_CONTENT_MD5, metadata.getContentMD5());
    }

    // Removes the original content MD5 if present from the meta data.
    metadata.setContentMD5(null);

    // Record the original, unencrypted content-length so it can be accessed
    // later
    final long plaintextLength = plaintextLength(request, metadata);
    if (plaintextLength >= 0) {
      metadata.addUserMetadata(Headers.UNENCRYPTED_CONTENT_LENGTH, Long.toString(plaintextLength));
      // Put the ciphertext length in the metadata
      metadata.setContentLength(ciphertextLength(plaintextLength));
    }
    request.setMetadata(metadata);
    request.setInputStream(newS3CipherLiteInputStream(request, cekMaterial, plaintextLength));
    // Treat all encryption requests as input stream upload requests, not as
    // file upload requests.
    request.setFile(null);
    return request;
  }
 /**
  * Updates put request to store the specified instruction object in S3.
  *
  * @param request The put request for the original object to be stored in S3.
  * @param cekMaterial The instruction object to be stored in S3.
  * @return A put request to store the specified instruction object in S3.
  */
 protected final PutObjectRequest upateInstructionPutRequest(
     PutObjectRequest request, ContentCryptoMaterial cekMaterial) {
   byte[] bytes = cekMaterial.toJsonString().getBytes(UTF8);
   InputStream is = new ByteArrayInputStream(bytes);
   ObjectMetadata metadata = request.getMetadata();
   if (metadata == null) {
     metadata = new ObjectMetadata();
     request.setMetadata(metadata);
   }
   // Set the content-length of the upload
   metadata.setContentLength(bytes.length);
   // Set the crypto instruction file header
   metadata.addUserMetadata(Headers.CRYPTO_INSTRUCTION_FILE, "");
   // Update the instruction request
   request.setKey(request.getKey() + INSTRUCTION_SUFFIX);
   request.setMetadata(metadata);
   request.setInputStream(is);
   request.setFile(null);
   return request;
 }
  /**
   * Returns an updated request where the input stream contains the encrypted object contents. The
   * specified instruction will be used to encrypt data.
   *
   * @param request The request whose contents are to be encrypted.
   * @param instruction The instruction that will be used to encrypt the object data.
   * @return The updated request where the input stream contains the encrypted contents.
   * @deprecated no longer used and will be removed in the future
   */
  @Deprecated
  public static PutObjectRequest encryptRequestUsingInstruction(
      PutObjectRequest request, EncryptionInstruction instruction) {
    // Create a new metadata object if there is no metadata already.
    ObjectMetadata metadata = request.getMetadata();
    if (metadata == null) {
      metadata = new ObjectMetadata();
    }

    // Record the original Content MD5, if present, for the unencrypted data
    if (metadata.getContentMD5() != null) {
      metadata.addUserMetadata(Headers.UNENCRYPTED_CONTENT_MD5, metadata.getContentMD5());
    }

    // Removes the original content MD5 if present from the meta data.
    metadata.setContentMD5(null);

    // Record the original, unencrypted content-length so it can be accessed later
    final long plaintextLength = getUnencryptedContentLength(request, metadata);
    if (plaintextLength >= 0) {
      metadata.addUserMetadata(Headers.UNENCRYPTED_CONTENT_LENGTH, Long.toString(plaintextLength));
    }

    // Put the calculated length of the encrypted contents in the metadata
    long cryptoContentLength =
        calculateCryptoContentLength(instruction.getSymmetricCipher(), request, metadata);
    if (cryptoContentLength >= 0) {
      metadata.setContentLength(cryptoContentLength);
    }

    request.setMetadata(metadata);

    // Create encrypted input stream
    request.setInputStream(
        getEncryptedInputStream(request, instruction.getCipherFactory(), plaintextLength));

    // Treat all encryption requests as input stream upload requests, not as file upload requests.
    request.setFile(null);

    return request;
  }
  /**
   * Creates a put request to store the specified instruction object in S3.
   *
   * @param request The put request for the original object to be stored in S3.
   * @param instruction The instruction object to be stored in S3.
   * @return A put request to store the specified instruction object in S3.
   * @deprecated no longer used and will be removed in the future
   */
  @Deprecated
  public static PutObjectRequest createInstructionPutRequest(
      PutObjectRequest request, EncryptionInstruction instruction) {
    JSONObject instructionJSON = convertInstructionToJSONObject(instruction);
    byte[] instructionBytes = instructionJSON.toString().getBytes(StringUtils.UTF8);
    InputStream instructionInputStream = new ByteArrayInputStream(instructionBytes);

    ObjectMetadata metadata = request.getMetadata();

    // Set the content-length of the upload
    metadata.setContentLength(instructionBytes.length);

    // Set the crypto instruction file header
    metadata.addUserMetadata(Headers.CRYPTO_INSTRUCTION_FILE, "");

    // Update the instruction request
    request.setKey(request.getKey() + INSTRUCTION_SUFFIX);
    request.setMetadata(metadata);
    request.setInputStream(instructionInputStream);

    return request;
  }