All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sshtools.synergy.s3.S3OutputStream Maven / Gradle / Ivy

package com.sshtools.synergy.s3;

/*-
 * #%L
 * S3 File System
 * %%
 * Copyright (C) 2002 - 2024 JADAPTIVE Limited
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * .
 * #L%
 */

import java.io.ByteArrayInputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.AbortMultipartUploadRequest;
import software.amazon.awssdk.services.s3.model.CompleteMultipartUploadRequest;
import software.amazon.awssdk.services.s3.model.CompletedMultipartUpload;
import software.amazon.awssdk.services.s3.model.CompletedPart;
import software.amazon.awssdk.services.s3.model.CreateMultipartUploadRequest;
import software.amazon.awssdk.services.s3.model.CreateMultipartUploadResponse;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.UploadPartRequest;
import software.amazon.awssdk.services.s3.model.UploadPartResponse;

public class S3OutputStream extends OutputStream {

  /**
   * Default chunk size is 10MB
   */
  protected static final int BUFFER_SIZE = 5 * 1024 * 1024;

  /**
   * The bucket-name on Amazon S3
   */
  private final String bucket;

  /**
   * The path (key) name within the bucket
   */
  private final String path;

  /**
   * The temporary buffer used for storing the chunks
   */
  private final byte[] buf;

  private final S3Client s3Client;
  /**
   * Collection of the etags for the parts that have been uploaded
   */
  private final List etags;
  /**
   * The position in the buffer
   */
  private int position;
  /**
   * The unique id for this upload
   */
  private String uploadId;
  /**
   * indicates whether the stream is still open / valid
   */
  private boolean open;

  /**
   * Creates a new S3 OutputStream
   *
   * @param s3Client the AmazonS3 client
   * @param bucket   name of the bucket
   * @param path     path within the bucket
   */
  public S3OutputStream(S3Client s3Client, String bucket, String path) {
    this.s3Client = s3Client;
    this.bucket = bucket;
    this.path = path;
    buf = new byte[BUFFER_SIZE];
    position = 0;
    etags = new ArrayList<>();
    open = true;
  }

  public void cancel() {
    open = false;
    if (uploadId != null) {
      s3Client.abortMultipartUpload(AbortMultipartUploadRequest.builder()
          .bucket(bucket)
          .key(path)
          .uploadId(uploadId)
          .build());
    }
  }

  @Override
  public void write(int b) {
    assertOpen();
    if (position >= buf.length) {
      flushBufferAndRewind();
    }
    buf[position++] = (byte) b;
  }

  /**
   * Write an array to the S3 output stream.
   *
   * @param b the byte-array to append
   */
  @Override
  public void write(byte[] b) {
    write(b, 0, b.length);
  }

  /**
   * Writes an array to the S3 Output Stream
   *
   * @param byteArray the array to write
   * @param o         the offset into the array
   * @param l         the number of bytes to write
   */
  @Override
  public void write(byte[] byteArray, int o, int l) {
    assertOpen();
    int ofs = o;
    int len = l;
    int size;
    while (len > (size = buf.length - position)) {
      System.arraycopy(byteArray, ofs, buf, position, size);
      position += size;
      flushBufferAndRewind();
      ofs += size;
      len -= size;
    }
    System.arraycopy(byteArray, ofs, buf, position, len);
    position += len;
  }

  /**
   * Flushes the buffer by uploading a part to S3.
   */
  @Override
  public synchronized void flush() {
    assertOpen();
  }

  @Override
  public void close() {
    if (open) {
      open = false;
      if (uploadId != null) {
        if (position > 0) {
          uploadPart();
        }

        CompletedPart[] completedParts = new CompletedPart[etags.size()];
        for (int i = 0; i < etags.size(); i++) {
          completedParts[i] = CompletedPart.builder()
              .eTag(etags.get(i))
              .partNumber(i + 1)
              .build();
        }

        CompletedMultipartUpload completedMultipartUpload = CompletedMultipartUpload.builder()
            .parts(completedParts)
            .build();
        CompleteMultipartUploadRequest completeMultipartUploadRequest = CompleteMultipartUploadRequest.builder()
            .bucket(bucket)
            .key(path)
            .uploadId(uploadId)
            .multipartUpload(completedMultipartUpload)
            .build();
        s3Client.completeMultipartUpload(completeMultipartUploadRequest);
      } else {
        PutObjectRequest putRequest = PutObjectRequest.builder()
                .bucket(bucket)
                .key(path)
                .contentLength((long) position)
            .build();

        RequestBody requestBody = RequestBody.fromInputStream(new ByteArrayInputStream(buf, 0, position),
            position);
        s3Client.putObject(putRequest, requestBody);
      }
    }
  }

  private void assertOpen() {
    if (!open) {
      throw new IllegalStateException("Closed");
    }
  }

  protected void flushBufferAndRewind() {
    if (uploadId == null) {
      CreateMultipartUploadRequest uploadRequest = CreateMultipartUploadRequest.builder()
          .bucket(bucket)
          .key(path)
          .build();
      CreateMultipartUploadResponse multipartUpload = s3Client.createMultipartUpload(uploadRequest);
      uploadId = multipartUpload.uploadId();
    }
    uploadPart();
    position = 0;
  }

  protected void uploadPart() {
    UploadPartRequest uploadRequest = UploadPartRequest.builder()
        .bucket(bucket)
        .key(path)
        .uploadId(uploadId)
        .partNumber(etags.size() + 1)
        .contentLength((long) position)
        .build();
    RequestBody requestBody = RequestBody.fromInputStream(new ByteArrayInputStream(buf, 0, position),
        position);
    UploadPartResponse uploadPartResponse = s3Client.uploadPart(uploadRequest, requestBody);
    etags.add(uploadPartResponse.eTag());
  }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy