diff --git a/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/switchIfEmpty/controller/UserController.java b/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/switchIfEmpty/controller/UserController.java new file mode 100644 index 000000000000..1c56f1203644 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/switchIfEmpty/controller/UserController.java @@ -0,0 +1,30 @@ +package com.baeldung.switchIfEmpty.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.baeldung.switchIfEmpty.model.User; +import com.baeldung.switchIfEmpty.service.UserService; + +import reactor.core.publisher.Mono; + +@RestController +@RequestMapping("api/v1/") +public class UserController { + + private final UserService userService; + + public UserController(UserService userService) { + this.userService = userService; + } + + @GetMapping("/user/{id}") + public Mono> findUserDetails(@PathVariable("id") String id, @RequestParam("withDefer") boolean withDefer) { + return (withDefer ? userService.findByUserIdWithDefer(id) : userService.findByUserIdWithoutDefer(id)).map(ResponseEntity::ok); + } + +} diff --git a/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/switchIfEmpty/model/User.java b/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/switchIfEmpty/model/User.java new file mode 100644 index 000000000000..589099144e39 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/switchIfEmpty/model/User.java @@ -0,0 +1,31 @@ +package com.baeldung.switchIfEmpty.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class User { + + @JsonProperty("id") + private String id; + + @JsonProperty("email") + private String email; + + @JsonProperty("username") + private String username; + + @JsonProperty("roles") + private String roles; + + public User(){} + + public User(String id, String email, String username, String roles) { + this.id = id; + this.email = email; + this.username = username; + this.roles = roles; + } + + public String getId() { + return id; + } +} \ No newline at end of file diff --git a/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/switchIfEmpty/service/UserService.java b/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/switchIfEmpty/service/UserService.java new file mode 100644 index 000000000000..47c4b9b35d94 --- /dev/null +++ b/spring-reactive-modules/spring-reactive-4/src/main/java/com/baeldung/switchIfEmpty/service/UserService.java @@ -0,0 +1,75 @@ +package com.baeldung.switchIfEmpty.service; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Service; + +import com.baeldung.switchIfEmpty.model.User; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import reactor.core.publisher.Mono; + +@Service +public class UserService { + + private static final Logger LOG = LoggerFactory.getLogger(UserService.class); + + private final Map usersCache; + + private final ObjectMapper objectMapper; + + public UserService(ObjectMapper objectMapper) { + this.usersCache = new HashMap<>(); + this.objectMapper = objectMapper; + } + + public Map getUsers() { + return usersCache; + } + + public Mono findByUserIdWithDefer(String id) { + return fetchFromCache(id).switchIfEmpty(Mono.defer(() -> fetchFromFile(id))); + } + + public Mono findByUserIdWithoutDefer(String id) { + return fetchFromCache(id).switchIfEmpty(fetchFromFile(id)); + } + + private Mono fetchFromCache(String id) { + User user = usersCache.get(id); + if (user != null) { + LOG.info("Fetched user {} from cache", id); + return Mono.just(user); + } + return Mono.empty(); + } + + private Mono fetchFromFile(String id) { + try { + File file = new ClassPathResource("users.json").getFile(); + String usersData = new String(Files.readAllBytes(file.toPath())); + List users = objectMapper.readValue(usersData, new TypeReference>() { + }); + User user = users.stream() + .filter(u -> u.getId() + .equalsIgnoreCase(id)) + .findFirst() + .get(); + usersCache.put(user.getId(), user); + LOG.info("Fetched user {} from file", id); + return Mono.just(user); + } catch (IOException e) { + return Mono.error(e); + } + } + +} diff --git a/spring-reactive-modules/spring-reactive-4/src/test/java/com/baeldung/switchIfEmpty/controller/UserControllerTest.java b/spring-reactive-modules/spring-reactive-4/src/test/java/com/baeldung/switchIfEmpty/controller/UserControllerTest.java new file mode 100644 index 000000000000..72c57d8b1a4c --- /dev/null +++ b/spring-reactive-modules/spring-reactive-4/src/test/java/com/baeldung/switchIfEmpty/controller/UserControllerTest.java @@ -0,0 +1,140 @@ +package com.baeldung.switchIfEmpty.controller; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.reactive.server.WebTestClient; + +import com.baeldung.switchIfEmpty.model.User; +import com.baeldung.switchIfEmpty.service.UserService; +import com.fasterxml.jackson.databind.ObjectMapper; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class UserControllerTest { + + @Autowired + private WebTestClient webTestClient; + + @Autowired + private UserService userService; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private UserController userController; + + private Map usersCache; + + protected ListAppender listAppender; + + @BeforeEach + void setLogger() { + Logger logger = (Logger) LoggerFactory.getLogger(UserService.class); + logger.setLevel(Level.DEBUG); + listAppender = new ListAppender<>(); + logger.addAppender(listAppender); + listAppender.start(); + } + + @Test + void givenUserDataIsAvailableInCache_whenUserByIdIsRequestedWithDeferParameter_thenCachedResponseShouldBeRetrieved() { + usersCache = new HashMap<>(); + User cachedUser = new User("66b29672e6f99a7156cc4ada", "gwen_dodson@beadzza.bmw", "boyle94", "admin"); + usersCache.put("66b29672e6f99a7156cc4ada", cachedUser); + userService.getUsers() + .putAll(usersCache); + + webTestClient.get() + .uri("/api/v1/user/66b29672e6f99a7156cc4ada?withDefer=true") + .exchange() + .expectStatus() + .isOk() + .expectBody(String.class) + .isEqualTo("{\"id\":\"66b29672e6f99a7156cc4ada\"," + "\"email\":\"gwen_dodson@beadzza.bmw\",\"username\":\"boyle94\",\"roles\":\"admin\"}"); + + assertTrue(listAppender.list.stream() + .anyMatch(e -> e.toString() + .contains("Fetched user 66b29672e6f99a7156cc4ada from cache"))); + + assertTrue(listAppender.list.stream() + .noneMatch(e -> e.toString() + .contains("Fetched user 66b29672e6f99a7156cc4ada from file"))); + } + + @Test + void givenUserDataIsAvailableInCache_whenUserByIdIsRequestedWithoutDeferParameter_thenUserIsFetchedFromFileInAdditionToCache() { + usersCache = new HashMap<>(); + User cachedUser1 = new User("66b29672e6f99a7156cc4ada", "gwen_dodson@beadzza.bmw", "boyle94", "admin"); + usersCache.put("66b29672e6f99a7156cc4ada", cachedUser1); + userService.getUsers() + .putAll(usersCache); + + webTestClient.get() + .uri("/api/v1/user/66b29672e6f99a7156cc4ada?withDefer=false") + .exchange() + .expectStatus() + .isOk() + .expectBody(String.class) + .isEqualTo("{\"id\":\"66b29672e6f99a7156cc4ada\"," + "\"email\":\"gwen_dodson@beadzza.bmw\",\"username\":\"boyle94\",\"roles\":\"admin\"}"); + + assertTrue(listAppender.list.stream() + .anyMatch(e -> e.toString() + .contains("Fetched user 66b29672e6f99a7156cc4ada from file"))); + + assertTrue(listAppender.list.stream() + .anyMatch(e -> e.toString() + .contains("Fetched user 66b29672e6f99a7156cc4ada from cache"))); + } + + @Test + void givenUserDataIsNotAvailableInCache_whenUserByIdIsRequestedWithDeferParameter_thenFileResponseShouldBeRetrieved() { + webTestClient.get() + .uri("/api/v1/user/66b29672e6f99a7156cc4ada?withDefer=true") + .exchange() + .expectStatus() + .isOk() + .expectBody(String.class) + .isEqualTo("{\"id\":\"66b29672e6f99a7156cc4ada\",\"email\":\"gwen_dodson@beadzza.bmw\",\"username\":\"boyle94\",\"roles\":\"admin\"}"); + + assertTrue(listAppender.list.stream() + .anyMatch(e -> e.toString() + .contains("Fetched user 66b29672e6f99a7156cc4ada from file"))); + + assertTrue(listAppender.list.stream() + .noneMatch(e -> e.toString() + .contains("Fetched user 66b29672e6f99a7156cc4ada from cache"))); + } + + @Test + void givenUserDataIsNotAvailableInCache_whenUserByIdIsRequestedWithoutDeferParameter_thenFileResponseShouldBeRetrieved() { + webTestClient.get() + .uri("/api/v1/user/66b29672e6f99a7156cc4ada?withDefer=false") + .exchange() + .expectStatus() + .isOk() + .expectBody(String.class) + .isEqualTo("{\"id\":\"66b29672e6f99a7156cc4ada\"," + "\"email\":\"gwen_dodson@beadzza.bmw\",\"username\":\"boyle94\",\"roles\":\"admin\"}"); + + assertTrue(listAppender.list.stream() + .anyMatch(e -> e.toString() + .contains("Fetched user 66b29672e6f99a7156cc4ada from file"))); + + assertTrue(listAppender.list.stream() + .noneMatch(e -> e.toString() + .contains("Fetched user 66b29672e6f99a7156cc4ada from cache"))); + } + +}