From 4711e4fedbb5e1f7eb4ed170f184c47103fdf30b Mon Sep 17 00:00:00 2001 From: GilvanOrnelas Date: Sun, 1 Nov 2020 11:46:15 -0300 Subject: [PATCH] Storing files indexed by a database (#10174) Creating the FileLocationService to link the FileSystemRepository to the ImageDbRepository. Removing test order Changing to BDDMockito Changing Long wrapper for @id. Changing the test names to given-when-then pattern Co-authored-by: Gilvan Ornelas Fernandes Filho --- .../db/indexing/FileLocationService.java | 31 ++++++++ .../indexing/FileSystemImageController.java | 30 ++++++++ .../db/indexing/FileSystemRepository.java | 36 +++++++++ .../java/com/baeldung/db/indexing/Image.java | 62 +++++++++++++++ .../db/indexing/ImageArchiveApplication.java | 13 ++++ .../baeldung/db/indexing/ImageController.java | 46 +++++++++++ .../db/indexing/ImageDbRepository.java | 9 +++ .../FileSystemImageIntegrationTest.java | 69 +++++++++++++++++ .../db/indexing/ImageIntegrationTest.java | 72 ++++++++++++++++++ .../src/test/resources/baeldung.jpeg | Bin 0 -> 9291 bytes 10 files changed, 368 insertions(+) create mode 100644 persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/FileLocationService.java create mode 100644 persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/FileSystemImageController.java create mode 100644 persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/FileSystemRepository.java create mode 100644 persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/Image.java create mode 100644 persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/ImageArchiveApplication.java create mode 100644 persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/ImageController.java create mode 100644 persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/ImageDbRepository.java create mode 100644 persistence-modules/spring-boot-persistence-2/src/test/java/com/baeldung/db/indexing/FileSystemImageIntegrationTest.java create mode 100644 persistence-modules/spring-boot-persistence-2/src/test/java/com/baeldung/db/indexing/ImageIntegrationTest.java create mode 100644 persistence-modules/spring-boot-persistence-2/src/test/resources/baeldung.jpeg diff --git a/persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/FileLocationService.java b/persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/FileLocationService.java new file mode 100644 index 0000000000..6b2f33e885 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/FileLocationService.java @@ -0,0 +1,31 @@ +package com.baeldung.db.indexing; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.FileSystemResource; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; + +@Service +class FileLocationService { + + @Autowired + FileSystemRepository fileSystemRepository; + @Autowired + ImageDbRepository imageDbRepository; + + Long save(byte[] bytes, String imageName) throws Exception { + String location = fileSystemRepository.save(bytes, imageName); + + return imageDbRepository.save(new Image(imageName, location)) + .getId(); + } + + FileSystemResource find(Long imageId) { + Image image = imageDbRepository.findById(imageId) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + + return fileSystemRepository.findInFileSystem(image.getLocation()); + } + +} diff --git a/persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/FileSystemImageController.java b/persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/FileSystemImageController.java new file mode 100644 index 0000000000..09e33bb943 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/FileSystemImageController.java @@ -0,0 +1,30 @@ +package com.baeldung.db.indexing; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.FileSystemResource; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequestMapping("file-system") +class FileSystemImageController { + + @Autowired + FileLocationService fileLocationService; + + @PostMapping("/image") + Long uploadImage(@RequestParam MultipartFile image) throws Exception { + return fileLocationService.save(image.getBytes(), image.getOriginalFilename()); + } + + @GetMapping(value = "/image/{imageId}", produces = MediaType.IMAGE_JPEG_VALUE) + FileSystemResource downloadImage(@PathVariable Long imageId) throws Exception { + return fileLocationService.find(imageId); + } +} diff --git a/persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/FileSystemRepository.java b/persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/FileSystemRepository.java new file mode 100644 index 0000000000..bc6bdecfed --- /dev/null +++ b/persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/FileSystemRepository.java @@ -0,0 +1,36 @@ +package com.baeldung.db.indexing; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Date; + +import org.springframework.core.io.FileSystemResource; +import org.springframework.stereotype.Repository; + +@Repository +class FileSystemRepository { + + String RESOURCES_DIR = FileSystemRepository.class.getResource("/") + .getPath(); + + String save(byte[] content, String imageName) throws Exception { + Path newFile = Paths.get(RESOURCES_DIR + new Date().getTime() + "-" + imageName); + Files.createDirectories(newFile.getParent()); + + Files.write(newFile, content); + + return newFile.toAbsolutePath() + .toString(); + } + + FileSystemResource findInFileSystem(String location) { + try { + return new FileSystemResource(Paths.get(location)); + } catch (Exception e) { + // Handle access or file not found problems. + throw new RuntimeException(); + } + } + +} diff --git a/persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/Image.java b/persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/Image.java new file mode 100644 index 0000000000..5348788c12 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/Image.java @@ -0,0 +1,62 @@ +package com.baeldung.db.indexing; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Lob; + +@Entity +class Image { + + @Id + @GeneratedValue + Long id; + + String name; + + String location; + + @Lob + byte[] content; + + public Image() { + } + + public Image(String name, String location) { + this.name = name; + this.location = location; + } + + public byte[] getContent() { + return content; + } + + public void setContent(byte[] content) { + this.content = content; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + +} diff --git a/persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/ImageArchiveApplication.java b/persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/ImageArchiveApplication.java new file mode 100644 index 0000000000..02d2db32b3 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/ImageArchiveApplication.java @@ -0,0 +1,13 @@ +package com.baeldung.db.indexing; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ImageArchiveApplication { + + public static void main(String[] args) { + SpringApplication.run(ImageArchiveApplication.class, args); + } + +} diff --git a/persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/ImageController.java b/persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/ImageController.java new file mode 100644 index 0000000000..1b2378a51f --- /dev/null +++ b/persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/ImageController.java @@ -0,0 +1,46 @@ +package com.baeldung.db.indexing; + +import java.io.IOException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.server.ResponseStatusException; + +import lombok.extern.slf4j.Slf4j; + +@RestController +@Slf4j +class ImageController { + + @Autowired + ImageDbRepository imageDbRepository; + + @PostMapping("/image") + long uploadImage(@RequestParam MultipartFile multipartImage) throws IOException { + Image dbImage = new Image(); + dbImage.setName(multipartImage.getOriginalFilename()); + dbImage.setContent(multipartImage.getBytes()); + + return imageDbRepository.save(dbImage) + .getId(); + } + + @GetMapping(value = "/image/{imageId}", produces = MediaType.IMAGE_JPEG_VALUE) + Resource downloadImage(@PathVariable Long imageId) { + byte[] image = imageDbRepository.findById(imageId) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)) + .getContent(); + + return new ByteArrayResource(image); + } + +} diff --git a/persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/ImageDbRepository.java b/persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/ImageDbRepository.java new file mode 100644 index 0000000000..eb33f14dae --- /dev/null +++ b/persistence-modules/spring-boot-persistence-2/src/main/java/com/baeldung/db/indexing/ImageDbRepository.java @@ -0,0 +1,9 @@ +package com.baeldung.db.indexing; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +interface ImageDbRepository extends JpaRepository { + +} diff --git a/persistence-modules/spring-boot-persistence-2/src/test/java/com/baeldung/db/indexing/FileSystemImageIntegrationTest.java b/persistence-modules/spring-boot-persistence-2/src/test/java/com/baeldung/db/indexing/FileSystemImageIntegrationTest.java new file mode 100644 index 0000000000..3082f16a78 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-2/src/test/java/com/baeldung/db/indexing/FileSystemImageIntegrationTest.java @@ -0,0 +1,69 @@ +package com.baeldung.db.indexing; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.InputStream; +import java.nio.file.Paths; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.core.io.FileSystemResource; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +@SpringBootTest(classes = ImageArchiveApplication.class) +@AutoConfigureMockMvc +class FileSystemImageIntegrationTest { + + @Autowired + MockMvc mockMvc; + + @MockBean + FileLocationService fileLocationService; + + @Test + void givenJpegImage_whenUploadIt_thenReturnItsId() throws Exception { + ClassLoader classLoader = ClassLoader.getSystemClassLoader(); + InputStream image = classLoader.getResourceAsStream("baeldung.jpeg"); + + MockMultipartHttpServletRequestBuilder multipartRequest = MockMvcRequestBuilders.multipart("/file-system/image") + .file(new MockMultipartFile("image", "baeldung", MediaType.TEXT_PLAIN_VALUE, image)); + + MvcResult result = mockMvc.perform(multipartRequest) + .andExpect(status().isOk()) + .andReturn(); + + assertThat(result.getResponse() + .getContentAsString()) + .isEqualTo("1"); + } + + @Test + void givenBaeldungImage_whenDownloadIt_thenReturnTheImage() throws Exception { + given(fileLocationService.find(1L)) + .willReturn(baeldungJpegResource()); + + mockMvc.perform(MockMvcRequestBuilders + .get("/file-system/image/1") + .contentType(MediaType.IMAGE_JPEG_VALUE)) + .andExpect(status().isOk()); + } + + private FileSystemResource baeldungJpegResource() { + ClassLoader classLoader = ClassLoader.getSystemClassLoader(); + String imagePath = classLoader.getResource("baeldung.jpeg") + .getFile(); + + return new FileSystemResource(Paths.get(imagePath)); + } + +} diff --git a/persistence-modules/spring-boot-persistence-2/src/test/java/com/baeldung/db/indexing/ImageIntegrationTest.java b/persistence-modules/spring-boot-persistence-2/src/test/java/com/baeldung/db/indexing/ImageIntegrationTest.java new file mode 100644 index 0000000000..f7d4ecf129 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-2/src/test/java/com/baeldung/db/indexing/ImageIntegrationTest.java @@ -0,0 +1,72 @@ +package com.baeldung.db.indexing; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Optional; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +@SpringBootTest(classes = ImageArchiveApplication.class) +@AutoConfigureMockMvc +class ImageIntegrationTest { + + @Autowired + MockMvc mockMvc; + + @MockBean + ImageDbRepository imageRepository; + + @Test + void givenBaeldungJpegImage_whenUploadIt_thenReturnItsId() throws Exception { + ClassLoader classLoader = ClassLoader.getSystemClassLoader(); + InputStream image = classLoader.getResourceAsStream("baeldung.jpeg"); + + MockMultipartHttpServletRequestBuilder multipartRequest = MockMvcRequestBuilders.multipart("/image") + .file(new MockMultipartFile("image", "baeldung", MediaType.TEXT_PLAIN_VALUE, image)); + + MvcResult result = mockMvc.perform(multipartRequest) + .andExpect(status().isOk()) + .andReturn(); + + assertThat(result.getResponse() + .getContentAsString()) + .isEqualTo("1"); + } + + @Test + void givenExistingImage_whenDownloadIt_thenReturnHttpStatusOk() throws Exception { + given(imageRepository.findById(1L)) + .willReturn(Optional.of(baeldungImage())); + + mockMvc.perform(MockMvcRequestBuilders + .get("/image/1") + .contentType(MediaType.IMAGE_JPEG_VALUE)) + .andExpect(status().isOk()); + } + + private Image baeldungImage() throws IOException { + ClassLoader classLoader = ClassLoader.getSystemClassLoader(); + + Image mockImage = new Image(); + mockImage.setContent(Files.readAllBytes(Paths.get(classLoader.getResource("baeldung.jpeg") + .getFile()))); + return mockImage; + } + +} diff --git a/persistence-modules/spring-boot-persistence-2/src/test/resources/baeldung.jpeg b/persistence-modules/spring-boot-persistence-2/src/test/resources/baeldung.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..2654748a27c61c03384e9c3597e40bf7380b5296 GIT binary patch literal 9291 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