BAEL-6827 - Update an Existing Amazon S3 Object using Java (#14585)

* Deep and Shallow copy - code snippets

* removed maven files

* Ignore mvn files

* Removed application test file

* Removed .gitIgnore, additional review comments incorporated.

* Incorporate review comments

* incorporated review comments

* https://jira.baeldung.com/browse/BAEL-6827 - Initial Commit

* BAEL-6827 - Unit tests

* BAEL-6827 - Tests

* BAEL-6827 - Minor issue fixes

* Removed deep shalow copy project

* BAEL-6827 - BDD naming convention

* BAEL-6827 - Test classes end with *UnitTest,.java
This commit is contained in:
Amol Gote 2023-08-17 16:38:05 -04:00 committed by GitHub
parent 7f13b86f3b
commit fed88e6609
8 changed files with 326 additions and 1 deletions

5
.gitignore vendored
View File

@ -124,4 +124,7 @@ devDb*.db
*.xjb
#neo4j
persistence-modules/neo4j/data/**
persistence-modules/neo4j/data/**
/deep-shallow-copy/.mvn/wrapper
/deep-shallow-copy/mvnw
/deep-shallow-copy/mvnw.cmd

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.baeldung</groupId>
<artifactId>aws-s3-update-object</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>aws-s3-update-object</name>
<description>Project demonstrating overwriting of S3 objects</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk</artifactId>
<version>1.12.523</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,13 @@
package com.baeldung.awss3updateobject;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AwsS3UpdateObjectApplication {
public static void main(String[] args) {
SpringApplication.run(AwsS3UpdateObjectApplication.class, args);
}
}

View File

@ -0,0 +1,24 @@
package com.baeldung.awss3updateobject.controller;
import com.baeldung.awss3updateobject.service.FileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("api/v1/file")
public class FileController {
@Autowired
FileService fileService;
@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile multipartFile) throws Exception {
return this.fileService.uploadFile(multipartFile);
}
@PostMapping("/update")
public String updateFile(@RequestParam("file") MultipartFile multipartFile, @RequestParam("filePath") String exitingFilePath) throws Exception {
return this.fileService.updateFile(multipartFile, exitingFilePath);
}
}

View File

@ -0,0 +1,80 @@
package com.baeldung.awss3updateobject.service;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.*;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
@Service
public class FileService {
private static final Logger logger = LoggerFactory.getLogger(FileService.class);
public AmazonS3 amazonS3;
@Value("${aws.s3bucket}")
public String awsS3Bucket;
@PostConstruct
private void init(){
AWSCredentials credentials = new BasicAWSCredentials(
"AWS AccessKey",
"AWS secretKey"
);
this.amazonS3 = AmazonS3ClientBuilder.standard()
.withRegion(Regions.fromName("us-east-1"))
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.build();
}
public String uploadFile(MultipartFile multipartFile) throws Exception {
String key = "/documents/" + multipartFile.getOriginalFilename();
return this.uploadDocument(this.awsS3Bucket, key, multipartFile);
}
public String updateFile(MultipartFile multipartFile, String key) throws Exception {
return this.uploadDocument(this.awsS3Bucket, key, multipartFile);
}
private String uploadDocument(String s3bucket, String key, MultipartFile multipartFile) throws Exception {
try {
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType(multipartFile.getContentType());
Map<String, String> attributes = new HashMap<>();
attributes.put("document-content-size", String.valueOf(multipartFile.getSize()));
metadata.setUserMetadata(attributes);
InputStream documentStream = multipartFile.getInputStream();
PutObjectResult putObjectResult = this.amazonS3.putObject(new PutObjectRequest(s3bucket, key, documentStream, metadata));
S3Object s3Object = this.amazonS3.getObject(s3bucket, key);
logger.info("Last Modified: " + s3Object.getObjectMetadata().getLastModified());
return key;
} catch (AmazonS3Exception ex) {
if (ex.getErrorCode().equalsIgnoreCase("NoSuchBucket")) {
String msg = String.format("No bucket found with name %s", s3bucket);
throw new Exception(msg);
} else if (ex.getErrorCode().equalsIgnoreCase("AccessDenied")) {
String msg = String.format("Access denied to S3 bucket %s", s3bucket);
throw new Exception(msg);
}
throw ex;
} catch (IOException ex) {
String msg = String.format("Error saving file %s to AWS S3 bucket %s", key, s3bucket);
throw new Exception(msg);
}
}
}

View File

@ -0,0 +1 @@
aws.s3bucket=baeldung-documents;

View File

@ -0,0 +1,61 @@
package com.baeldung.awss3updateobject.controller;
import com.baeldung.awss3updateobject.service.FileService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.multipart.MultipartFile;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
public class FileControllerUnitTest {
private MockMvc mockMvc;
@Mock
private FileService fileService;
@InjectMocks
private FileController fileController;
@BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(fileController).build();
}
@Test
public void givenValidMultipartFile_whenUploadedViaEndpoint_thenCorrectPathIsReturned() throws Exception {
MockMultipartFile multipartFile = new MockMultipartFile("multipartFile", "test.txt",
"text/plain", "test data".getBytes());
when(fileService.uploadFile(any(MultipartFile.class))).thenReturn("/documents/test.txt");
mockMvc.perform(multipart("/file/upload").file(multipartFile))
.andExpect(status().isOk())
.andExpect(content().string("/documents/test.txt"));
}
@Test
public void givenValidMultipartFileAndExistingPath_whenUpdatedViaEndpoint_thenSamePathIsReturned() throws Exception {
MockMultipartFile multipartFile = new MockMultipartFile("multipartFile", "test.txt",
"text/plain", "test update data".getBytes());
String existingFilePath = "/documents/existingFile.txt";
when(fileService.updateFile(any(MultipartFile.class), eq(existingFilePath))).thenReturn(existingFilePath);
mockMvc.perform(multipart("/file/update")
.file(multipartFile)
.param("exitingFilePath", existingFilePath))
.andExpect(status().isOk())
.andExpect(content().string(existingFilePath));
}
}

View File

@ -0,0 +1,97 @@
package com.baeldung.awss3updateobject.service;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.AmazonS3Exception;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.S3Object;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
public class FileServiceUnitTest {
@Mock
private AmazonS3 amazonS3;
@Mock
private MultipartFile multipartFile;
@InjectMocks
private FileService fileService;
@BeforeEach
public void setup() {
MockitoAnnotations.openMocks(this);
fileService = new FileService();
fileService.awsS3Bucket = "test-bucket";
fileService.amazonS3 = amazonS3;
}
@Test
public void givenValidFile_whenUploaded_thenKeyMatchesDocumentPath() throws Exception {
when(multipartFile.getName()).thenReturn("testFile");
when(multipartFile.getContentType()).thenReturn("application/pdf");
when(multipartFile.getSize()).thenReturn(1024L);
when(multipartFile.getInputStream()).thenReturn(mock(InputStream.class));
S3Object s3Object = new S3Object();
when(amazonS3.putObject(any())).thenReturn(null);
when(amazonS3.getObject(anyString(), anyString())).thenReturn(s3Object);
String key = fileService.uploadFile(multipartFile);
assertEquals("/documents/testFile", key);
}
@Test
public void givenValidFile_whenUploadFailsDueToNoBucket_thenExceptionIsThrown() throws Exception {
when(multipartFile.getName()).thenReturn("testFile");
when(multipartFile.getContentType()).thenReturn("application/pdf");
when(multipartFile.getSize()).thenReturn(1024L);
when(multipartFile.getInputStream()).thenReturn(mock(InputStream.class));
AmazonS3Exception exception = new AmazonS3Exception("Test exception");
exception.setErrorCode("NoSuchBucket");
when(amazonS3.putObject(any(PutObjectRequest.class))).thenThrow(exception);
assertThrows(Exception.class, () -> fileService.uploadFile(multipartFile));
}
@Test
public void givenExistingFile_whenUpdated_thenSameKeyIsReturned() throws Exception {
when(multipartFile.getName()).thenReturn("testFile");
when(multipartFile.getContentType()).thenReturn("application/pdf");
when(multipartFile.getSize()).thenReturn(1024L);
when(multipartFile.getInputStream()).thenReturn(mock(InputStream.class));
S3Object s3Object = new S3Object();
when(amazonS3.putObject(any(PutObjectRequest.class))).thenReturn(null);
when(amazonS3.getObject(anyString(), anyString())).thenReturn(s3Object);
String key = "/documents/existingFile";
String resultKey = fileService.updateFile(multipartFile, key);
assertEquals(key, resultKey);
}
@Test
public void givenFileWithIOException_whenUpdated_thenExceptionIsThrown() throws Exception {
when(multipartFile.getName()).thenReturn("testFile");
when(multipartFile.getContentType()).thenReturn("application/pdf");
when(multipartFile.getSize()).thenReturn(1024L);
when(multipartFile.getInputStream()).thenThrow(new IOException("Test IO Exception"));
assertThrows(Exception.class, () -> fileService.updateFile(multipartFile, "/documents/existingFile"));
}
}