MinioChatAttachmentStorage.java
package com.ecommerce.chat.infrastructure.storage;
import com.ecommerce.chat.application.exception.ChatError;
import com.ecommerce.chat.application.exception.ChatException;
import com.ecommerce.chat.application.port.ChatAttachmentStorage;
import io.minio.CopyObjectArgs;
import io.minio.GetObjectArgs;
import io.minio.GetObjectResponse;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.Http.Method;
import io.minio.MinioClient;
import io.minio.RemoveObjectArgs;
import io.minio.SourceObject;
import io.minio.StatObjectArgs;
import io.minio.StatObjectResponse;
import io.minio.errors.ErrorResponseException;
import org.springframework.stereotype.Component;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.security.MessageDigest;
import java.time.Duration;
import java.util.HexFormat;
@Component
public class MinioChatAttachmentStorage implements ChatAttachmentStorage {
private final MinioClient minioClient;
public MinioChatAttachmentStorage(MinioClient minioClient) {
this.minioClient = minioClient;
}
@Override
public String createUploadUrl(String bucket, String objectKey, Duration expiry) {
return presignedUrl(Method.PUT, bucket, objectKey, expiry);
}
@Override
public String createDownloadUrl(String bucket, String objectKey, Duration expiry) {
return presignedUrl(Method.GET, bucket, objectKey, expiry);
}
@Override
public void copyIfUnchanged(
String bucket,
String sourceObjectKey,
String targetObjectKey,
String expectedEntityTag) {
try {
minioClient.copyObject(CopyObjectArgs.builder()
.bucket(bucket)
.object(targetObjectKey)
.source(SourceObject.builder()
.bucket(bucket)
.object(sourceObjectKey)
.matchETag(expectedEntityTag)
.build())
.build());
} catch (ErrorResponseException exception) {
throw storageException(exception);
} catch (Exception exception) {
throw new ChatException(ChatError.ATTACHMENT_STORAGE_UNAVAILABLE, exception);
}
}
@Override
public StoredObject inspect(
String bucket,
String objectKey,
int prefixLength,
long maximumBytes) {
try {
StatObjectResponse stat = minioClient.statObject(StatObjectArgs.builder()
.bucket(bucket)
.object(objectKey)
.build());
if (stat.size() <= 0 || stat.size() > maximumBytes) {
return new StoredObject(
stat.size(), stat.contentType(), new byte[0], null, stat.etag());
}
MessageDigest digest = MessageDigest.getInstance("SHA-256");
ByteArrayOutputStream prefix = new ByteArrayOutputStream(prefixLength);
long totalBytes = 0;
try (GetObjectResponse response = minioClient.getObject(GetObjectArgs.builder()
.bucket(bucket)
.object(objectKey)
.matchETag(stat.etag())
.build())) {
byte[] buffer = new byte[8192];
int read;
while ((read = response.read(buffer)) != -1) {
digest.update(buffer, 0, read);
int prefixRemaining = prefixLength - prefix.size();
if (prefixRemaining > 0) {
prefix.write(buffer, 0, Math.min(prefixRemaining, read));
}
totalBytes += read;
if (totalBytes > maximumBytes) {
return new StoredObject(
totalBytes, stat.contentType(), new byte[0], null, stat.etag());
}
}
}
if (totalBytes != stat.size()) {
throw new IllegalStateException("Attachment object length changed during inspection");
}
return new StoredObject(
stat.size(),
stat.contentType(),
prefix.toByteArray(),
HexFormat.of().formatHex(digest.digest()),
stat.etag());
} catch (ErrorResponseException exception) {
throw storageException(exception);
} catch (Exception exception) {
throw new ChatException(ChatError.ATTACHMENT_STORAGE_UNAVAILABLE, exception);
}
}
@Override
public InputStream open(String bucket, String objectKey) {
try {
return minioClient.getObject(GetObjectArgs.builder()
.bucket(bucket)
.object(objectKey)
.build());
} catch (ErrorResponseException exception) {
throw storageException(exception);
} catch (Exception exception) {
throw new ChatException(ChatError.ATTACHMENT_STORAGE_UNAVAILABLE, exception);
}
}
@Override
public void remove(String bucket, String objectKey) {
try {
minioClient.removeObject(RemoveObjectArgs.builder()
.bucket(bucket)
.object(objectKey)
.build());
} catch (ErrorResponseException exception) {
String code = exception.errorResponse().code();
if (!"NoSuchKey".equals(code) && !"NoSuchObject".equals(code)) {
throw new ChatException(ChatError.ATTACHMENT_STORAGE_UNAVAILABLE, exception);
}
} catch (Exception exception) {
throw new ChatException(ChatError.ATTACHMENT_STORAGE_UNAVAILABLE, exception);
}
}
private String presignedUrl(Method method, String bucket, String objectKey, Duration expiry) {
try {
return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.method(method)
.bucket(bucket)
.object(objectKey)
.expiry(Math.toIntExact(expiry.toSeconds()))
.build());
} catch (Exception exception) {
throw new ChatException(ChatError.ATTACHMENT_STORAGE_UNAVAILABLE, exception);
}
}
private ChatException storageException(ErrorResponseException exception) {
String code = exception.errorResponse().code();
if ("NoSuchKey".equals(code) || "NoSuchObject".equals(code)) {
return new ChatException(ChatError.ATTACHMENT_OBJECT_MISSING, exception);
}
if ("PreconditionFailed".equals(code)) {
return new ChatException(ChatError.ATTACHMENT_OBJECT_MISMATCH, exception);
}
return new ChatException(ChatError.ATTACHMENT_STORAGE_UNAVAILABLE, exception);
}
}