Commit 0e81fdbb authored by Jean-Baptiste Nizet's avatar Jean-Baptiste Nizet
Browse files

refactor: move search in DAO, and add page support

parent 70ad16a9
......@@ -6,5 +6,5 @@ import org.springframework.data.elasticsearch.repository.ElasticsearchRepository
/**
* DAO for {@link GeneticResource}
*/
public interface GeneticResourceDao extends ElasticsearchRepository<GeneticResource, String> {
public interface GeneticResourceDao extends ElasticsearchRepository<GeneticResource, String>, GeneticResourceDaoCustom {
}
package fr.inra.urgi.rare.dao;
import fr.inra.urgi.rare.domain.GeneticResource;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
/**
* Custom methods of the {@link GeneticResourceDao}
* @author JB Nizet
*/
public interface GeneticResourceDaoCustom {
/**
* Searches for the given text anywhere (except in identifier, URL and numeric fields) in the genetic resources,
* and returns the requested page (results are sorted by score, in descending order)
*/
Page<GeneticResource> search(String query, Pageable page);
}
package fr.inra.urgi.rare.dao;
import static org.elasticsearch.index.query.QueryBuilders.multiMatchQuery;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import fr.inra.urgi.rare.domain.GeneticResource;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
/**
* Implementation of {@link GeneticResourceDaoCustom}
* @author JB Nizet
*/
public class GeneticResourceDaoImpl implements GeneticResourceDaoCustom {
/**
* Contains the fields searchable on a {@link GeneticResource}.
* This is basically all fields at the exception of a few ones like `identifier`,
* and the ones containing a URL or a numeric value.
*/
private static final Set<String> SEARCHABLE_FIELDS = Collections.unmodifiableSet(Stream.of(
"name",
"description",
"pillarName",
"databaseSource",
"domain",
"taxon",
"family",
"genus",
"species",
"materialType",
"biotopeType",
"countryOfOrigin",
"countryOfCollect"
).collect(Collectors.toSet()));
private final ElasticsearchTemplate elasticsearchTemplate;
public GeneticResourceDaoImpl(ElasticsearchTemplate elasticsearchTemplate) {
this.elasticsearchTemplate = elasticsearchTemplate;
}
@Override
public Page<GeneticResource> search(String query, Pageable page) {
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(multiMatchQuery(query, SEARCHABLE_FIELDS.toArray(new String[0])))
.withPageable(page)
.build();
return elasticsearchTemplate.queryForPage(searchQuery, GeneticResource.class);
}
}
package fr.inra.urgi.rare.search;
import static org.elasticsearch.index.query.QueryBuilders.multiMatchQuery;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.Optional;
import fr.inra.urgi.rare.dao.GeneticResourceDao;
import fr.inra.urgi.rare.domain.GeneticResource;
import fr.inra.urgi.rare.dto.PageDTO;
import org.springframework.data.domain.Page;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.domain.PageRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
......@@ -24,26 +18,7 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/api/genetic-resources")
public class SearchController {
/**
* Contains the fields searchable on a {@link GeneticResource}.
* This is basically all fields at the exception of a few ones like `identifier`,
* and the ones containing a URL.
*/
private final Set<String> searchableFields = Stream.of(
"name",
"description",
"pillarName",
"databaseSource",
"domain",
"taxon",
"family",
"genus",
"species",
"materialType",
"biotopeType",
"countryOfOrigin",
"countryOfCollect"
).collect(Collectors.toSet());
public static final int PAGE_SIZE = 20;
private GeneticResourceDao geneticResourceDao;
......@@ -52,12 +27,7 @@ public class SearchController {
}
@GetMapping
public PageDTO<GeneticResource> search(@RequestParam("query") String query) {
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(multiMatchQuery(query, searchableFields.toArray(new String[0])))
.build();
Page<GeneticResource> geneticResources = geneticResourceDao.search(searchQuery);
return PageDTO.fromPage(geneticResources);
public PageDTO<GeneticResource> search(@RequestParam("query") String query, Optional<Integer> page) {
return PageDTO.fromPage(geneticResourceDao.search(query, PageRequest.of(page.orElse(0), PAGE_SIZE)));
}
}
......@@ -3,14 +3,19 @@ package fr.inra.urgi.rare.dao;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Collections;
import java.util.function.BiConsumer;
import fr.inra.urgi.rare.config.ElasticSearchConfig;
import fr.inra.urgi.rare.domain.GeneticResource;
import fr.inra.urgi.rare.domain.GeneticResourceBuilder;
import org.junit.jupiter.api.BeforeEach;
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.json.JsonTest;
import org.springframework.context.annotation.Import;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
......@@ -23,6 +28,13 @@ class GeneticResourceDaoTest {
@Autowired
private GeneticResourceDao geneticResourceDao;
private Pageable firstPage = PageRequest.of(0, 10);
@BeforeEach
void prepare() {
geneticResourceDao.deleteAll();
}
@Test
void shouldSaveAndGet() {
......@@ -56,5 +68,98 @@ class GeneticResourceDaoTest {
assertThat(geneticResourceDao.findById(geneticResource.getId()).get()).isEqualTo(geneticResource);
}
@Test
public void shouldSearchOnName() {
shouldSearch(GeneticResourceBuilder::withName);
}
@Test
public void shouldSearchOnDescription() {
shouldSearch(GeneticResourceBuilder::withDescription);
}
@Test
public void shouldSearchOnPillarName() {
shouldSearch(GeneticResourceBuilder::withPillarName);
}
@Test
public void shouldSearchOnDatabaseSource() {
shouldSearch(GeneticResourceBuilder::withDatabaseSource);
}
@Test
public void shouldSearchOnDomain() {
shouldSearch(GeneticResourceBuilder::withDomain);
}
@Test
public void shouldSearchOnTaxon() {
shouldSearch((b, s) -> b.withTaxon(Collections.singletonList(s)));
}
@Test
public void shouldSearchOnFamily() {
shouldSearch((b, s) -> b.withFamily(Collections.singletonList(s)));
}
@Test
public void shouldSearchOnGenus() {
shouldSearch((b, s) -> b.withGenus(Collections.singletonList(s)));
}
@Test
public void shouldSearchOnSpecies() {
shouldSearch((b, s) -> b.withSpecies(Collections.singletonList(s)));
}
@Test
public void shouldSearchOnMaterialType() {
shouldSearch((b, s) -> b.withMaterialType(Collections.singletonList(s)));
}
@Test
public void shouldSearchOnBiotopeType() {
shouldSearch((b, s) -> b.withBiotopeType(Collections.singletonList(s)));
}
@Test
public void shouldSearchOnCountryOfOrigin() {
shouldSearch(GeneticResourceBuilder::withCountryOfOrigin);
}
@Test
public void shouldSearchOnCountryOfCollect() {
shouldSearch(GeneticResourceBuilder::withCountryOfCollect);
}
@Test
public void shouldNotSearchOnIdentifier() {
GeneticResource geneticResource = new GeneticResourceBuilder().build();
geneticResourceDao.save(geneticResource);
assertThat(geneticResourceDao.search(geneticResource.getId(), firstPage).getContent()).isEmpty();
}
@Test
public void shouldNotSearchOnUrls() {
GeneticResource geneticResource =
new GeneticResourceBuilder().withDataURL("foo bar baz").withPortalURL("foo bar baz").build();
geneticResourceDao.save(geneticResource);
assertThat(geneticResourceDao.search("bar", firstPage).getContent()).isEmpty();
}
private void shouldSearch(BiConsumer<GeneticResourceBuilder, String> config) {
GeneticResourceBuilder geneticResourceBuilder = new GeneticResourceBuilder();
config.accept(geneticResourceBuilder, "foo bar baz");
GeneticResource geneticResource = geneticResourceBuilder.build();
geneticResourceDao.save(geneticResource);
assertThat(geneticResourceDao.search("bar", firstPage).getContent()).hasSize(1);
assertThat(geneticResourceDao.search("bing", firstPage).getContent()).isEmpty();
}
}
package fr.inra.urgi.rare.search;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.List;
import java.util.Arrays;
import fr.inra.urgi.rare.config.SecurityConfig;
import fr.inra.urgi.rare.dao.GeneticResourceDao;
import fr.inra.urgi.rare.domain.GeneticResource;
import fr.inra.urgi.rare.domain.GeneticResourceBuilder;
import org.assertj.core.util.Lists;
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.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.data.domain.PageRequest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
......@@ -34,7 +31,7 @@ import org.springframework.test.web.servlet.MockMvc;
class SearchControllerTest {
@MockBean
private GeneticResourceDao geneticResourceDao;
private GeneticResourceDao mockGeneticResourceDao;
@Autowired
private MockMvc mockMvc;
......@@ -42,18 +39,21 @@ class SearchControllerTest {
@Test
void shouldSearch() throws Exception {
GeneticResource resource = new GeneticResourceBuilder()
.withId("CFBP 8402")
.withName("CFBP 8402")
.withDescription("Xylella fastidiosa subsp. Pauca, risk group = Quarantine")
.build();
List<GeneticResource> geneticResources = Lists.newArrayList(resource);
Page<GeneticResource> searchResults = new PageImpl<>(geneticResources);
when(geneticResourceDao.search(any(SearchQuery.class))).thenReturn(searchResults);
mockMvc.perform(get("/api/genetic-resources?query=Bacteria"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.totalElements").value(1))
.andExpect(jsonPath("$.content[0].identifier").value(resource.getId()))
.andExpect(jsonPath("$.content[0].name").value(resource.getName()));
.withId("CFBP 8402")
.withName("CFBP 8402")
.withDescription("Xylella fastidiosa subsp. Pauca, risk group = Quarantine")
.build();
PageRequest pageRequest = PageRequest.of(0, SearchController.PAGE_SIZE);
String query = "pauca";
when(mockGeneticResourceDao.search(query, pageRequest))
.thenReturn(new PageImpl<>(Arrays.asList(resource), pageRequest, 1));
mockMvc.perform(get("/api/genetic-resources").param("query", query))
.andExpect(status().isOk())
.andExpect(jsonPath("$.number").value(0))
.andExpect(jsonPath("$.content[0].identifier").value(resource.getId()))
.andExpect(jsonPath("$.content[0].name").value(resource.getName()))
.andExpect(jsonPath("$.content[0].description").value(resource.getDescription()));
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment