diff --git a/pom.xml b/pom.xml
index 1849179..3d90867 100644
--- a/pom.xml
+++ b/pom.xml
@@ -31,6 +31,16 @@
spring-data-commons
3.3.0
+
+ org.springframework.boot
+ spring-boot-starter-cache
+
+
+
+ com.github.ben-manes.caffeine
+ caffeine
+ 3.1.8
+
org.postgresql
diff --git a/src/main/java/edu/harvard/dbmi/avillach/dictionary/DictionaryApplication.java b/src/main/java/edu/harvard/dbmi/avillach/dictionary/DictionaryApplication.java
index c12b6eb..40fee56 100644
--- a/src/main/java/edu/harvard/dbmi/avillach/dictionary/DictionaryApplication.java
+++ b/src/main/java/edu/harvard/dbmi/avillach/dictionary/DictionaryApplication.java
@@ -2,8 +2,10 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
+@EnableCaching
public class DictionaryApplication {
public static void main(String[] args) {
diff --git a/src/main/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptService.java b/src/main/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptService.java
index f22b65e..edd1e4d 100644
--- a/src/main/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptService.java
+++ b/src/main/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptService.java
@@ -6,6 +6,7 @@
import edu.harvard.dbmi.avillach.dictionary.concept.model.ContinuousConcept;
import edu.harvard.dbmi.avillach.dictionary.filter.Filter;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
@@ -26,6 +27,7 @@ public ConceptService(ConceptRepository conceptRepository, ConceptDecoratorServi
this.conceptDecoratorService = conceptDecoratorService;
}
+ @Cacheable("concepts")
public List listConcepts(Filter filter, Pageable page) {
return conceptRepository.getConcepts(filter, page);
}
@@ -40,6 +42,7 @@ public List listDetailedConcepts(Filter filter, Pageable page) {
}).toList();
}
+ @Cacheable("concepts_count")
public long countConcepts(Filter filter) {
return conceptRepository.countConcepts(filter);
}
diff --git a/src/main/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetService.java b/src/main/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetService.java
index b330865..3ad2aac 100644
--- a/src/main/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetService.java
+++ b/src/main/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetService.java
@@ -2,6 +2,7 @@
import edu.harvard.dbmi.avillach.dictionary.filter.Filter;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.List;
@@ -17,6 +18,7 @@ public FacetService(FacetRepository repository) {
this.repository = repository;
}
+ @Cacheable("facets")
public List getFacets(Filter filter) {
return repository.getFacets(filter);
}
diff --git a/src/main/java/edu/harvard/dbmi/avillach/dictionary/facet/FilterPreProcessor.java b/src/main/java/edu/harvard/dbmi/avillach/dictionary/facet/FilterPreProcessor.java
index 483dc02..98357dc 100644
--- a/src/main/java/edu/harvard/dbmi/avillach/dictionary/facet/FilterPreProcessor.java
+++ b/src/main/java/edu/harvard/dbmi/avillach/dictionary/facet/FilterPreProcessor.java
@@ -10,6 +10,10 @@
import java.io.IOException;
import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.function.Function;
@ControllerAdvice
public class FilterPreProcessor implements RequestBodyAdvice {
@@ -30,8 +34,23 @@ public Object afterBodyRead(
Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
Class extends HttpMessageConverter>> converterType
) {
- if (body instanceof Filter filter && StringUtils.hasLength(filter.search())) {
- return new Filter(filter.facets(), filter.search().replaceAll("_", "/"), filter.consents());
+ if (body instanceof Filter filter) {
+ List newFacets = filter.facets();
+ List newConsents = filter.consents();
+ if (filter.facets() != null) {
+ newFacets = new ArrayList<>(filter.facets());
+ newFacets.sort(Comparator.comparing(Facet::name));
+ }
+ if (filter.consents() != null) {
+ newConsents = new ArrayList<>(newConsents);
+ newConsents.sort(Comparator.comparing(Function.identity()));
+ }
+ filter = new Filter(newFacets, filter.search(), newConsents);
+
+ if (StringUtils.hasLength(filter.search())) {
+ filter = new Filter(filter.facets(), filter.search().replaceAll("_", "/"), filter.consents());
+ }
+ return filter;
}
return body;
}
diff --git a/src/main/java/edu/harvard/dbmi/avillach/dictionary/util/CacheConfig.java b/src/main/java/edu/harvard/dbmi/avillach/dictionary/util/CacheConfig.java
new file mode 100644
index 0000000..a383929
--- /dev/null
+++ b/src/main/java/edu/harvard/dbmi/avillach/dictionary/util/CacheConfig.java
@@ -0,0 +1,24 @@
+package edu.harvard.dbmi.avillach.dictionary.util;
+
+import com.github.benmanes.caffeine.cache.Caffeine;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.caffeine.CaffeineCacheManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.concurrent.TimeUnit;
+
+@Configuration
+public class CacheConfig {
+ @Bean
+ public Caffeine caffeineConfig() {
+ return Caffeine.newBuilder().expireAfterAccess(15, TimeUnit.MINUTES).maximumSize(5000);
+ }
+
+ @Bean
+ public CacheManager cacheManager(Caffeine caffeine) {
+ CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
+ caffeineCacheManager.setCaffeine(caffeine);
+ return caffeineCacheManager;
+ }
+}
diff --git a/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptServiceTest.java b/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptServiceTest.java
index ba1b9cb..0238e0c 100644
--- a/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptServiceTest.java
+++ b/src/test/java/edu/harvard/dbmi/avillach/dictionary/concept/ConceptServiceTest.java
@@ -6,6 +6,7 @@
import edu.harvard.dbmi.avillach.dictionary.concept.model.ContinuousConcept;
import edu.harvard.dbmi.avillach.dictionary.filter.Filter;
import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
@@ -38,6 +39,8 @@ void shouldListConcepts() {
Mockito.when(repository.getConcepts(filter, page)).thenReturn(expected);
List actual = subject.listConcepts(filter, page);
+ subject.listConcepts(filter, page);
+ Mockito.verify(repository, Mockito.times(1)).getConcepts(filter, page);
Assertions.assertEquals(expected, actual);
}
@@ -48,7 +51,9 @@ void shouldCountConcepts() {
Mockito.when(repository.countConcepts(filter)).thenReturn(1L);
long actual = subject.countConcepts(filter);
+ subject.countConcepts(filter);
+ Mockito.verify(repository, Mockito.times(1)).countConcepts(filter);
Assertions.assertEquals(1L, actual);
}
diff --git a/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetServiceTest.java b/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetServiceTest.java
index 84da8a8..e9c3aab 100644
--- a/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetServiceTest.java
+++ b/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FacetServiceTest.java
@@ -4,6 +4,7 @@
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
+import org.mockito.internal.verification.Times;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
@@ -28,6 +29,8 @@ void shouldGetFacets() {
Mockito.when(repository.getFacets(filter)).thenReturn(expected);
List actual = subject.getFacets(filter);
+ subject.getFacets(filter);
+ Mockito.verify(repository, Mockito.times(1)).getFacets(filter);
Assertions.assertEquals(expected, actual);
}
diff --git a/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FilterPreProcessorTest.java b/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FilterPreProcessorTest.java
index 5fa6150..6d3d452 100644
--- a/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FilterPreProcessorTest.java
+++ b/src/test/java/edu/harvard/dbmi/avillach/dictionary/facet/FilterPreProcessorTest.java
@@ -18,6 +18,19 @@ class FilterPreProcessorTest {
@Autowired
private FilterPreProcessor subject;
+ @Test
+ void shouldSortFilter() {
+ Filter filter = new Filter(List.of(new Facet("b", ""), new Facet("a", "")), "", List.of("c", "b", "a"));
+ Filter actual = (Filter) subject.afterBodyRead(
+ filter, Mockito.mock(HttpInputMessage.class), Mockito.mock(MethodParameter.class), SimpleType.constructUnsafe(Filter.class),
+ null
+ );
+
+ Filter expected = new Filter(List.of(new Facet("a", ""), new Facet("b", "")), "", List.of("a", "b", "c"));
+ Assertions.assertEquals(expected, actual);
+
+ }
+
@Test
void shouldProcessFilter() {
Object processedFilter = subject.afterBodyRead(