-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial reimplementation of multipart file upload
- Loading branch information
1 parent
ffebed6
commit 036effa
Showing
12 changed files
with
430 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
src/main/java/edu/stanford/protege/webprotege/gateway/FileStorageService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package edu.stanford.protege.webprotege.gateway; | ||
|
||
import io.minio.BucketExistsArgs; | ||
import io.minio.MakeBucketArgs; | ||
import io.minio.MinioClient; | ||
import io.minio.UploadObjectArgs; | ||
import io.minio.errors.MinioException; | ||
import org.apache.commons.io.FileUtils; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import org.springframework.stereotype.Component; | ||
|
||
import java.io.IOException; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.security.InvalidKeyException; | ||
import java.security.NoSuchAlgorithmException; | ||
import java.util.UUID; | ||
|
||
/** | ||
* Matthew Horridge | ||
* Stanford Center for Biomedical Informatics Research | ||
* 2024-06-10 | ||
*/ | ||
@Component | ||
public class FileStorageService { | ||
|
||
|
||
private static final Logger logger = LoggerFactory.getLogger(FileStorageService.class); | ||
|
||
private final MinioClient minioClient; | ||
|
||
private final MinioProperties minioProperties; | ||
|
||
public FileStorageService(MinioClient minioClient, | ||
MinioProperties minioProperties) { | ||
this.minioClient = minioClient; | ||
this.minioProperties = minioProperties; | ||
} | ||
|
||
public FileSubmissionId storeFile(Path tempFile) { | ||
var fileIdentifier = UUID.randomUUID().toString(); | ||
logger.info("Storing file ({}) in {} bucket with an object id of {}", getFileSizeInMB(tempFile), minioProperties.getBucketName(), fileIdentifier); | ||
createBucketIfNecessary(); | ||
uploadObject(tempFile, fileIdentifier); | ||
return new FileSubmissionId(fileIdentifier); | ||
} | ||
|
||
private String getFileSizeInMB(Path tempFile) { | ||
try { | ||
return FileUtils.byteCountToDisplaySize(Files.size(tempFile)); | ||
} catch (IOException e) { | ||
return ""; | ||
} | ||
} | ||
|
||
private void uploadObject(Path tempFile, String fileIdentifier) { | ||
try { | ||
minioClient.uploadObject(UploadObjectArgs.builder() | ||
.bucket(minioProperties.getBucketName()) | ||
.object(fileIdentifier) | ||
.filename(tempFile.toString()) | ||
.build()); | ||
} catch (MinioException | NoSuchAlgorithmException | | ||
InvalidKeyException | IOException e) { | ||
throw new StorageException(e); | ||
} | ||
} | ||
|
||
private void createBucketIfNecessary() { | ||
try { | ||
if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(minioProperties.getBucketName()).build())) { | ||
minioClient.makeBucket(MakeBucketArgs.builder().bucket(minioProperties.getBucketName()).build()); | ||
} | ||
} catch (MinioException | IOException | NoSuchAlgorithmException | IllegalArgumentException | | ||
InvalidKeyException e) { | ||
throw new StorageException(e); | ||
} | ||
} | ||
|
||
} |
18 changes: 18 additions & 0 deletions
18
src/main/java/edu/stanford/protege/webprotege/gateway/FileSubmissionId.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package edu.stanford.protege.webprotege.gateway; | ||
|
||
import com.fasterxml.jackson.annotation.JsonCreator; | ||
import com.fasterxml.jackson.annotation.JsonValue; | ||
|
||
public record FileSubmissionId(String value) { | ||
|
||
@JsonCreator | ||
public static FileSubmissionId valueOf(String value) { | ||
return new FileSubmissionId(value); | ||
} | ||
|
||
@JsonValue | ||
@Override | ||
public String value() { | ||
return value; | ||
} | ||
} |
40 changes: 40 additions & 0 deletions
40
src/main/java/edu/stanford/protege/webprotege/gateway/FileUploadController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package edu.stanford.protege.webprotege.gateway; | ||
|
||
import org.slf4j.*; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.http.*; | ||
import org.springframework.security.core.annotation.AuthenticationPrincipal; | ||
import org.springframework.security.oauth2.jwt.Jwt; | ||
import org.springframework.web.bind.annotation.*; | ||
import org.springframework.web.client.RestTemplate; | ||
import org.springframework.web.multipart.MultipartFile; | ||
|
||
import java.io.IOException; | ||
import java.nio.file.Files; | ||
|
||
/** | ||
* Matthew Horridge | ||
* Stanford Center for Biomedical Informatics Research | ||
* 2024-06-10 | ||
*/ | ||
@RestController | ||
public class FileUploadController { | ||
|
||
private final Logger logger = LoggerFactory.getLogger(GatewayController.class); | ||
|
||
private final FileStorageService fileStorageService; | ||
|
||
public FileUploadController(FileStorageService fileStorageService) { | ||
this.fileStorageService = fileStorageService; | ||
} | ||
|
||
@PostMapping(path = "/files/submit") | ||
public FileSubmissionId execute(@RequestParam MultipartFile file, | ||
@AuthenticationPrincipal Jwt principal) throws java.io.IOException { | ||
var userId = principal.getClaimAsString("preferred_username"); | ||
logger.info("Received a multipart file from {} with a size of {} bytes", userId, file.getSize()); | ||
var tempFile = Files.createTempFile("webprotege-file-upload", null); | ||
file.transferTo(tempFile); | ||
return fileStorageService.storeFile(tempFile); | ||
} | ||
} |
53 changes: 53 additions & 0 deletions
53
src/main/java/edu/stanford/protege/webprotege/gateway/MinioProperties.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package edu.stanford.protege.webprotege.gateway; | ||
|
||
import org.springframework.boot.context.properties.ConfigurationProperties; | ||
import org.springframework.context.annotation.Configuration; | ||
|
||
/** | ||
* Matthew Horridge | ||
* Stanford Center for Biomedical Informatics Research | ||
* 2024-06-10 | ||
*/ | ||
@ConfigurationProperties(prefix = "webprotege.minio") | ||
public class MinioProperties { | ||
|
||
private String accessKey; | ||
|
||
private String secretKey; | ||
|
||
private String endPoint; | ||
|
||
private String bucketName; | ||
|
||
public void setAccessKey(String accessKey) { | ||
this.accessKey = accessKey; | ||
} | ||
|
||
public void setSecretKey(String secretKey) { | ||
this.secretKey = secretKey; | ||
} | ||
|
||
public void setEndPoint(String endPoint) { | ||
this.endPoint = endPoint; | ||
} | ||
|
||
public String getAccessKey() { | ||
return accessKey; | ||
} | ||
|
||
public String getSecretKey() { | ||
return secretKey; | ||
} | ||
|
||
public String getEndPoint() { | ||
return endPoint; | ||
} | ||
|
||
public void setBucketName(String bucketName) { | ||
this.bucketName = bucketName; | ||
} | ||
|
||
public String getBucketName() { | ||
return bucketName; | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
src/main/java/edu/stanford/protege/webprotege/gateway/StorageException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package edu.stanford.protege.webprotege.gateway; | ||
|
||
/** | ||
* Matthew Horridge | ||
* Stanford Center for Biomedical Informatics Research | ||
* 2024-06-10 | ||
*/ | ||
public class StorageException extends RuntimeException { | ||
|
||
public StorageException(Throwable cause) { | ||
super(cause); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
78 changes: 78 additions & 0 deletions
78
src/test/java/edu/stanford/protege/webprotege/gateway/FileStorageServiceIntegrationTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package edu.stanford.protege.webprotege.gateway; | ||
|
||
import io.minio.*; | ||
import org.junit.jupiter.api.*; | ||
import org.junit.jupiter.api.extension.ExtendWith; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.boot.test.context.SpringBootTest; | ||
import org.springframework.test.context.ActiveProfiles; | ||
|
||
import java.nio.file.Files; | ||
|
||
import static org.assertj.core.api.Assertions.*; | ||
|
||
/** | ||
* Matthew Horridge | ||
* Stanford Center for Biomedical Informatics Research | ||
* 2024-06-12 | ||
*/ | ||
|
||
@ExtendWith(MinioTestExtension.class) | ||
@ActiveProfiles("test") | ||
public class FileStorageServiceIntegrationTest { | ||
|
||
private FileStorageService storageService; | ||
|
||
private MinioClient client; | ||
|
||
@BeforeEach | ||
void setUp() { | ||
var properties = new MinioProperties(); | ||
properties.setBucketName("webprotege-uploads"); | ||
properties.setSecretKey("minioadmin"); | ||
properties.setAccessKey("minioadmin"); | ||
properties.setEndPoint(System.getProperty("webprotege.minio.endPoint")); | ||
client = MinioClient.builder() | ||
.credentials(properties.getAccessKey(), properties.getSecretKey()) | ||
.endpoint(properties.getEndPoint()) | ||
.build(); | ||
storageService = new FileStorageService(client, properties); | ||
} | ||
|
||
@Test | ||
public void testStoreFile() throws Exception { | ||
// Setup a temporary file | ||
var tempFile = Files.createTempFile("test-file-", ".txt"); | ||
Files.write(tempFile, "This is a test file".getBytes()); | ||
|
||
// Test storing the file | ||
var fileSubmissionId = storageService.storeFile(tempFile); | ||
assertThat(fileSubmissionId).isNotNull(); | ||
|
||
// Cleanup | ||
Files.deleteIfExists(tempFile); | ||
|
||
var exists = client.bucketExists(BucketExistsArgs.builder().bucket("webprotege-uploads").build()); | ||
assertThat(exists).isTrue(); | ||
} | ||
|
||
@Test | ||
public void testStoreFileWhenMinioIsDown() throws Exception { | ||
// Simulate Minio down by incorrect port or server address | ||
storageService = new FileStorageService(MinioClient.builder() | ||
.endpoint("http://localhost:9999") | ||
.credentials("minioadmin", "minioadmin") | ||
.build(), | ||
new MinioProperties()); | ||
|
||
var tempFile = Files.createTempFile("test-file-", ".txt"); | ||
Files.write(tempFile, "Content".getBytes()); | ||
|
||
// Expect an exception since Minio is down | ||
assertThatThrownBy(() -> storageService.storeFile(tempFile)) | ||
.isInstanceOf(StorageException.class); | ||
|
||
// Cleanup | ||
Files.deleteIfExists(tempFile); | ||
} | ||
} |
Oops, something went wrong.