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

io.linguarobot.aws.cdk.maven.FileAssetPublisher Maven / Gradle / Ivy

Go to download

The AWS CDK Maven plugin produces and deploys CloudFormation templates based on the cloud infrastructure defined by means of CDK. The goal of the project is to improve the experience of Java developers while working with CDK by eliminating the need for installing Node.js and interacting with the CDK application by means of CDK Toolkit.

There is a newer version: 0.0.8
Show newest version
package io.linguarobot.aws.cdk.maven;

import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.core.async.AsyncRequestBody;
import software.amazon.awssdk.services.s3.S3AsyncClient;
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.UploadPartRequest;

import javax.annotation.Nullable;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * Publishes file assets to S3.
 */
public class FileAssetPublisher {

    private static final Logger logger = LoggerFactory.getLogger(FileAssetPublisher.class);

    private final ResolvedEnvironment environment;

    private S3AsyncClient s3Client;

    public FileAssetPublisher(ResolvedEnvironment environment) {
        this.environment = environment;
    }

    /**
     * Uploads a file or a directory (zipping it before uploading) to S3 bucket.
     *
     * @param file the file or directory to be uploaded
     * @param objectName the name of the object in the bucket
     * @param bucketName the name of the bucket
     * @throws IOException if I/O error occurs while uploading a file or directory
     */
    public void publish(Path file, String objectName, String bucketName) throws IOException {
        logger.info("Publishing file asset, file={}, bucketName={}, objectName={}", file, bucketName, objectName);
        if (Files.isDirectory(file)) {
            publishDirectory(file, objectName, bucketName);
        } else {
            publishFile(file, objectName, bucketName);
        }
    }

    /**
     * Zips the directory and uploads it to S3 bucket.
     */
    private void publishDirectory(Path directory, String objectName, String bucketName) throws IOException {
        try (
                OutputStream outputStream = new S3ObjectOutputStream(getS3Client(), bucketName, objectName, "application/zip");
                ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)
        ) {
            Files.walkFileTree(directory, new SimpleFileVisitor() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    ZipEntry zipEntry = new ZipEntry(directory.relativize(file).toString());
                    zipOutputStream.putNextEntry(zipEntry);
                    Files.copy(file, zipOutputStream);
                    zipOutputStream.closeEntry();
                    return FileVisitResult.CONTINUE;
                }
            });
        }
    }

    /**
     * Uploads the file to S3 bucket.
     */
    private void publishFile(Path file, String objectName, String bucketName) throws IOException {
        try (OutputStream outputStream = new S3ObjectOutputStream(getS3Client(), bucketName, objectName)) {
            Files.copy(file, outputStream);
        }
    }

    private S3AsyncClient getS3Client() {
        if (this.s3Client == null) {
            this.s3Client = S3AsyncClient.builder()
                    .region(environment.getRegion())
                    .credentialsProvider(environment.getCredentialsProvider())
                    .build();
        }

        return s3Client;
    }

    private static class S3ObjectOutputStream extends OutputStream {

        private static final int MINIMUM_PART_SIZE = 5 * 1024 * 1024;

        private S3AsyncClient s3Client;
        private CreateMultipartUploadResponse createUploadResponse;
        private List> parts;
        private ByteBuffer buffer;

        S3ObjectOutputStream(S3AsyncClient s3Client, String bucketName, String objectKey) {
            this(s3Client, bucketName, objectKey, null);
        }

        S3ObjectOutputStream(S3AsyncClient s3Client, String bucketName, String objectKey, @Nullable String contentType) {
            this(s3Client, bucketName, objectKey, contentType, MINIMUM_PART_SIZE);
        }

        S3ObjectOutputStream(S3AsyncClient s3Client, String bucketName, String objectKey, String contentType, int partSize) {
            if (partSize <= 0) {
                throw new IllegalArgumentException("The minimum part size is 5 MB (" + MINIMUM_PART_SIZE + " bytes)");
            }
            this.s3Client = s3Client;
            this.buffer = ByteBuffer.allocate(partSize);
            this.parts = new ArrayList<>();
            CreateMultipartUploadRequest uploadRequest = buildUploadRequest(bucketName, objectKey, contentType);
            this.createUploadResponse = s3Client.createMultipartUpload(uploadRequest).join();
        }

        @Override
        public void write(int b) throws IOException {
            if (isClosed()) {
                throw new IOException("The stream is closed");
            }

            if (!buffer.hasRemaining()) {
                flush();
            }

            buffer.put((byte) b);
        }

        @Override
        public void write(@NotNull byte[] bytes, int offset, int length) throws IOException {
            if (isClosed()) {
                throw new IOException("The stream is closed");
            }
            int remaining = buffer.remaining();
            buffer.put(bytes, offset, Math.min(remaining, length));
            if (remaining < length) {
                flush();
                write(bytes, offset + remaining, length - remaining);
            }
        }

        @Override
        public void flush() {
            if (!isClosed()) {
                buffer.flip();
                if (buffer.remaining() > 0) {
                    CompletableFuture part = CompletableFuture.completedFuture(parts.size() + 1)
                            .thenApply(this::buildUploadPartRequest)
                            .thenCompose(uploadPartRequest -> {
                                AsyncRequestBody requestBody = AsyncRequestBody.fromByteBuffer(buffer);
                                return s3Client.uploadPart(uploadPartRequest, requestBody)
                                        .thenApply(r -> completedPart(r.eTag(), uploadPartRequest.partNumber()));
                            });
                    parts.add(part);
                }
                buffer.clear();
            }
        }

        @Override
        public void close() {
            if (!isClosed()) {
                flush();
                join(this.parts)
                        .thenCompose(completedParts -> {
                            CompleteMultipartUploadRequest completeUploadRequest = buildCompleteUploadRequest(completedParts);
                            return s3Client.completeMultipartUpload(completeUploadRequest);
                        })
                        .join();

                s3Client = null;
                this.createUploadResponse = null;
                buffer = null;
                this.parts = null;
            }

        }

        private  CompletableFuture> join(List> futures) {
            return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
                    .thenApply(r -> futures.stream()
                            .map(CompletableFuture::join)
                            .collect(Collectors.toList()));
        }

        private boolean isClosed() {
            return buffer == null;
        }

        private CreateMultipartUploadRequest buildUploadRequest(String bucketName,
                                                                String objectKey,
                                                                @Nullable String contentType) {

            CreateMultipartUploadRequest.Builder requestBuilder = CreateMultipartUploadRequest.builder()
                    .bucket(bucketName)
                    .key(objectKey);
            if (contentType != null) {
                requestBuilder = requestBuilder.contentType(contentType);
            }

            return requestBuilder.build();
        }

        private UploadPartRequest buildUploadPartRequest(int partNumber) {
            return UploadPartRequest.builder()
                    .bucket(createUploadResponse.bucket())
                    .key(createUploadResponse.key())
                    .uploadId(createUploadResponse.uploadId())
                    .partNumber(partNumber)
                    .build();
        }

        private CompletedPart completedPart(String eTag, int partNumber) {
            return CompletedPart.builder()
                    .eTag(eTag)
                    .partNumber(partNumber)
                    .build();
        }

        private CompleteMultipartUploadRequest buildCompleteUploadRequest(Collection parts) {
            CompletedMultipartUpload completedMultipartUpload = CompletedMultipartUpload.builder()
                    .parts(parts)
                    .build();

            return CompleteMultipartUploadRequest.builder()
                    .bucket(createUploadResponse.bucket())
                    .key(createUploadResponse.key())
                    .uploadId(createUploadResponse.uploadId())
                    .multipartUpload(completedMultipartUpload)
                    .build();
        }

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy