Skip to content

Commit

Permalink
fix: resolve file content type based on file extension (#128)
Browse files Browse the repository at this point in the history
Co-authored-by: Maksim_Hadalau <[email protected]>
  • Loading branch information
Maxim-Gadalov and Maksim_Hadalau authored Jan 12, 2024
1 parent 7075561 commit 760f54f
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 2 deletions.
16 changes: 14 additions & 2 deletions src/main/java/com/epam/aidial/core/storage/BlobStorage.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.jclouds.io.ContentMetadata;
import org.jclouds.io.ContentMetadataBuilder;
import org.jclouds.io.payloads.BaseMutableContentMetadata;
import org.jclouds.s3.domain.ObjectMetadataBuilder;

import java.io.Closeable;
import java.util.List;
Expand All @@ -32,6 +33,11 @@
@Slf4j
public class BlobStorage implements Closeable {

// S3 implementation do not return a blob content type without additional head request.
// To avoid additional request for each blob in the listing we try to recognize blob content type by its extension.
// Default value is binary/octet-stream, see org.jclouds.s3.domain.ObjectMetadataBuilder
private static final String DEFAULT_CONTENT_TYPE = ObjectMetadataBuilder.create().build().getContentMetadata().getContentType();

private final BlobStoreContext storeContext;
private final BlobStore blobStore;
private final String bucketName;
Expand Down Expand Up @@ -171,8 +177,14 @@ private static FileMetadataBase buildFileMetadata(ResourceDescription resource,
resource.getBucketLocation(), metadata.getName());

return switch (metadata.getType()) {
case BLOB -> new FileMetadata(resultResource, metadata.getSize(),
((BlobMetadata) metadata).getContentMetadata().getContentType());
case BLOB -> {
String blobContentType = ((BlobMetadata) metadata).getContentMetadata().getContentType();
if (blobContentType != null && blobContentType.equals(DEFAULT_CONTENT_TYPE)) {
blobContentType = BlobStorageUtil.getContentType(metadata.getName());
}

yield new FileMetadata(resultResource, metadata.getSize(), blobContentType);
}
case FOLDER, RELATIVE_PATH -> new FolderMetadata(resultResource);
case CONTAINER -> throw new IllegalArgumentException("Can't list container");
};
Expand Down
66 changes: 66 additions & 0 deletions src/test/java/com/epam/aidial/core/FileApiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,72 @@ public void testListFileWithFolder(Vertx vertx, VertxTestContext context) {
});
}

@Test
public void testListFileWithDefaultContentType(Vertx vertx, VertxTestContext context) {
Checkpoint checkpoint = context.checkpoint(3);
WebClient client = WebClient.create(vertx);

FolderMetadata emptyFolderResponse = new FolderMetadata("7G9WZNcoY26Vy9D7bEgbv6zqbJGfyDp9KZyEbJR4XMZt",
null, null, "7G9WZNcoY26Vy9D7bEgbv6zqbJGfyDp9KZyEbJR4XMZt/", List.of());

FileMetadata expectedFileMetadata1 = new FileMetadata("7G9WZNcoY26Vy9D7bEgbv6zqbJGfyDp9KZyEbJR4XMZt",
"image.png", null, "7G9WZNcoY26Vy9D7bEgbv6zqbJGfyDp9KZyEbJR4XMZt/image.png", 17, "binary/octet-stream");

FileMetadata expectedImageMetadata = new FileMetadata("7G9WZNcoY26Vy9D7bEgbv6zqbJGfyDp9KZyEbJR4XMZt",
"image.png", null, "7G9WZNcoY26Vy9D7bEgbv6zqbJGfyDp9KZyEbJR4XMZt/image.png", 17, "image/png");
FolderMetadata expectedRootFolderMetadata = new FolderMetadata("7G9WZNcoY26Vy9D7bEgbv6zqbJGfyDp9KZyEbJR4XMZt",
null, null, "7G9WZNcoY26Vy9D7bEgbv6zqbJGfyDp9KZyEbJR4XMZt/",
List.of(expectedImageMetadata));

Future.succeededFuture().compose((mapper) -> {
Promise<Void> promise = Promise.promise();
// verify no files
client.get(serverPort, "localhost", "/v1/files/metadata/7G9WZNcoY26Vy9D7bEgbv6zqbJGfyDp9KZyEbJR4XMZt/")
.putHeader("Api-key", "proxyKey2")
.as(BodyCodec.json(FolderMetadata.class))
.send(context.succeeding(response -> {
context.verify(() -> {
assertEquals(200, response.statusCode());
assertEquals(emptyFolderResponse, response.body());
checkpoint.flag();
promise.complete();
});
}));

return promise.future();
}).compose((mapper) -> {
Promise<Void> promise = Promise.promise();
// upload test file1
client.put(serverPort, "localhost", "/v1/files/7G9WZNcoY26Vy9D7bEgbv6zqbJGfyDp9KZyEbJR4XMZt/image.png")
.putHeader("Api-key", "proxyKey2")
.as(BodyCodec.json(FileMetadata.class))
.sendMultipartForm(generateMultipartForm("filename", TEST_FILE_CONTENT, "binary/octet-stream"),
context.succeeding(response -> {
context.verify(() -> {
assertEquals(200, response.statusCode());
assertEquals(expectedFileMetadata1, response.body());
checkpoint.flag();
promise.complete();
});
})
);

return promise.future();
}).andThen((result) -> {
// verify uploaded files can be listed
client.get(serverPort, "localhost", "/v1/files/metadata/7G9WZNcoY26Vy9D7bEgbv6zqbJGfyDp9KZyEbJR4XMZt/")
.putHeader("Api-key", "proxyKey2")
.as(BodyCodec.string())
.send(context.succeeding(response -> {
context.verify(() -> {
assertEquals(200, response.statusCode());
assertEquals(ProxyUtil.MAPPER.writeValueAsString(expectedRootFolderMetadata), response.body());
checkpoint.flag();
});
}));
});
}

@Test
public void testFileDelete(Vertx vertx, VertxTestContext context) {
Checkpoint checkpoint = context.checkpoint(3);
Expand Down

0 comments on commit 760f54f

Please sign in to comment.