From 6a32c3e954e2e4a12584dd056c2963cbdfaaa90b Mon Sep 17 00:00:00 2001 From: Martin Kuba Date: Fri, 4 Oct 2024 15:56:04 +0200 Subject: [PATCH] fixed paging for newer Spring --- README.md | 2 +- .../main/java/cz/muni/chat/client/Main.java | 17 ++-- .../chat/generated/server/api/ChatImpl.java | 45 +++------ .../cz/muni/chat/server/rest/ApiConfig.java | 6 ++ .../java/cz/muni/chat/server/rest/ChatIT.java | 9 +- .../rest/ChatRestControllerUnitTests.java | 3 + openapi.yaml | 91 +++++-------------- 7 files changed, 60 insertions(+), 113 deletions(-) diff --git a/README.md b/README.md index be7ad2f..901c982 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ This project demonstrates the following features: ## Installation -Prerequisites: git, [Apache Maven](https://maven.apache.org/) and JDK 17+ +Prerequisites: git, [Apache Maven](https://maven.apache.org/) and JDK 21+ Download and compile: ```bash diff --git a/chat-client-java/src/main/java/cz/muni/chat/client/Main.java b/chat-client-java/src/main/java/cz/muni/chat/client/Main.java index 3f90ddf..4b8ac4d 100644 --- a/chat-client-java/src/main/java/cz/muni/chat/client/Main.java +++ b/chat-client-java/src/main/java/cz/muni/chat/client/Main.java @@ -5,7 +5,8 @@ import cz.muni.chat.client.model.BackgroundColorEnum; import cz.muni.chat.client.model.ChatMessage; import cz.muni.chat.client.model.NewChatMessageRequest; -import cz.muni.chat.client.model.PageChatMessage; +import cz.muni.chat.client.model.PageMetadata; +import cz.muni.chat.client.model.PagedModelChatMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.CommandLineRunner; @@ -59,13 +60,13 @@ public void run(String... args) throws Exception { //get paged messages int pageIndex = 2; int pageSize = 3; - PageChatMessage paged = chat.paged(pageIndex, pageSize, null); - log.info("paged messages: page={}/{} offset={} items={}/{} total={}", - paged.getNumber() + 1, paged.getTotalPages(), - paged.getPageable().getOffset(), - paged.getNumberOfElements(), paged.getSize(), - paged.getTotalElements()); - for (ChatMessage chatMessage : paged.getContent()) { + PagedModelChatMessage pagedModel = chat.paged(pageIndex, pageSize, null); + PageMetadata page = pagedModel.getPage(); + log.info("paged messages: page={}/{} page.size={} totalElements={}", + page.getNumber() + 1, page.getTotalPages(), + page.getSize(), + page.getTotalElements()); + for (ChatMessage chatMessage : pagedModel.getContent()) { log.info("msg: {}", chatMessage); } diff --git a/chat-server-generated/src/main/java/cz/muni/chat/generated/server/api/ChatImpl.java b/chat-server-generated/src/main/java/cz/muni/chat/generated/server/api/ChatImpl.java index 7d19a4f..283c2b7 100644 --- a/chat-server-generated/src/main/java/cz/muni/chat/generated/server/api/ChatImpl.java +++ b/chat-server-generated/src/main/java/cz/muni/chat/generated/server/api/ChatImpl.java @@ -5,9 +5,8 @@ import cz.muni.chat.generated.server.model.ErrorMessage; import cz.muni.chat.generated.server.model.NewChatMessageRequest; import cz.muni.chat.generated.server.model.NewChatMessageRequest.TextColorEnum; -import cz.muni.chat.generated.server.model.PageChatMessage; -import cz.muni.chat.generated.server.model.PageableObject; -import cz.muni.chat.generated.server.model.SortObject; +import cz.muni.chat.generated.server.model.PageMetadata; +import cz.muni.chat.generated.server.model.PagedModelChatMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.domain.Page; @@ -37,7 +36,7 @@ public ResponseEntity> getAllMessages() { return new ResponseEntity<>(messages, HttpStatus.OK); } - @SuppressWarnings({"rawtypes","unchecked"}) + @SuppressWarnings({"rawtypes", "unchecked"}) @Override public ResponseEntity getMessage(String id) { log.debug("getMessage({})", id); @@ -48,7 +47,7 @@ public ResponseEntity getMessage(String id) { ErrorMessage errorMessage = new ErrorMessage() .error(HttpStatus.NOT_FOUND.getReasonPhrase()) .status(HttpStatus.NOT_FOUND.value()) - .path("/api/message/"+id) + .path("/api/message/" + id) .timestamp(OffsetDateTime.now()) .message("message with id=" + id + " not found"); return new ResponseEntity(errorMessage, HttpStatus.NOT_FOUND); @@ -70,40 +69,22 @@ public ResponseEntity createMessage(NewChatMessageRequest r, String } @Override - public ResponseEntity paged(Integer page, Integer size, List sort) { + public ResponseEntity paged(Integer page, Integer size, List sort) { log.debug("paged(page={}, size={})", page, size); PageRequest p = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "timestamp")); List chatMessages = messages.stream().skip(p.getOffset()).limit(p.getPageSize()).toList(); Page m = new PageImpl<>(chatMessages, p, messages.size()); // copy to generated models :-( - List s = p.getSort().get().map(order -> new SortObject() - .property(order.getProperty()) - .ascending(order.isAscending()) - .direction(order.getDirection().name()) - .ignoreCase(order.isIgnoreCase()) - .nullHandling(order.getNullHandling().name()) - ).toList(); - PageableObject pageableObject = new PageableObject() - .paged(p.isPaged()) - .unpaged(p.isUnpaged()) - .pageNumber(p.getPageNumber()) - .pageSize(p.getPageSize()) - .sort(s) - .offset(p.getOffset()); - PageChatMessage pageChatMessage = new PageChatMessage() - .content(chatMessages) - .pageable(pageableObject) - .last(m.isLast()) - .first(m.isFirst()) - .empty(m.isEmpty()) - .totalPages(m.getTotalPages()) + PageMetadata pageMetadata = new PageMetadata() + .size((long) m.getSize()) + .number((long) m.getNumber()) .totalElements(m.getTotalElements()) - .number(m.getNumber()) - .numberOfElements(m.getNumberOfElements()) - .size(m.getSize()) - .sort(s); - return new ResponseEntity<>(pageChatMessage, HttpStatus.OK); + .totalPages((long) m.getTotalPages()); + PagedModelChatMessage pagedModelChatMessage = new PagedModelChatMessage() + .content(chatMessages) + .page(pageMetadata); + return new ResponseEntity<>(pagedModelChatMessage, HttpStatus.OK); } private final List messages = new CopyOnWriteArrayList<>(); diff --git a/chat-server/src/main/java/cz/muni/chat/server/rest/ApiConfig.java b/chat-server/src/main/java/cz/muni/chat/server/rest/ApiConfig.java index 6f95690..d71310e 100644 --- a/chat-server/src/main/java/cz/muni/chat/server/rest/ApiConfig.java +++ b/chat-server/src/main/java/cz/muni/chat/server/rest/ApiConfig.java @@ -13,8 +13,14 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.event.EventListener; +import org.springframework.data.web.config.EnableSpringDataWebSupport; + +import static org.springframework.data.web.config.EnableSpringDataWebSupport.PageSerializationMode.VIA_DTO; @Configuration +// fix for paging +// see https://docs.spring.io/spring-data/commons/reference/repositories/core-extensions.html#core.web.page +@EnableSpringDataWebSupport(pageSerializationMode = VIA_DTO) public class ApiConfig { private static final Logger log = LoggerFactory.getLogger(ApiConfig.class); diff --git a/chat-server/src/test/java/cz/muni/chat/server/rest/ChatIT.java b/chat-server/src/test/java/cz/muni/chat/server/rest/ChatIT.java index aec6dc7..90f14b8 100644 --- a/chat-server/src/test/java/cz/muni/chat/server/rest/ChatIT.java +++ b/chat-server/src/test/java/cz/muni/chat/server/rest/ChatIT.java @@ -84,11 +84,10 @@ void testPaging() throws Exception { int pageSize = 3; mockMvc.perform(get("/api/paged?page={page}&size={size}", pageIndex, pageSize).contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) - .andExpect(jsonPath("$.pageable.paged").value(true)) - .andExpect(jsonPath("$.pageable.pageNumber").value(pageIndex)) - .andExpect(jsonPath("$.pageable.pageSize").value(pageSize)) - .andExpect(jsonPath("$.totalElements").value(totalMessages)) - .andExpect(jsonPath("$.numberOfElements").value(pageSize)) + .andExpect(jsonPath("$.page.size").value(pageSize)) + .andExpect(jsonPath("$.page.number").value(pageIndex)) + .andExpect(jsonPath("$.page.totalElements").value(totalMessages)) + .andExpect(jsonPath("$.page.totalPages").value(4)) .andExpect(jsonPath("$.content.length()").value(pageSize)) // .andDo(print()) ; diff --git a/chat-server/src/test/java/cz/muni/chat/server/rest/ChatRestControllerUnitTests.java b/chat-server/src/test/java/cz/muni/chat/server/rest/ChatRestControllerUnitTests.java index b600584..64ce75e 100644 --- a/chat-server/src/test/java/cz/muni/chat/server/rest/ChatRestControllerUnitTests.java +++ b/chat-server/src/test/java/cz/muni/chat/server/rest/ChatRestControllerUnitTests.java @@ -19,6 +19,7 @@ import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.data.web.config.EnableSpringDataWebSupport; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; @@ -29,6 +30,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; +import static org.springframework.data.web.config.EnableSpringDataWebSupport.PageSerializationMode.VIA_DTO; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -45,6 +47,7 @@ * and with mock implementation of Spring MVC that calls the RestController. */ @WebMvcTest(ChatRestController.class) +@EnableSpringDataWebSupport(pageSerializationMode = VIA_DTO) class ChatRestControllerUnitTests { private static final Logger log = LoggerFactory.getLogger(ChatRestControllerUnitTests.class); diff --git a/openapi.yaml b/openapi.yaml index 7467488..f427c65 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -59,7 +59,7 @@ paths: schema: type: array items: - $ref: '#/components/schemas/ChatMessage' + $ref: "#/components/schemas/ChatMessage" post: tags: - Chat @@ -83,17 +83,17 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/NewChatMessageRequest' + $ref: "#/components/schemas/NewChatMessageRequest" required: true responses: "201": - $ref: '#/components/responses/SingleMessageResponse' + $ref: "#/components/responses/SingleMessageResponse" "400": description: input data were not correct content: application/json: schema: - $ref: '#/components/schemas/ErrorMessage' + $ref: "#/components/schemas/ErrorMessage" /api/paged: get: tags: @@ -137,7 +137,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PageChatMessage' + $ref: "#/components/schemas/PagedModelChatMessage" /api/message/{id}: get: tags: @@ -153,13 +153,13 @@ paths: type: string responses: "200": - $ref: '#/components/responses/SingleMessageResponse' + $ref: "#/components/responses/SingleMessageResponse" "404": description: message not found content: application/json: schema: - $ref: '#/components/schemas/ErrorMessage' + $ref: "#/components/schemas/ErrorMessage" components: schemas: ErrorMessage: @@ -199,7 +199,7 @@ components: - aquamarine - lightyellow - lightblue - - '#ffe4c4' + - "#ffe4c4" NewChatMessageRequest: required: - text @@ -218,7 +218,7 @@ components: - blue - darkgrey backgroundColor: - $ref: '#/components/schemas/BackgroundColorEnum' + $ref: "#/components/schemas/BackgroundColorEnum" description: | Object for requesting new message. **Text** of the message must be located in the request body because URLs are limited in size. @@ -253,82 +253,39 @@ components: description: HTML color name or RGB hex code example: black backgroundColor: - $ref: '#/components/schemas/BackgroundColorEnum' + $ref: "#/components/schemas/BackgroundColorEnum" description: represents a message in a chat - PageChatMessage: + PageMetadata: type: object properties: - totalPages: - type: integer - format: int32 - totalElements: - type: integer - format: int64 - pageable: - $ref: '#/components/schemas/PageableObject' - first: - type: boolean - last: - type: boolean size: type: integer - format: int32 - content: - type: array - items: - $ref: '#/components/schemas/ChatMessage' + format: int64 number: type: integer - format: int32 - sort: - type: array - items: - $ref: '#/components/schemas/SortObject' - numberOfElements: - type: integer - format: int32 - empty: - type: boolean - PageableObject: - type: object - properties: - pageNumber: - type: integer - format: int32 - pageSize: + format: int64 + totalElements: type: integer - format: int32 - offset: + format: int64 + totalPages: type: integer format: int64 - sort: - type: array - items: - $ref: '#/components/schemas/SortObject' - paged: - type: boolean - unpaged: - type: boolean - SortObject: + PagedModelChatMessage: type: object properties: - direction: - type: string - nullHandling: - type: string - ascending: - type: boolean - property: - type: string - ignoreCase: - type: boolean + content: + type: array + items: + $ref: "#/components/schemas/ChatMessage" + page: + $ref: "#/components/schemas/PageMetadata" responses: SingleMessageResponse: description: response containing a single message content: application/json: schema: - $ref: '#/components/schemas/ChatMessage' + $ref: "#/components/schemas/ChatMessage" links: link_to_getMessage: operationId: getMessage