-1

I am aware of the TransferManager and the .uploadFileList() and .uploadFileDirectory() methods, however they accept java.io.File types as arguments. I have a collection of byte array input streams containing jpeg image data. I don't want to create in-memory files to store this data before I upload it either.

So what I need is essentially what the S3 client's PutObjectRequest does but for a collection of InputStream objects. Also, if one upload fails, I want to abort the whole thing and not upload anything, much like how a database transaction will reverse the changes if something goes wrong along the way.

Is this possible with the Java SDK?

Nalyd
  • 95
  • 1
  • 16

1 Answers1

0

Before I share an answer, please consider upgrading...

fyi - TransferManager is deprecated, now supported as TransferManagerBuilder in JAVA AWS SDK, please consider upgrading if TransferManagerBuilder Object suits your needs.


now since you asked about TransferManager, you could either 1) copy the code below and replace the functionality/arguments with your custom in memory handling of the input stream and handle it in your custom function... or; 2) further below is another sample, try to use this as-is...


  1. Github source modify with with inputstream and issue listed here
private def uploadFile(is: InputStream, s3ObjectName: String, metadata: ObjectMetadata) = {
    try {
      val putObjectRequest = new PutObjectRequest(bucketName, s3ObjectName,
        is, metadata)
      // TransferManager supports asynchronous uploads and downloads
      val upload = transferManager.upload(putObjectRequest)
      upload.addProgressListener(ExceptionReporter.wrap(UploadProgressListener(putObjectRequest)))
    } catch {
      case e: Exception => throw new RuntimeException(e)
    }
  }

  1. Bonus, Nice custom answer here using sequence input streams
public void combineFiles() {
    List<String> files = getFiles();
    long totalFileSize = files.stream()
                               .map(this::getContentLength)
                               .reduce(0L, (f, s) -> f + s);

    try {
        try (InputStream partialFile = new SequenceInputStream(getInputStreamEnumeration(files))) {
            ObjectMetadata resultFileMetadata = new ObjectMetadata();
            resultFileMetadata.setContentLength(totalFileSize);
            s3Client.putObject("bucketName", "resultFilePath", partialFile, resultFileMetadata);
        }
    } catch (IOException e) {
        LOG.error("An error occurred while combining files. {}", e);
    }
}

private Enumeration<? extends InputStream> getInputStreamEnumeration(List<String> files) {
    return new Enumeration<InputStream>() {
        private Iterator<String> fileNamesIterator = files.iterator();

        @Override
        public boolean hasMoreElements() {
            return fileNamesIterator.hasNext();
        }

        @Override
        public InputStream nextElement() {
            try {
                return new FileInputStream(Paths.get(fileNamesIterator.next()).toFile());
            } catch (FileNotFoundException e) {
                System.err.println(e.getMessage());
                throw new RuntimeException(e);
            }
        }

    };
}

Transformer
  • 6,963
  • 2
  • 26
  • 52
  • Thank you for the answer and taking the time to write that out but it's not really what I asked. I have a collection of ByteArrayInputStreams and I need a more efficient way to batch upload them without creating multiple `.putObject()` requests. Also, the second answer isn't as relevant either as I mentioned I don't want to load the files in memory before the upload which your code seems to rely on. – Nalyd Oct 15 '21 at 12:55
  • Afaik I don't think the SDK provides a way to do this though. – Nalyd Oct 15 '21 at 12:56