diff --git a/.gitignore b/.gitignore index d22c792f43..0b6bd24070 100644 --- a/.gitignore +++ b/.gitignore @@ -124,4 +124,7 @@ devDb*.db *.xjb #neo4j -persistence-modules/neo4j/data/** \ No newline at end of file +persistence-modules/neo4j/data/** +/deep-shallow-copy/.mvn/wrapper +/deep-shallow-copy/mvnw +/deep-shallow-copy/mvnw.cmd diff --git a/aws-modules/aws-s3-update-object/pom.xml b/aws-modules/aws-s3-update-object/pom.xml new file mode 100644 index 0000000000..b44cdb8c6a --- /dev/null +++ b/aws-modules/aws-s3-update-object/pom.xml @@ -0,0 +1,46 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.1.2 + + + com.baeldung + aws-s3-update-object + 0.0.1-SNAPSHOT + aws-s3-update-object + Project demonstrating overwriting of S3 objects + + 17 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + com.amazonaws + aws-java-sdk + 1.12.523 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/aws-modules/aws-s3-update-object/src/main/java/com/baeldung/awss3updateobject/AwsS3UpdateObjectApplication.java b/aws-modules/aws-s3-update-object/src/main/java/com/baeldung/awss3updateobject/AwsS3UpdateObjectApplication.java new file mode 100644 index 0000000000..24866c287b --- /dev/null +++ b/aws-modules/aws-s3-update-object/src/main/java/com/baeldung/awss3updateobject/AwsS3UpdateObjectApplication.java @@ -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); + } + +} diff --git a/aws-modules/aws-s3-update-object/src/main/java/com/baeldung/awss3updateobject/controller/FileController.java b/aws-modules/aws-s3-update-object/src/main/java/com/baeldung/awss3updateobject/controller/FileController.java new file mode 100644 index 0000000000..e87358ef56 --- /dev/null +++ b/aws-modules/aws-s3-update-object/src/main/java/com/baeldung/awss3updateobject/controller/FileController.java @@ -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); + } +} diff --git a/aws-modules/aws-s3-update-object/src/main/java/com/baeldung/awss3updateobject/service/FileService.java b/aws-modules/aws-s3-update-object/src/main/java/com/baeldung/awss3updateobject/service/FileService.java new file mode 100644 index 0000000000..8f3458d060 --- /dev/null +++ b/aws-modules/aws-s3-update-object/src/main/java/com/baeldung/awss3updateobject/service/FileService.java @@ -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 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); + } + } +} diff --git a/aws-modules/aws-s3-update-object/src/main/resources/application.properties b/aws-modules/aws-s3-update-object/src/main/resources/application.properties new file mode 100644 index 0000000000..c840d970a8 --- /dev/null +++ b/aws-modules/aws-s3-update-object/src/main/resources/application.properties @@ -0,0 +1 @@ +aws.s3bucket=baeldung-documents; diff --git a/aws-modules/aws-s3-update-object/src/test/java/com/baeldung/awss3updateobject/controller/FileControllerUnitTest.java b/aws-modules/aws-s3-update-object/src/test/java/com/baeldung/awss3updateobject/controller/FileControllerUnitTest.java new file mode 100644 index 0000000000..ec2385f62b --- /dev/null +++ b/aws-modules/aws-s3-update-object/src/test/java/com/baeldung/awss3updateobject/controller/FileControllerUnitTest.java @@ -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)); + } +} \ No newline at end of file diff --git a/aws-modules/aws-s3-update-object/src/test/java/com/baeldung/awss3updateobject/service/FileServiceUnitTest.java b/aws-modules/aws-s3-update-object/src/test/java/com/baeldung/awss3updateobject/service/FileServiceUnitTest.java new file mode 100644 index 0000000000..3ccd41820e --- /dev/null +++ b/aws-modules/aws-s3-update-object/src/test/java/com/baeldung/awss3updateobject/service/FileServiceUnitTest.java @@ -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")); + } +} \ No newline at end of file