Skip to content

Commit 19af0c4

Browse files
committed
Added caching comic requests [#2414]
1 parent 7df4f62 commit 19af0c4

File tree

3 files changed

+215
-10
lines changed

3 files changed

+215
-10
lines changed

comixed-model/src/main/java/org/comixedproject/model/net/library/LoadComicsByFilterRequest.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package org.comixedproject.model.net.library;
2020

2121
import com.fasterxml.jackson.annotation.JsonProperty;
22+
import java.util.Objects;
2223
import lombok.AllArgsConstructor;
2324
import lombok.Getter;
2425
import lombok.NoArgsConstructor;
@@ -95,6 +96,47 @@ public class LoadComicsByFilterRequest {
9596
@Getter
9697
private String sortDirection;
9798

99+
@Override
100+
public boolean equals(final Object o) {
101+
if (o == null || getClass() != o.getClass()) return false;
102+
final LoadComicsByFilterRequest that = (LoadComicsByFilterRequest) o;
103+
return getPageSize() == that.getPageSize()
104+
&& getPageIndex() == that.getPageIndex()
105+
&& Objects.equals(getCoverYear(), that.getCoverYear())
106+
&& Objects.equals(getCoverMonth(), that.getCoverMonth())
107+
&& getArchiveType() == that.getArchiveType()
108+
&& getComicType() == that.getComicType()
109+
&& getComicState() == that.getComicState()
110+
&& Objects.equals(getUnscrapedState(), that.getUnscrapedState())
111+
&& Objects.equals(getSearchText(), that.getSearchText())
112+
&& Objects.equals(getPublisher(), that.getPublisher())
113+
&& Objects.equals(getSeries(), that.getSeries())
114+
&& Objects.equals(getVolume(), that.getVolume())
115+
&& Objects.equals(getPageCount(), that.getPageCount())
116+
&& Objects.equals(getSortBy(), that.getSortBy())
117+
&& Objects.equals(getSortDirection(), that.getSortDirection());
118+
}
119+
120+
@Override
121+
public int hashCode() {
122+
return Objects.hash(
123+
getPageSize(),
124+
getPageIndex(),
125+
getCoverYear(),
126+
getCoverMonth(),
127+
getArchiveType(),
128+
getComicType(),
129+
getComicState(),
130+
getUnscrapedState(),
131+
getSearchText(),
132+
getPublisher(),
133+
getSeries(),
134+
getVolume(),
135+
getPageCount(),
136+
getSortBy(),
137+
getSortDirection());
138+
}
139+
98140
@Override
99141
public String toString() {
100142
return "LoadComicDetailsRequest{"

comixed-rest/src/main/java/org/comixedproject/rest/library/DisplayableComicController.java

Lines changed: 81 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@
2424
import io.micrometer.core.annotation.Timed;
2525
import jakarta.servlet.http.HttpSession;
2626
import java.security.Principal;
27-
import java.util.Collections;
28-
import java.util.List;
27+
import java.util.*;
28+
import java.util.concurrent.ConcurrentHashMap;
29+
import lombok.AllArgsConstructor;
30+
import lombok.Getter;
2931
import lombok.extern.log4j.Log4j2;
32+
import org.comixedproject.model.comicbooks.ComicState;
3033
import org.comixedproject.model.comicbooks.ComicTagType;
3134
import org.comixedproject.model.library.DisplayableComic;
3235
import org.comixedproject.model.net.library.*;
@@ -40,10 +43,16 @@
4043
import org.comixedproject.service.lists.ReadingListService;
4144
import org.comixedproject.service.user.ComiXedUserException;
4245
import org.comixedproject.service.user.UserService;
46+
import org.comixedproject.state.comicbooks.ComicEvent;
47+
import org.comixedproject.state.comicbooks.ComicStateChangeListener;
48+
import org.comixedproject.state.comicbooks.ComicStateHandler;
4349
import org.comixedproject.views.View;
50+
import org.springframework.beans.factory.InitializingBean;
4451
import org.springframework.beans.factory.annotation.Autowired;
4552
import org.springframework.http.MediaType;
53+
import org.springframework.messaging.Message;
4654
import org.springframework.security.access.prepost.PreAuthorize;
55+
import org.springframework.statemachine.state.State;
4756
import org.springframework.web.bind.annotation.PathVariable;
4857
import org.springframework.web.bind.annotation.PostMapping;
4958
import org.springframework.web.bind.annotation.RequestBody;
@@ -57,12 +66,30 @@
5766
*/
5867
@RestController
5968
@Log4j2
60-
public class DisplayableComicController {
69+
public class DisplayableComicController implements InitializingBean, ComicStateChangeListener {
6170
@Autowired private DisplayableComicService displayableComicService;
6271
@Autowired private ComicBookService comicBookService;
6372
@Autowired private ComicSelectionService comicSelectionService;
6473
@Autowired private UserService userService;
6574
@Autowired private ReadingListService readingListService;
75+
@Autowired private ComicStateHandler comicStateHandler;
76+
77+
Map<LoadComicsByFilterRequest, LoadComicsResponse> filterCache = new ConcurrentHashMap<>();
78+
Map<TagTypeAndValue, LoadComicsResponse> tagAndValueCache = new ConcurrentHashMap<>();
79+
80+
@Override
81+
public void afterPropertiesSet() {
82+
log.trace("Registering for comic state change updates");
83+
this.comicStateHandler.addListener(this);
84+
}
85+
86+
@Override
87+
public void onComicStateChange(
88+
final State<ComicState, ComicEvent> state, final Message<ComicEvent> message) {
89+
log.debug("Cleaning comic caches");
90+
this.filterCache.clear();
91+
this.tagAndValueCache.clear();
92+
}
6693

6794
/**
6895
* Loads one page's worth of displayable comics.
@@ -80,6 +107,10 @@ public class DisplayableComicController {
80107
public LoadComicsResponse loadComicsByFilter(
81108
@RequestBody() final LoadComicsByFilterRequest request) {
82109
log.info("Loading comics: {}", request);
110+
if (this.filterCache.containsKey(request)) {
111+
log.info("Cache hit for request");
112+
return this.filterCache.get(request);
113+
}
83114
final List<DisplayableComic> comics =
84115
this.displayableComicService.loadComicsByFilter(
85116
request.getPageSize(),
@@ -133,7 +164,10 @@ public LoadComicsResponse loadComicsByFilter(
133164
request.getVolume(),
134165
request.getPageCount());
135166
final long totalCount = this.comicBookService.getComicBookCount();
136-
return new LoadComicsResponse(comics, coverYears, coverMonths, totalCount, filterCount);
167+
final LoadComicsResponse response =
168+
new LoadComicsResponse(comics, coverYears, coverMonths, totalCount, filterCount);
169+
this.filterCache.put(request, response);
170+
return response;
137171
}
138172

139173
/**
@@ -192,17 +226,26 @@ public LoadComicsResponse loadComicsByTagTypeAndValue(
192226
final int pageIndex = request.getPageIndex();
193227
final String sortBy = request.getSortBy();
194228
final String sortDirection = request.getSortDirection();
229+
final TagTypeAndValue key =
230+
new TagTypeAndValue(tagType, tagValue, pageSize, pageIndex, sortBy, sortDirection);
231+
if (this.tagAndValueCache.containsKey(key)) {
232+
log.info("Cache hit for tag: {}={}", tagType, tagValue);
233+
return this.tagAndValueCache.get(key);
234+
}
195235
final List<DisplayableComic> comicDetails =
196236
this.displayableComicService.loadComicsByTagTypeAndValue(
197237
pageSize, pageIndex, tagType, tagValue, sortBy, sortDirection);
198238
final long filteredComics =
199239
this.displayableComicService.getComicCountForTagTypeAndValue(tagType, tagValue);
200-
return new LoadComicsResponse(
201-
comicDetails,
202-
this.displayableComicService.getCoverYearsForTagTypeAndValue(tagType, tagValue),
203-
this.displayableComicService.getCoverMonthsForTagTypeAndValue(tagType, tagValue),
204-
filteredComics,
205-
filteredComics);
240+
final LoadComicsResponse result =
241+
new LoadComicsResponse(
242+
comicDetails,
243+
this.displayableComicService.getCoverYearsForTagTypeAndValue(tagType, tagValue),
244+
this.displayableComicService.getCoverMonthsForTagTypeAndValue(tagType, tagValue),
245+
filteredComics,
246+
filteredComics);
247+
this.tagAndValueCache.put(key, result);
248+
return result;
206249
}
207250

208251
/**
@@ -307,4 +350,32 @@ public LoadComicsResponse loadComicsForList(
307350
return new LoadComicsResponse(
308351
comics, Collections.emptyList(), Collections.emptyList(), filterCount, filterCount);
309352
}
353+
354+
@AllArgsConstructor
355+
static class TagTypeAndValue {
356+
@Getter private ComicTagType tagType;
357+
@Getter private String value;
358+
@Getter private int pageSize;
359+
@Getter private int pageIndex;
360+
@Getter private String sortBy;
361+
@Getter private String sortDirection;
362+
363+
@Override
364+
public boolean equals(final Object o) {
365+
if (o == null || getClass() != o.getClass()) return false;
366+
final TagTypeAndValue that = (TagTypeAndValue) o;
367+
return getPageSize() == that.getPageSize()
368+
&& getPageIndex() == that.getPageIndex()
369+
&& getTagType() == that.getTagType()
370+
&& Objects.equals(getValue(), that.getValue())
371+
&& Objects.equals(getSortBy(), that.getSortBy())
372+
&& Objects.equals(getSortDirection(), that.getSortDirection());
373+
}
374+
375+
@Override
376+
public int hashCode() {
377+
return Objects.hash(
378+
getTagType(), getValue(), getPageSize(), getPageIndex(), getSortBy(), getSortDirection());
379+
}
380+
}
310381
}

comixed-rest/src/test/java/org/comixedproject/rest/library/DisplayableComicControllerTest.java

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
import org.comixedproject.service.lists.ReadingListService;
4545
import org.comixedproject.service.user.ComiXedUserException;
4646
import org.comixedproject.service.user.UserService;
47+
import org.comixedproject.state.comicbooks.ComicEvent;
48+
import org.comixedproject.state.comicbooks.ComicStateHandler;
4749
import org.junit.jupiter.api.BeforeEach;
4850
import org.junit.jupiter.api.Test;
4951
import org.junit.jupiter.api.extension.ExtendWith;
@@ -53,6 +55,8 @@
5355
import org.mockito.junit.jupiter.MockitoExtension;
5456
import org.mockito.junit.jupiter.MockitoSettings;
5557
import org.mockito.quality.Strictness;
58+
import org.springframework.messaging.Message;
59+
import org.springframework.statemachine.state.State;
5660

5761
@ExtendWith(MockitoExtension.class)
5862
@MockitoSettings(strictness = Strictness.LENIENT)
@@ -79,6 +83,14 @@ class DisplayableComicControllerTest {
7983
private static final ComicTagType TEST_TAG_TYPE =
8084
ComicTagType.values()[RandomUtils.nextInt(ComicTagType.values().length)];
8185
private static final String TEST_TAG_VALUE = "tag.value";
86+
public static final DisplayableComicController.TagTypeAndValue TEST_TAG_VALUE_AND_TYPE_KEY =
87+
new DisplayableComicController.TagTypeAndValue(
88+
TEST_TAG_TYPE,
89+
TEST_TAG_VALUE,
90+
TEST_PAGE_SIZE,
91+
TEST_PAGE_INDEX,
92+
TEST_SORT_BY,
93+
TEST_SORT_DIRECTION);
8294
private static final String TEST_EMAIL = "[email protected]";
8395
private static final int TEST_READ_COMIC_COUNT = 275;
8496
private static final long TEST_READING_LIST_ID = 293L;
@@ -89,6 +101,7 @@ class DisplayableComicControllerTest {
89101
@Mock private ComicSelectionService comicSelectionService;
90102
@Mock private UserService userService;
91103
@Mock private ReadingListService readingListService;
104+
@Mock private ComicStateHandler comicStateHandler;
92105

93106
@Mock private LoadComicsByFilterRequest filteredRequest;
94107
@Mock private List<DisplayableComic> comicList;
@@ -100,6 +113,9 @@ class DisplayableComicControllerTest {
100113
@Mock private Principal principal;
101114
@Mock private ComiXedUser user;
102115
@Mock private Set<Long> comicBooksRead;
116+
@Mock private State<ComicState, ComicEvent> state;
117+
@Mock private Message<ComicEvent> message;
118+
@Mock private LoadComicsResponse loadComicsResponse;
103119

104120
@BeforeEach
105121
void setUp() throws ComicBookSelectionException, ComiXedUserException {
@@ -136,6 +152,24 @@ void setUp() throws ComicBookSelectionException, ComiXedUserException {
136152
Mockito.when(userService.findByEmail(Mockito.anyString())).thenReturn(user);
137153
}
138154

155+
@Test
156+
void afterPropertiesSet() {
157+
controller.afterPropertiesSet();
158+
159+
Mockito.verify(comicStateHandler, Mockito.times(1)).addListener(controller);
160+
}
161+
162+
@Test
163+
void onComicStateChange() {
164+
controller.filterCache.put(filteredRequest, loadComicsResponse);
165+
controller.tagAndValueCache.put(TEST_TAG_VALUE_AND_TYPE_KEY, loadComicsResponse);
166+
167+
controller.onComicStateChange(state, message);
168+
169+
assertTrue(controller.filterCache.isEmpty());
170+
assertTrue(controller.tagAndValueCache.isEmpty());
171+
}
172+
139173
@Test
140174
void loadComicsByFilter() {
141175
Mockito.when(
@@ -224,6 +258,34 @@ void loadComicsByFilter() {
224258
TEST_SORT_DIRECTION);
225259
}
226260

261+
@Test
262+
void loadComicsByFilter_cached() {
263+
controller.filterCache.put(filteredRequest, loadComicsResponse);
264+
265+
final LoadComicsResponse result = controller.loadComicsByFilter(filteredRequest);
266+
267+
assertNotNull(result);
268+
assertSame(loadComicsResponse, result);
269+
270+
Mockito.verify(displayableComicService, Mockito.never())
271+
.loadComicsByFilter(
272+
Mockito.any(),
273+
Mockito.any(),
274+
Mockito.any(),
275+
Mockito.any(),
276+
Mockito.any(),
277+
Mockito.any(),
278+
Mockito.any(),
279+
Mockito.any(),
280+
Mockito.any(),
281+
Mockito.any(),
282+
Mockito.any(),
283+
Mockito.any(),
284+
Mockito.any(),
285+
Mockito.any(),
286+
Mockito.any());
287+
}
288+
227289
@Test
228290
void loadComicsBySelectedState() throws ComicBookSelectionException {
229291
Mockito.when(
@@ -304,6 +366,36 @@ void loadComicsByTagTypeAndValue() {
304366
.getComicCountForTagTypeAndValue(TEST_TAG_TYPE, TEST_TAG_VALUE);
305367
}
306368

369+
@Test
370+
void loadComicsByTagTypeAndValue_cached() {
371+
controller.tagAndValueCache.put(TEST_TAG_VALUE_AND_TYPE_KEY, loadComicsResponse);
372+
373+
final LoadComicsResponse result =
374+
controller.loadComicsByTagTypeAndValue(
375+
new LoadComicsForCollectionRequest(
376+
TEST_PAGE_SIZE, TEST_PAGE_INDEX, TEST_SORT_BY, TEST_SORT_DIRECTION),
377+
TEST_TAG_TYPE,
378+
TEST_TAG_VALUE);
379+
380+
assertNotNull(result);
381+
assertSame(loadComicsResponse, result);
382+
383+
Mockito.verify(displayableComicService, Mockito.never())
384+
.loadComicsByTagTypeAndValue(
385+
Mockito.anyInt(),
386+
Mockito.anyInt(),
387+
Mockito.any(),
388+
Mockito.any(),
389+
Mockito.any(),
390+
Mockito.any());
391+
Mockito.verify(displayableComicService, Mockito.never())
392+
.getCoverYearsForTagTypeAndValue(Mockito.any(), Mockito.any());
393+
Mockito.verify(displayableComicService, Mockito.never())
394+
.getCoverYearsForTagTypeAndValue(Mockito.any(), Mockito.any());
395+
Mockito.verify(displayableComicService, Mockito.never())
396+
.getComicCountForTagTypeAndValue(Mockito.any(), Mockito.any());
397+
}
398+
307399
@Test
308400
void loadUnreadComics() throws ComiXedUserException {
309401
Mockito.when(

0 commit comments

Comments
 (0)