From 3fce34ba572cfbd432c8e6861c23b1ca124aff41 Mon Sep 17 00:00:00 2001 From: Erhun Giray TUNCAY <48091473+giraygi@users.noreply.github.com> Date: Fri, 22 Nov 2024 16:12:42 +0100 Subject: [PATCH] added a paginated term instances endpoint and made related from and equivalent class endpoints paginated for #98 --- .../api/v1/V1OntologyTermController.java | 61 +++++++++++++++++-- .../ols/repository/v1/V1GraphRepository.java | 42 ++++++++++--- 2 files changed, 89 insertions(+), 14 deletions(-) diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1OntologyTermController.java b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1OntologyTermController.java index eb962253..d328b1d3 100644 --- a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1OntologyTermController.java +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1OntologyTermController.java @@ -22,6 +22,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.util.UriUtils; +import uk.ac.ebi.spot.ols.model.v1.V1Individual; import uk.ac.ebi.spot.ols.model.v1.V1Term; import uk.ac.ebi.spot.ols.repository.v1.V1GraphRepository; import uk.ac.ebi.spot.ols.repository.v1.V1JsTreeRepository; @@ -53,6 +54,9 @@ public class V1OntologyTermController { @Autowired V1TermAssembler termAssembler; + @Autowired + V1IndividualAssembler individualAssembler; + @Autowired V1PreferredRootTermAssembler preferredRootTermAssembler; @@ -410,7 +414,7 @@ HttpEntity> ancestors(@PathVariable("onto") @RequestMapping(path = "/{onto}/terms/{iri}/equivalentclasses", produces = {MediaType.APPLICATION_JSON_VALUE, MediaTypes.HAL_JSON_VALUE}, method = RequestMethod.GET) - HttpEntity>> getEquivalentClasses( + HttpEntity> getEquivalentClasses( @PathVariable("onto") @Parameter(name = "onto", description = "The ID of the ontology. For example for Data Use Ontology, the ID is duo.", @@ -418,18 +422,54 @@ HttpEntity>> getEquivalentClasses( @PathVariable("iri") @Parameter(name = "iri", description = "The IRI of the term, this value must be single URL encoded", - example = "http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FDUO_0000017") String termId) { + example = "http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FDUO_0000017") String termId, + @RequestParam(value = "lang", required = false, defaultValue = "en") String lang, + @Parameter(hidden = true) Pageable pageable, + @Parameter(hidden = true) PagedResourcesAssembler assembler) { ontologyId = ontologyId.toLowerCase(); + String decoded = UriUtils.decode(termId, "UTF-8"); String entityId = ontologyId+"+class+"+decoded; - return new ResponseEntity<>( graphRepository.getEquivalentClass(entityId), HttpStatus.OK); + Page equivalentClasses = graphRepository.getEquivalentClassPaginated(entityId, lang, pageable); + if (equivalentClasses == null) + throw new ResourceNotFoundException("No equivalent classes could be found for " + ontologyId + + " and " + termId); + + return new ResponseEntity<>( assembler.toModel(equivalentClasses, termAssembler), HttpStatus.OK); } @RequestMapping(path = "/{onto}/terms/{iri}/relatedfrom", produces = {MediaType.APPLICATION_JSON_VALUE, MediaTypes.HAL_JSON_VALUE}, method = RequestMethod.GET) - HttpEntity> getRelatedFrom( + HttpEntity> getRelatedFrom( + @PathVariable("onto") + @Parameter(name = "onto", + description = "The ID of the ontology. For example for Data Use Ontology, the ID is duo.", + example = "duo") String ontologyId, + @PathVariable("iri") + @Parameter(name = "iri", + description = "The IRI of the term, this value must be single URL encoded", + example = "http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FDUO_0000017") String termId, + @RequestParam(value = "lang", required = false, defaultValue = "en") String lang, + @Parameter(hidden = true) Pageable pageable, + @Parameter(hidden = true) PagedResourcesAssembler assembler) { + + ontologyId = ontologyId.toLowerCase(); + + String decoded = UriUtils.decode(termId, "UTF-8"); + String entityId = ontologyId+"+class+"+decoded; + Page relatedFroms = graphRepository.getRelatedFromPaginated(entityId, lang, pageable); + if (relatedFroms == null) + throw new ResourceNotFoundException("No related from terms could be found for " + ontologyId + + " and " + termId); + + return new ResponseEntity<>( assembler.toModel(relatedFroms, termAssembler), HttpStatus.OK); + } + + @RequestMapping(path = "/{onto}/terms/{iri}/instances", produces = {MediaType.APPLICATION_JSON_VALUE, + MediaTypes.HAL_JSON_VALUE}, method = RequestMethod.GET) + HttpEntity> getInstances( @PathVariable("onto") @Parameter(name = "onto", description = "The ID of the ontology. For example for Data Use Ontology, the ID is duo.", @@ -437,12 +477,21 @@ HttpEntity> getRelatedFrom( @PathVariable("iri") @Parameter(name = "iri", description = "The IRI of the term, this value must be single URL encoded", - example = "http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FDUO_0000017") String termId) { + example = "http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FDUO_0000017") String termId, + @RequestParam(value = "lang", required = false, defaultValue = "en") String lang, + @Parameter(hidden = true) Pageable pageable, + @Parameter(hidden = true) PagedResourcesAssembler assembler) { ontologyId = ontologyId.toLowerCase(); + String decoded = UriUtils.decode(termId, "UTF-8"); String entityId = ontologyId+"+class+"+decoded; - return new ResponseEntity<>( graphRepository.getRelatedFrom(entityId), HttpStatus.OK); + Page instances = graphRepository.getTermInstancesPaginated(entityId, lang, pageable); + if (instances == null) + throw new ResourceNotFoundException("No instances could be found for " + ontologyId + + " and " + termId); + + return new ResponseEntity<>( assembler.toModel(instances, individualAssembler), HttpStatus.OK); } @RequestMapping(path = "/{onto}/terms/{iri}/jstree", diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/repository/v1/V1GraphRepository.java b/backend/src/main/java/uk/ac/ebi/spot/ols/repository/v1/V1GraphRepository.java index cb727192..e2079099 100644 --- a/backend/src/main/java/uk/ac/ebi/spot/ols/repository/v1/V1GraphRepository.java +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/repository/v1/V1GraphRepository.java @@ -7,13 +7,19 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Component; +import uk.ac.ebi.spot.ols.model.v1.V1Individual; +import uk.ac.ebi.spot.ols.model.v1.V1Term; import uk.ac.ebi.spot.ols.repository.transforms.LocalizationTransform; import uk.ac.ebi.spot.ols.repository.transforms.RemoveLiteralDatatypesTransform; +import uk.ac.ebi.spot.ols.repository.v1.mappers.V1IndividualMapper; +import uk.ac.ebi.spot.ols.repository.v1.mappers.V1TermMapper; import uk.ac.ebi.spot.ols.service.Neo4jClient; import java.util.*; import java.util.stream.Collectors; +import static org.neo4j.driver.Values.parameters; + @Component public class V1GraphRepository { @@ -134,26 +140,46 @@ Map getParentsAndRelatedTo(String entityId) { return (Map) results.get(0).get("result"); } - public Map getRelatedFrom(String entityId) { + Map getRelatedFrom(String entityId) { + String query = "MATCH path = (x)-[r:relatedTo]->(n:OntologyClass)\n" + "WHERE n.id=\"" + entityId + "\"\n" - + "RETURN { nodes: collect({ label: x.label, iri: x.iri }),\n" - + "edges: collect({ source: startNode(r).iri, target: endNode(r).iri, relationship: type(r) })\n" + + "RETURN { nodes: collect(distinct x),\n" + + "edges: collect({ source: startNode(r).iri, target: endNode(r).iri, relationship: r })\n" + "} AS result"; List> results = neo4jClient.rawQuery(query); return (Map) results.get(0).get("result"); } - public List> getEquivalentClass(String entityId) { + public Page getRelatedFromPaginated(String entityId, String lang, Pageable pageable) { + String query = "MATCH (x:OntologyClass)-[r:relatedTo]->(n:OntologyClass) WHERE n.id= $id RETURN x"; + String countQuery = "MATCH (x:OntologyClass)-[r:relatedTo]->(n:OntologyClass) WHERE n.id= $id RETURN count(x)"; + + return neo4jClient.queryPaginated(query, "x", countQuery, parameters("id", entityId), pageable).map(record -> V1TermMapper.mapTerm(record, lang)); + } + + public Page getEquivalentClassPaginated(String entityId, String lang, Pageable pageable) { String query = "MATCH (a:OntologyClass)-[r:`http://www.w3.org/2002/07/owl#equivalentClass`]-(b:OntologyClass) " + - "WHERE a.id = '"+entityId+"' RETURN {nodes: collect( DISTINCT { label: b.label, iri: b.iri })," + - "edges: collect({ source: startNode(r).iri, target: endNode(r).iri, relationship: type(r) })} AS result"; + "WHERE a.id = $id RETURN DISTINCT b"; + String countQuery = + "MATCH (a:OntologyClass)-[r:`http://www.w3.org/2002/07/owl#equivalentClass`]-(b:OntologyClass) " + + "WHERE a.id = $id RETURN count(DISTINCT b)"; - List> results = neo4jClient.rawQuery(query); - return results; + return neo4jClient.queryPaginated(query, "b", countQuery, parameters("id", entityId), pageable).map(record -> V1TermMapper.mapTerm(record, lang)); + } + + public Page getTermInstancesPaginated(String entityId, String lang, Pageable pageable) { + String query = + "MATCH (a:OntologyClass)<-[r:`http://www.w3.org/1999/02/22-rdf-syntax-ns#type`]-(b:OntologyIndividual) " + + "WHERE a.id = $id RETURN b"; + String countQuery = + "MATCH (a:OntologyClass)<-[r:`http://www.w3.org/1999/02/22-rdf-syntax-ns#type`]-(b:OntologyIndividual) " + + "WHERE a.id = $id RETURN count(b)"; + + return neo4jClient.queryPaginated(query, "b", countQuery, parameters("id", entityId), pageable).map(record -> V1IndividualMapper.mapIndividual(record, lang)); } JsonObject getOntologyNodeJson(Node node, String lang) {