diff --git a/oan-etl/pom.xml b/oan-etl/pom.xml
index 3406d09..ae6d99b 100644
--- a/oan-etl/pom.xml
+++ b/oan-etl/pom.xml
@@ -7,7 +7,7 @@
org.jax.oan
ontology-annotation-network
- 1.0.3-SNAPSHOT
+ 1.0.4
oan-etl
diff --git a/oan-model/pom.xml b/oan-model/pom.xml
index 09a5e0e..af921a8 100644
--- a/oan-model/pom.xml
+++ b/oan-model/pom.xml
@@ -4,7 +4,7 @@
org.jax.oan
ontology-annotation-network
- 1.0.3-SNAPSHOT
+ 1.0.4
oan-model
diff --git a/oan-model/src/main/java/org/jax/oan/core/SupportedEntity.java b/oan-model/src/main/java/org/jax/oan/core/SupportedEntity.java
index 9b611f2..ba208d3 100644
--- a/oan-model/src/main/java/org/jax/oan/core/SupportedEntity.java
+++ b/oan-model/src/main/java/org/jax/oan/core/SupportedEntity.java
@@ -31,7 +31,7 @@ public static SupportedEntity from(TermId termId) {
return SupportedEntity.UNKNOWN;
}
- public static boolean isSupportedDownload(SupportedEntity entity, SupportedEntity type){
+ public static boolean isLinkedType(SupportedEntity entity, SupportedEntity type){
return switch (entity) {
case PHENOTYPE -> (type.equals(DISEASE) || type.equals(GENE));
case DISEASE -> (type.equals(PHENOTYPE) || type.equals(GENE));
diff --git a/oan-model/src/test/java/org/jax/oan/core/SupportedEntityTest.java b/oan-model/src/test/java/org/jax/oan/core/SupportedEntityTest.java
index b1181d9..1747c37 100644
--- a/oan-model/src/test/java/org/jax/oan/core/SupportedEntityTest.java
+++ b/oan-model/src/test/java/org/jax/oan/core/SupportedEntityTest.java
@@ -44,12 +44,12 @@ private static Stream from() {
@ParameterizedTest
@MethodSource
- void isSupportedDownload(SupportedEntity entity, SupportedEntity association) {
- assertTrue(SupportedEntity.isSupportedDownload(entity, association));
- assertFalse(SupportedEntity.isSupportedDownload(entity, entity));
+ void isLinkedType(SupportedEntity entity, SupportedEntity association) {
+ assertTrue(SupportedEntity.isLinkedType(entity, association));
+ assertFalse(SupportedEntity.isLinkedType(entity, entity));
}
- private static Stream isSupportedDownload() {
+ private static Stream isLinkedType() {
return Stream.of(
Arguments.of(SupportedEntity.GENE, SupportedEntity.PHENOTYPE),
Arguments.of(SupportedEntity.PHENOTYPE, SupportedEntity.DISEASE),
diff --git a/oan-rest/pom.xml b/oan-rest/pom.xml
index b8e5962..d270937 100644
--- a/oan-rest/pom.xml
+++ b/oan-rest/pom.xml
@@ -4,7 +4,7 @@
org.jax.oan
ontology-annotation-network
- 1.0.3-SNAPSHOT
+ 1.0.4
oan-rest
@@ -78,11 +78,6 @@
javax.ws.rs-api
test
-
- io.micronaut
- micronaut-http-client
- test
-
io.micronaut.test
micronaut-test-junit5
diff --git a/oan-rest/src/main/java/org/jax/oan/Application.java b/oan-rest/src/main/java/org/jax/oan/Application.java
index f5d2b25..fd0d5cc 100644
--- a/oan-rest/src/main/java/org/jax/oan/Application.java
+++ b/oan-rest/src/main/java/org/jax/oan/Application.java
@@ -12,11 +12,11 @@
@OpenAPIDefinition(
info = @Info(
title = "ontology-annotation-network",
- version = "1.0.3-SNAPSHOT",
+ version = "1.0.4",
description = "A restful service for access to the ontology annotation network.",
contact = @Contact(name = "Michael Gargano", email = "Michael.Gargano@jax.org")
), servers = {@Server(url = "https://ontology.jax.org/api/network", description = "Production Server URL")
- // @Server(url = "http://localhost:8080/api/network", description = "Development Server URL")
+// @Server(url = "http://localhost:8080/api/network", description = "Development Server URL")
}
)
public class Application {
diff --git a/oan-rest/src/main/java/org/jax/oan/controller/AnnotationController.java b/oan-rest/src/main/java/org/jax/oan/controller/AnnotationController.java
index df6d3da..87e18f9 100644
--- a/oan-rest/src/main/java/org/jax/oan/controller/AnnotationController.java
+++ b/oan-rest/src/main/java/org/jax/oan/controller/AnnotationController.java
@@ -8,7 +8,6 @@
import io.micronaut.http.exceptions.HttpStatusException;
import io.micronaut.http.server.types.files.SystemFile;
import io.micronaut.serde.annotation.SerdeImport;
-import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import org.jax.oan.core.*;
import org.jax.oan.exception.OntologyAnnotationNetworkException;
@@ -84,7 +83,7 @@ public SystemFile download(
TermId termId = TermId.of(id);
SupportedEntity entity = SupportedEntity.from(termId);
SupportedEntity downloadType = SupportedEntity.valueOf(type.toUpperCase());
- if (SupportedEntity.isSupportedDownload(entity, downloadType)){
+ if (SupportedEntity.isLinkedType(entity, downloadType)){
return this.downloadService.associations(termId, entity, downloadType);
} else {
throw new HttpStatusException(HttpStatus.BAD_REQUEST, String.format("Downloading %s associations for %s is not supported.", entity, termId.getValue()));
diff --git a/oan-rest/src/main/java/org/jax/oan/controller/SearchController.java b/oan-rest/src/main/java/org/jax/oan/controller/SearchController.java
index 486f5c9..ab26bce 100644
--- a/oan-rest/src/main/java/org/jax/oan/controller/SearchController.java
+++ b/oan-rest/src/main/java/org/jax/oan/controller/SearchController.java
@@ -1,22 +1,38 @@
package org.jax.oan.controller;
import io.micronaut.http.HttpResponse;
+import io.micronaut.http.HttpStatus;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.PathVariable;
import io.micronaut.http.annotation.QueryValue;
+import io.micronaut.http.exceptions.HttpStatusException;
+import io.micronaut.http.server.types.files.SystemFile;
import io.micronaut.serde.annotation.SerdeImport;
import io.swagger.v3.oas.annotations.media.Schema;
+import org.jax.oan.core.Disease;
import org.jax.oan.core.SearchDto;
+import org.jax.oan.core.SupportedEntity;
+import org.jax.oan.exception.OntologyAnnotationNetworkRuntimeException;
+import org.jax.oan.service.DiseaseService;
import org.jax.oan.service.SearchService;
+import org.monarchinitiative.phenol.base.PhenolRuntimeException;
+import org.monarchinitiative.phenol.ontology.data.TermId;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.stream.Collectors;
@Controller("/search")
@SerdeImport(SearchDto.class)
+@SerdeImport(Disease.class)
public class SearchController {
- private SearchService searchService;
+ private final SearchService searchService;
+ private final DiseaseService diseaseService;
- public SearchController(SearchService searchService) {
+ public SearchController(SearchService searchService, DiseaseService diseaseService) {
this.searchService = searchService;
+ this.diseaseService = diseaseService;
}
@Get(uri="/{entity}", produces="application/json")
@@ -34,7 +50,34 @@ public HttpResponse> searchEntity(@Schema(minLength = 1, maxLength = 20, type
} else if (entity.equalsIgnoreCase("DISEASE")){
return HttpResponse.ok(this.searchService.searchDisease(q.toUpperCase(), page, limit));
} else {
- return HttpResponse.noContent();
+ return HttpResponse.badRequest();
+ }
+ }
+
+
+ /**
+ * This is our base controller for annotations that deals with different ontology term types
+ * and returns a defined annotation schema.
+ * @param entity the entity you care about with your list of phenotypes
+ * @param p the list of comma-seperated phenotype(hp) term ids
+ * @return an http response with the specific annotation schema based on the type
+ * @throws OntologyAnnotationNetworkRuntimeException which will be a 500
+ */
+ @Get(uri="/{entity}/intersect", produces="application/json")
+ public HttpResponse> intersect(
+ @Schema(minLength = 1, maxLength = 20, type = "string", pattern = ".*", format = "string") @PathVariable String entity,
+ @Schema(minLength = 1, maxLength = 20000, type = "string", pattern = ".*", format = "string") @QueryValue String p) {
+ try {
+ Collection terms = Arrays.stream(p.split(",")).map(TermId::of).toList();
+ SupportedEntity target = SupportedEntity.valueOf(entity.toUpperCase());
+ if (SupportedEntity.isLinkedType(SupportedEntity.PHENOTYPE, target)){
+ return HttpResponse.ok(this.diseaseService.findIntersectingByPhenotypes(terms));
+ } else {
+ throw new HttpStatusException(HttpStatus.BAD_REQUEST, String.format("Intersecting %s associations for your phenotypes is not supported.", entity));
+ }
+ } catch(PhenolRuntimeException | OntologyAnnotationNetworkRuntimeException e){
+ throw new HttpStatusException(HttpStatus.BAD_REQUEST, e.getMessage());
}
}
+
}
diff --git a/oan-rest/src/main/java/org/jax/oan/service/DiseaseService.java b/oan-rest/src/main/java/org/jax/oan/service/DiseaseService.java
index 986e39b..00a3206 100644
--- a/oan-rest/src/main/java/org/jax/oan/service/DiseaseService.java
+++ b/oan-rest/src/main/java/org/jax/oan/service/DiseaseService.java
@@ -1,22 +1,26 @@
package org.jax.oan.service;
+import io.micronaut.serde.annotation.SerdeImport;
import jakarta.inject.Singleton;
import org.jax.oan.core.*;
import org.jax.oan.exception.OntologyAnnotationNetworkException;
import org.jax.oan.repository.DiseaseRepository;
+import org.jax.oan.repository.PhenotypeRepository;
import org.monarchinitiative.phenol.ontology.data.TermId;
-import java.util.Collection;
-import java.util.Optional;
+import java.util.*;
import java.util.stream.Collectors;
@Singleton
+@SerdeImport(Disease.class)
public class DiseaseService {
private final DiseaseRepository diseaseRepository;
+ private final PhenotypeRepository phenotypeRepository;
- public DiseaseService(DiseaseRepository diseaseRepository) {
+ public DiseaseService(DiseaseRepository diseaseRepository, PhenotypeRepository phenotypeRepository) {
this.diseaseRepository = diseaseRepository;
+ this.phenotypeRepository = phenotypeRepository;
}
public DiseaseAnnotationDto findAll(TermId termId) throws OntologyAnnotationNetworkException {
@@ -33,4 +37,24 @@ public DiseaseAnnotationDto findAll(TermId termId) throws OntologyAnnotationNetw
}
throw new OntologyAnnotationNetworkException(String.format("Could not find disease with id %s", termId.getValue()));
}
+
+ public Collection findIntersectingByPhenotypes(Collection termIds){
+ List intersecting = new ArrayList<>();
+ for (TermId id: termIds){
+ try {
+ if (intersecting.isEmpty()){
+ intersecting.addAll(phenotypeRepository.findDiseasesByTerm(id));
+ } else {
+ Collection diseases = phenotypeRepository.findDiseasesByTerm(id);
+ intersecting = intersecting.stream().distinct()
+ .filter(diseases::contains)
+ .collect(Collectors.toList());
+ }
+
+ } catch (Exception ex) {
+ return Collections.emptyList();
+ }
+ }
+ return intersecting.stream().distinct().toList();
+ }
}
diff --git a/oan-rest/src/test/java/org/jax/oan/controller/SearchControllerTest.java b/oan-rest/src/test/java/org/jax/oan/controller/SearchControllerTest.java
new file mode 100644
index 0000000..4c7dc80
--- /dev/null
+++ b/oan-rest/src/test/java/org/jax/oan/controller/SearchControllerTest.java
@@ -0,0 +1,92 @@
+package org.jax.oan.controller;
+
+import io.micronaut.runtime.EmbeddedApplication;
+import io.micronaut.test.annotation.MockBean;
+import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import io.restassured.specification.RequestSpecification;
+import jakarta.inject.Inject;
+import org.jax.oan.core.Disease;
+import org.jax.oan.core.Gene;
+import org.jax.oan.core.SearchDto;
+import org.jax.oan.service.DiseaseService;
+import org.jax.oan.service.SearchService;
+import org.junit.jupiter.api.Test;
+import org.monarchinitiative.phenol.ontology.data.TermId;
+
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.hasItems;
+import static org.hamcrest.Matchers.equalTo;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@MicronautTest
+public class SearchControllerTest {
+ @Inject
+ EmbeddedApplication> application;
+
+ @Inject
+ DiseaseService diseaseService;
+
+ @Inject
+ SearchService searchService;
+
+ @Test
+ void positive_search_by_gene(RequestSpecification spec){
+ when(searchService.searchGene("TP53", 0,10))
+ .thenReturn(new SearchDto(List.of(new Gene(TermId.of("NCBIGene:093232"), "tp53"),
+ new Gene(TermId.of("NCBIGene:093234"), "tp53b")), 2));
+ spec.when().get("/api/network/search/gene?q=tp53").then()
+ .statusCode(200)
+ .body("results.id", hasItems("NCBIGene:093234", "NCBIGene:093232"))
+ .body("totalCount", equalTo(2));
+ }
+
+ @Test
+ void positive_search_by_disease(RequestSpecification spec){
+ when(searchService.searchDisease("MARFAN", 0,10))
+ .thenReturn(new SearchDto(List.of(
+ new Disease(TermId.of("OMIM:333333"), "Bad disease 1", "MONDO:000001", "no description"),
+ new Disease(TermId.of("OMIM:444444"), "Bad disease 2", "MONDO:000002", "funky description"),
+ new Disease(TermId.of("OMIM:555555"), "Bad disease 3", "MONDO:000003", "description")), 3));
+ spec.when().get("/api/network/search/disease?q=marfan").then()
+ .statusCode(200)
+ .body("results.id", hasItems("OMIM:333333", "OMIM:444444"))
+ .body("totalCount", equalTo(3));
+ }
+
+ @Test
+ void negative_search_by_bad_entity(RequestSpecification spec){
+ spec.when().get("/api/network/search/variant?q=chr1:60023-2300").then().statusCode(400);
+ spec.when().get("/api/network/search/phenotype?q=bighead").then().statusCode(400);
+ }
+
+
+ @Test
+ void postive_intersecting(RequestSpecification spec){
+ when(diseaseService.findIntersectingByPhenotypes(List.of(TermId.of("HP:333333"), TermId.of("HP:44444"))))
+ .thenReturn(List.of(
+ new Disease(TermId.of("OMIM:333333"), "Bad disease 1", "MONDO:000001", "no description"),
+ new Disease(TermId.of("OMIM:444444"), "Bad disease 2", "MONDO:000002", "funky description"),
+ new Disease(TermId.of("OMIM:555555"), "Bad disease 3", "MONDO:000003", "description")));
+ spec.when().get("/api/network/search/disease/intersect?p=HP:333333,HP:44444").then()
+ .statusCode(200)
+ .body("id", hasItems("OMIM:333333", "OMIM:444444"));
+ }
+
+ @Test
+ void negative_intersecting(RequestSpecification spec){
+ spec.when().get("/api/network/search/disease/intersect?q=chr1:60023-2300").then().statusCode(400);
+ spec.when().get("/api/network/search/phenotype?q=bighead").then().statusCode(400);
+ }
+
+ @MockBean(DiseaseService.class)
+ DiseaseService diseaseService() {
+ return mock(DiseaseService.class);
+ }
+
+ @MockBean(SearchService.class)
+ SearchService searchService() {
+ return mock(SearchService.class);
+ }
+}
diff --git a/oan-rest/src/test/java/org/jax/oan/repository/DiseaseRepositoryTest.java b/oan-rest/src/test/java/org/jax/oan/repository/DiseaseRepositoryTest.java
index 9b461a7..a86d54f 100644
--- a/oan-rest/src/test/java/org/jax/oan/repository/DiseaseRepositoryTest.java
+++ b/oan-rest/src/test/java/org/jax/oan/repository/DiseaseRepositoryTest.java
@@ -15,6 +15,7 @@
import java.util.Collection;
import java.util.List;
+import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
@@ -70,7 +71,7 @@ void findPhenotypesByDisease() {
}
@Test
- void findDisease(){
+ void findDiseases(){
Collection diseases = diseaseRepository.findDiseases("bad");
List expected = List.of(
new Disease(TermId.of("OMIM:555555"), "Bad disease", "MONDO:0000001", "Rare disease"),
@@ -79,4 +80,13 @@ void findDisease(){
assertTrue(diseases.containsAll(expected));
}
+
+ @Test
+ void findDiseaseById(){
+ Optional disease = diseaseRepository.findDiseaseById(TermId.of("OMIM:555555"));
+ Disease expected = new Disease(TermId.of("OMIM:555555"), "Bad disease", "MONDO:0000001", "Rare disease");
+
+ assertTrue(disease.isPresent());
+ assertEquals(disease.get(), expected);
+ }
}
diff --git a/oan-rest/src/test/java/org/jax/oan/service/DiseaseServiceTest.java b/oan-rest/src/test/java/org/jax/oan/service/DiseaseServiceTest.java
index f284b37..06bb958 100644
--- a/oan-rest/src/test/java/org/jax/oan/service/DiseaseServiceTest.java
+++ b/oan-rest/src/test/java/org/jax/oan/service/DiseaseServiceTest.java
@@ -6,10 +6,10 @@
import org.jax.oan.core.*;
import org.jax.oan.exception.OntologyAnnotationNetworkException;
import org.jax.oan.repository.DiseaseRepository;
+import org.jax.oan.repository.PhenotypeRepository;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
-import org.mockito.stubbing.Answer;
import org.monarchinitiative.phenol.ontology.data.TermId;
import java.util.Collection;
@@ -32,6 +32,9 @@ class DiseaseServiceTest {
@Inject
DiseaseService diseaseService;
+ @Inject
+ PhenotypeRepository phenotypeRepository;
+
@ParameterizedTest
@MethodSource
void test_find_all(TermId id, List genes, List phenotypes) throws OntologyAnnotationNetworkException {
@@ -46,6 +49,17 @@ void test_find_all(TermId id, List genes, List phenotypes) thro
.collect(Collectors.toList()));
}
+ @ParameterizedTest
+ @MethodSource
+ void test_intersect_disease_phenotypes(List ids, List diseaseGroup1,
+ List diseaseGroup2, List expected){
+
+ when(phenotypeRepository.findDiseasesByTerm(ids.get(0))).thenReturn(diseaseGroup1);
+ when(phenotypeRepository.findDiseasesByTerm(ids.get(1))).thenReturn(diseaseGroup2);
+ Collection intersecting = diseaseService.findIntersectingByPhenotypes(ids);
+ assertEquals(expected, intersecting);
+ }
+
private static Stream test_find_all(){
return Stream.of(
Arguments.of(TermId.of("OMIM:0392932"), List.of(
@@ -62,9 +76,31 @@ private static Stream test_find_all(){
);
}
+ private static Stream test_intersect_disease_phenotypes(){
+ return Stream.of(
+ Arguments.of(List.of(TermId.of("HP:0001252"), TermId.of("HP:0001249")), List.of(
+ new Disease(TermId.of("OMIM:333333"), "Bad disease 1", "MONDO:000001", "no description"),
+ new Disease(TermId.of("OMIM:444444"), "Bad disease 2", "MONDO:000002", "funky description"),
+ new Disease(TermId.of("OMIM:555555"), "Bad disease 3", "MONDO:000003", "description")
+
+ ), List.of(
+ new Disease(TermId.of("OMIM:777777"), "Bad disease 5", "MONDO:000005", ""),
+ new Disease(TermId.of("OMIM:666666"), "Bad disease 4", "MONDO:000004", ""),
+ new Disease(TermId.of("OMIM:555555"), "Bad disease 3", "MONDO:000003", "description")
+ ),List.of(
+ new Disease(TermId.of("OMIM:555555"), "Bad disease 3", "MONDO:000003", "description")
+ ))
+ );
+ }
+
@MockBean(DiseaseRepository.class)
DiseaseRepository diseaseRepository() {
return mock(DiseaseRepository.class);
}
+ @MockBean(PhenotypeRepository.class)
+ PhenotypeRepository phenotypeRepository() {
+ return mock(PhenotypeRepository.class);
+ }
+
}
diff --git a/pom.xml b/pom.xml
index c47f7b8..aa07139 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
org.jax.oan
ontology-annotation-network
pom
- 1.0.3-SNAPSHOT
+ 1.0.4
ontology-annotation-network
https://github.com/TheJacksonLaboratory/ontology-annotation-network