S3FileStorage.java
package com.wilzwert.myjobs.infrastructure.storage;
import com.wilzwert.myjobs.core.domain.model.DownloadableFile;
import com.wilzwert.myjobs.core.domain.model.attachment.AttachmentId;
import com.wilzwert.myjobs.core.domain.model.job.JobId;
import com.wilzwert.myjobs.core.domain.shared.ports.driven.FileStorage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import software.amazon.awssdk.core.ResponseInputStream;
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.*;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Optional;
/**
* @author Wilhelm Zwertvaegher
*/
@Slf4j
public class S3FileStorage implements FileStorage {
private final S3Client s3Client;
private final S3Presigner s3Presigner;
private final String bucketName;
private final SecureTempFileHelper secureTempFileHelper;
public S3FileStorage(S3Client s3Client, S3Presigner s3Presigner, String bucketName, SecureTempFileHelper secureTempFileHelper) {
this.s3Client = s3Client;
this.s3Presigner = s3Presigner;
this.bucketName = bucketName;
this.secureTempFileHelper = secureTempFileHelper;
}
@Override
public DownloadableFile store(File file, String targetFilename, String originalFilename) {
log.debug("Putting file to S3 bucket {}", bucketName);
try {
s3Client.putObject(PutObjectRequest.builder()
.bucket(bucketName)
.key(targetFilename)
.contentDisposition("attachment; filename=\"" + originalFilename + "\"")
.build(),
RequestBody.fromFile(file));
}
catch (Exception e) {
log.error("Unable to put file to S3 Bucket {}", bucketName, e);
throw new StorageException("Failed to store file in S3 bucket", e);
}
// use the targetfilename as key and fileId
return new DownloadableFile(targetFilename, targetFilename, getContentType(originalFilename), originalFilename);
}
@Override
public void delete(String fileId) {
s3Client.deleteObject(DeleteObjectRequest.builder()
.bucket(bucketName)
.key(fileId)
.build());
}
@Override
public DownloadableFile retrieve(String fileId, String originalFilename) {
try {
ResponseInputStream<GetObjectResponse> responseInputStream = s3Client.getObject(GetObjectRequest.builder()
.bucket(bucketName)
.key(fileId)
.build());
File tempFile = Files.createTempFile("temp", "s3", secureTempFileHelper.getFileAttribute()).toFile();
Files.write(Path.of(tempFile.getPath()), responseInputStream.readAllBytes());
// for now, target path and fileId are the same
return new DownloadableFile(fileId, tempFile.getPath(), getContentType(originalFilename), originalFilename);
}
catch (SdkClientException e) {
throw new StorageException("Unable to retrieve file from S3 bucket", e);
}
catch (IOException e) {
throw new StorageException("Unable to store file downloaded from S3 bucket", e);
}
}
@Override
public String generateProtectedUrl(JobId jobId, AttachmentId attachmentId, String fileId) {
GetObjectRequest getRequest = GetObjectRequest.builder()
.bucket(bucketName)
.key(fileId)
.build();
GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder()
.signatureDuration(Duration.ofMinutes(30))
.getObjectRequest(getRequest)
.build();
URL presignedUrl = s3Presigner.presignGetObject(presignRequest).url();
return presignedUrl.toString();
}
public String getContentType(String originalFilename) {
// get file MIME type
String contentType = null;
Optional<MediaType> mimeTypeOptional = MediaTypeFactory.getMediaType(originalFilename);
if(mimeTypeOptional.isPresent()) {
contentType = mimeTypeOptional.get().toString();
}
if (contentType == null) {
contentType = "application/octet-stream"; // default value
}
return contentType;
}
}