From 9a1c2dcf79c86128ef9afc8ddfe885722fc1607d Mon Sep 17 00:00:00 2001 From: michaelin007 Date: Sun, 14 Jan 2024 18:30:38 +0000 Subject: [PATCH] Upload Multiple Files using WebFlux --- spring-5-webflux-2/pom.xml | 22 +++++ .../webflux/filerecord/FileRecord.java | 35 +++++++ .../filerecord/FileRecordApplication.java | 28 ++++++ .../filerecord/FileRecordController.java | 45 +++++++++ .../filerecord/FileRecordRepository.java | 8 ++ .../webflux/filerecord/FileRecordService.java | 23 +++++ .../src/main/resources/application.properties | 1 + .../src/main/resources/schema.sql | 5 + .../FileRecordControllerIntegrationTest.java | 98 +++++++++++++++++++ 9 files changed, 265 insertions(+) create mode 100644 spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecord.java create mode 100644 spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordApplication.java create mode 100644 spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordController.java create mode 100644 spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordRepository.java create mode 100644 spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordService.java create mode 100644 spring-5-webflux-2/src/main/resources/application.properties create mode 100644 spring-5-webflux-2/src/main/resources/schema.sql create mode 100644 spring-5-webflux-2/src/test/java/com/baeldung/webflux/filerecord/FileRecordControllerIntegrationTest.java diff --git a/spring-5-webflux-2/pom.xml b/spring-5-webflux-2/pom.xml index efb99e06d5..5422ed55c4 100644 --- a/spring-5-webflux-2/pom.xml +++ b/spring-5-webflux-2/pom.xml @@ -88,6 +88,20 @@ mockwebserver 4.12.0 + + org.springframework.boot + spring-boot-starter-data-r2dbc + + + com.h2database + h2 + runtime + + + io.r2dbc + r2dbc-h2 + runtime + @@ -96,6 +110,14 @@ org.springframework.boot spring-boot-maven-plugin + + org.apache.maven.plugins + maven-compiler-plugin + + 16 + 16 + + diff --git a/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecord.java b/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecord.java new file mode 100644 index 0000000000..5c0e265e13 --- /dev/null +++ b/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecord.java @@ -0,0 +1,35 @@ +package com.baeldung.webflux.filerecord; + +import org.springframework.data.annotation.Id; + +import java.util.List; + +public class FileRecord { + @Id + private int id; + + private List filenames; + + public FileRecord(List filenames) { + this.filenames = filenames; + } + + public FileRecord() { + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public List getFilenames() { + return filenames; + } + + public void setFilenames(List filenames) { + this.filenames = filenames; + } +} diff --git a/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordApplication.java b/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordApplication.java new file mode 100644 index 0000000000..9c20e10040 --- /dev/null +++ b/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordApplication.java @@ -0,0 +1,28 @@ +package com.baeldung.webflux.filerecord; + +import io.r2dbc.spi.ConnectionFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.core.io.ClassPathResource; +import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer; +import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator; + +@SpringBootApplication +public class FileRecordApplication { + + public static void main(String[] args) { + SpringApplication.run(FileRecordApplication.class, args); + } + + @Bean + ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) { + + ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer(); + initializer.setConnectionFactory(connectionFactory); + initializer.setDatabasePopulator(new ResourceDatabasePopulator(new ClassPathResource("schema.sql"))); + + return initializer; + } + +} diff --git a/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordController.java b/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordController.java new file mode 100644 index 0000000000..943cec9a2d --- /dev/null +++ b/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordController.java @@ -0,0 +1,45 @@ +package com.baeldung.webflux.filerecord; + +import org.springframework.http.codec.multipart.FilePart; +import org.springframework.web.bind.annotation.*; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.nio.file.Paths; + +@RestController +public class FileRecordController { + + private final FileRecordService fileRecordService; + + public FileRecordController(FileRecordService fileRecordService) { + this.fileRecordService = fileRecordService; + } + + @PostMapping("/upload-files") + public Mono uploadFileWithoutEntity(@RequestPart("files") Flux filePartFlux) { + return filePartFlux.flatMap(file -> file.transferTo(Paths.get(file.filename()))) + .then(Mono.just("OK")) + .onErrorResume(error -> Mono.just("Error uploading files")); + } + + @PostMapping("/upload-files-entity") + public Mono uploadFileWithEntity(@RequestPart("files") Flux filePartFlux) { + FileRecord fileRecord = new FileRecord(); + + return filePartFlux.flatMap(filePart -> filePart.transferTo(Paths.get(filePart.filename())) + .then(Mono.just(filePart.filename()))) + .collectList() + .flatMap(filenames -> { + fileRecord.setFilenames(filenames); + return fileRecordService.save(fileRecord); + }) + .onErrorResume(error -> Mono.error(error)); + } + + @GetMapping("/files/{id}") + public Mono geFilesById(@PathVariable("id") int id) { + return fileRecordService.findById(id) + .onErrorResume(error -> Mono.error(error)); + } +} diff --git a/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordRepository.java b/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordRepository.java new file mode 100644 index 0000000000..b4cd4df627 --- /dev/null +++ b/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordRepository.java @@ -0,0 +1,8 @@ +package com.baeldung.webflux.filerecord; + +import org.springframework.data.r2dbc.repository.R2dbcRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface FileRecordRepository extends R2dbcRepository { +} diff --git a/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordService.java b/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordService.java new file mode 100644 index 0000000000..6e74f3577b --- /dev/null +++ b/spring-5-webflux-2/src/main/java/com/baeldung/webflux/filerecord/FileRecordService.java @@ -0,0 +1,23 @@ +package com.baeldung.webflux.filerecord; + +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +@Service +public class FileRecordService { + + private FileRecordRepository fileRecordRepository; + + public FileRecordService(FileRecordRepository fileRecordRepository) { + this.fileRecordRepository = fileRecordRepository; + } + + public Mono save(FileRecord fileRecord) { + return fileRecordRepository.save(fileRecord); + } + + public Mono findById(int id) { + return fileRecordRepository.findById(id); + } + +} diff --git a/spring-5-webflux-2/src/main/resources/application.properties b/spring-5-webflux-2/src/main/resources/application.properties new file mode 100644 index 0000000000..5e52669cdb --- /dev/null +++ b/spring-5-webflux-2/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.r2dbc.url=r2dbc:h2:file:///./testdb \ No newline at end of file diff --git a/spring-5-webflux-2/src/main/resources/schema.sql b/spring-5-webflux-2/src/main/resources/schema.sql new file mode 100644 index 0000000000..76a70c8ff6 --- /dev/null +++ b/spring-5-webflux-2/src/main/resources/schema.sql @@ -0,0 +1,5 @@ +CREATE TABLE IF NOT EXISTS file_record ( + id INT NOT NULL AUTO_INCREMENT, + filenames VARCHAR(255), + PRIMARY KEY (id) +); \ No newline at end of file diff --git a/spring-5-webflux-2/src/test/java/com/baeldung/webflux/filerecord/FileRecordControllerIntegrationTest.java b/spring-5-webflux-2/src/test/java/com/baeldung/webflux/filerecord/FileRecordControllerIntegrationTest.java new file mode 100644 index 0000000000..c47d53632b --- /dev/null +++ b/spring-5-webflux-2/src/test/java/com/baeldung/webflux/filerecord/FileRecordControllerIntegrationTest.java @@ -0,0 +1,98 @@ +package com.baeldung.webflux.filerecord; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.util.LinkedMultiValueMap; +import reactor.core.publisher.Mono; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(classes = FileRecordController.class) +@ContextConfiguration(classes = { FileRecordController.class }) +@ComponentScan("com.baeldung.webflux.filerecord") +@AutoConfigureWebTestClient +class FileRecordControllerIntegrationTest { + + @Autowired + private WebTestClient webTestClient; + + @MockBean + private FileRecordService fileRecordService; + + @Test + public void givenUploadFilesWithEntity_whenRequestIsValid_thenReturnCreated() throws Exception { + + FileRecord fileRecord = new FileRecord(); + MockMultipartFile firstFile = new MockMultipartFile("file", "baeldungdata.txt", MediaType.TEXT_PLAIN_VALUE, "Test file content".getBytes()); + MockMultipartFile secondFile = new MockMultipartFile("file", "baeldungdata.pdf", MediaType.TEXT_PLAIN_VALUE, "Test file content".getBytes()); + List files = List.of(firstFile, secondFile); + fileRecord.setFilenames(files.stream() + .map(MockMultipartFile::getOriginalFilename) + .toList()); + + Mono fileRecordMono = Mono.just(fileRecord); + when(fileRecordService.save(any(FileRecord.class))).thenReturn(fileRecordMono); + + webTestClient.post() + .uri("/upload-files-entity") + .body(Mono.just(fileRecord), FileRecord.class) + .exchange() + .expectBody(FileRecord.class); + + } + + @Test + public void givenUploadFiles_whenRequestIsValid_thenReturnOk() throws Exception { + + MockMultipartFile firstFile = new MockMultipartFile("file", "baeldungdata.txt", MediaType.TEXT_PLAIN_VALUE, "Test file content".getBytes()); + MockMultipartFile secondFile = new MockMultipartFile("file", "baeldungdata.pdf", MediaType.TEXT_PLAIN_VALUE, "Test file content".getBytes()); + LinkedMultiValueMap multipartData = new LinkedMultiValueMap<>(); + multipartData.add("file", firstFile); + multipartData.add("file", secondFile); + + webTestClient.post() + .uri("/upload-files") + .bodyValue(multipartData) + .exchange() + .expectStatus() + .isOk(); + } + + @Test + public void givenUploadFilesWithEntity_whenRequestFileIsFindById_thenReturnOk() throws Exception { + + FileRecord fileRecord = new FileRecord(); + MockMultipartFile firstFile = new MockMultipartFile("file", "baeldungdata.txt", MediaType.TEXT_PLAIN_VALUE, "Test file content".getBytes()); + MockMultipartFile secondFile = new MockMultipartFile("file", "baeldungdata.pdf", MediaType.TEXT_PLAIN_VALUE, "Test file content".getBytes()); + List files = List.of(firstFile, secondFile); + fileRecord.setId(1); + fileRecord.setFilenames(files.stream() + .map(MockMultipartFile::getOriginalFilename) + .toList()); + + Mono fileRecordMono = Mono.just(fileRecord); + + when(fileRecordService.findById(1)).thenReturn(fileRecordMono); + + webTestClient.get() + .uri("/files/{id}", 1) + .exchange() + .expectStatus() + .isOk(); + } + +} \ No newline at end of file