diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/TopConceptEnum.java b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/TopConceptEnum.java new file mode 100644 index 000000000..3a86e8c58 --- /dev/null +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/TopConceptEnum.java @@ -0,0 +1,7 @@ +package uk.ac.ebi.spot.ols.controller.api.v1; + +public enum TopConceptEnum { + SCHEMA, + TOPCONCEPTOF_PROPERTY, + RELATIONSHIPS +} diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1ApiUnavailable.java b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1ApiUnavailable.java index 4452d07bf..dabe0309e 100644 --- a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1ApiUnavailable.java +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1ApiUnavailable.java @@ -6,8 +6,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletResponse; @@ -16,7 +15,7 @@ * @date 27/09/2016 * Samples, Phenotypes and Ontologies Team, EMBL-EBI */ -@Controller +@RestController public class V1ApiUnavailable { @RequestMapping(path = "/api/unavailable", produces = {MediaType.APPLICATION_JSON_VALUE}, method = RequestMethod.GET) diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1IndividualController.java b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1IndividualController.java index c8b900379..7c967b65b 100644 --- a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1IndividualController.java +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1IndividualController.java @@ -28,7 +28,7 @@ * @date 18/08/2015 * Samples, Phenotypes and Ontologies Team, EMBL-EBI */ -@Controller +@RestController @RequestMapping("/api/individuals") @ExposesResourceFor(V1Individual.class) public class V1IndividualController implements @@ -81,7 +81,7 @@ HttpEntity> getAllIndividuals( return new ResponseEntity<>(assembler.toModel(terms, individualAssembler), HttpStatus.OK); } - + @RequestMapping(path = "/findByIdAndIsDefiningOntology/{id}", produces = {MediaType.APPLICATION_JSON_VALUE, MediaTypes.HAL_JSON_VALUE}, method = RequestMethod.GET) HttpEntity> getAllIndividualsByIdAndIsDefiningOntology( @PathVariable("id") String termId, @@ -92,11 +92,11 @@ HttpEntity> getAllIndividualsByIdAndIsDefiningOntology( decoded = UriUtils.decode(termId, "UTF-8"); return getAllIndividualsByIdAndIsDefiningOntology(decoded, null, null, lang, pageable, assembler); - } - - - @RequestMapping(path = "/findByIdAndIsDefiningOntology", - produces = {MediaType.APPLICATION_JSON_VALUE, MediaTypes.HAL_JSON_VALUE}, + } + + + @RequestMapping(path = "/findByIdAndIsDefiningOntology", + produces = {MediaType.APPLICATION_JSON_VALUE, MediaTypes.HAL_JSON_VALUE}, method = RequestMethod.GET) HttpEntity> getAllIndividualsByIdAndIsDefiningOntology( @RequestParam(value = "iri", required = false) String iri, @@ -120,11 +120,11 @@ HttpEntity> getAllIndividualsByIdAndIsDefiningOntology( return new ResponseEntity<>(assembler.toModel(terms, individualAssembler), HttpStatus.OK); } - + @ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "EntityModel not found") @ExceptionHandler(ResourceNotFoundException.class) public void handleError(HttpServletRequest req, Exception exception) { } -} \ No newline at end of file +} diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1OntologyController.java b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1OntologyController.java index a7bb975fc..89b708a69 100644 --- a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1OntologyController.java +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1OntologyController.java @@ -31,7 +31,7 @@ * @date 19/08/2015 * Samples, Phenotypes and Ontologies Team, EMBL-EBI */ -@Controller +@RestController @RequestMapping("/api/ontologies") @ExposesResourceFor(V1Ontology.class) public class V1OntologyController implements diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1OntologyIndividualController.java b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1OntologyIndividualController.java index 5ea636170..75b5ca482 100644 --- a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1OntologyIndividualController.java +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1OntologyIndividualController.java @@ -34,7 +34,7 @@ * @date 02/11/15 * Samples, Phenotypes and Ontologies Team, EMBL-EBI */ -@Controller +@RestController @RequestMapping("/api/ontologies") public class V1OntologyIndividualController { diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1OntologyPropertyController.java b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1OntologyPropertyController.java index 9e7441d7e..e01786710 100644 --- a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1OntologyPropertyController.java +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1OntologyPropertyController.java @@ -27,7 +27,7 @@ import javax.servlet.http.HttpServletRequest; import java.util.Arrays; -@Controller +@RestController @RequestMapping("/api/ontologies") public class V1OntologyPropertyController { diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1OntologySKOSConceptController.java b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1OntologySKOSConceptController.java new file mode 100644 index 000000000..42a8a94d9 --- /dev/null +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1OntologySKOSConceptController.java @@ -0,0 +1,348 @@ +package uk.ac.ebi.spot.ols.controller.api.v1; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.tags.Tag; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.data.web.PagedResourcesAssembler; +import org.springframework.hateoas.MediaTypes; +import org.springframework.hateoas.PagedModel; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.util.UriUtils; + +import uk.ac.ebi.spot.ols.model.Edge; +import uk.ac.ebi.spot.ols.model.Node; +import uk.ac.ebi.spot.ols.model.SKOSRelation; +import uk.ac.ebi.spot.ols.model.v1.V1Term; +import uk.ac.ebi.spot.ols.repository.v1.TreeNode; +import uk.ac.ebi.spot.ols.repository.v1.V1TermRepository; + +import javax.servlet.http.HttpServletRequest; +import java.util.*; + +/** + * @author Erhun Giray TUNCAY + * @email giray.tuncay@tib.eu + * TIB-Leibniz Information Center for Science and Technology + */ +@RestController +@RequestMapping("/api/ontologies") +@Tag(name = "v1-ontology-skos-controller", description = "SKOS concept hierarchies and relations extracted from individuals (instances) from a particular ontology in this service") +public class V1OntologySKOSConceptController { + + private Logger log = LoggerFactory.getLogger(getClass()); + + @Autowired + private V1TermRepository termRepository; + + @Autowired + V1TermAssembler termAssembler; + + @Operation(description = "Get complete SKOS concept hierarchy or only top concepts based on alternative top concept identification methods and concept relations. If only top concepts are identified, they can be used to extract the following levels of the concept tree one by one using the /{onto}/conceptrelations/{iri} method with broader or narrower concept relations.") + @RequestMapping(path = "/{onto}/skos/tree", produces = {MediaType.APPLICATION_JSON_VALUE, MediaTypes.HAL_JSON_VALUE}, method = RequestMethod.GET) + HttpEntity>> getSKOSConceptHierarchyByOntology( + @Parameter(description = "ontology ID", required = true) + @PathVariable("onto") String ontologyId, + @Parameter(description = "infer top concepts by schema (hasTopConcept) or TopConceptOf property or broader/narrower relationships", required = true) + @RequestParam(value = "find_roots", required = true, defaultValue = "SCHEMA") TopConceptEnum topConceptIdentification, + @Parameter(description = "infer from narrower or broader relationships", required = true) + @RequestParam(value = "narrower", required = true, defaultValue = "false") boolean narrower, + @Parameter(description = "Extract the whole tree with children or only the top concepts", required = true) + @RequestParam(value = "with_children", required = true, defaultValue = "false") boolean withChildren, + @RequestParam(value = "obsoletes", required = false) Boolean obsoletes, + @RequestParam(value = "lang", required = false, defaultValue = "en") String lang, + Pageable pageable) { + ontologyId = ontologyId.toLowerCase(); + if (TopConceptEnum.RELATIONSHIPS == topConceptIdentification) + return new ResponseEntity<>(termRepository.conceptTreeWithoutTop(ontologyId,narrower,withChildren,obsoletes,lang,pageable), HttpStatus.OK); + else + return new ResponseEntity<>(termRepository.conceptTree(ontologyId,TopConceptEnum.SCHEMA == topConceptIdentification,narrower, withChildren,obsoletes,lang,pageable), HttpStatus.OK); + } + + @Operation(description = "Display complete SKOS concept hierarchy or only top concepts based on alternative top concept identification methods and concept relations. If only top concepts are identified, they can be used to extract the following levels of the concept tree one by one using the /{onto}/displayconceptrelations/{iri} method with broader or narrower concept relations.") + @RequestMapping(path = "/{onto}/skos/displaytree", produces = {MediaType.TEXT_PLAIN_VALUE}, method = RequestMethod.GET) + @ResponseBody + HttpEntity displaySKOSConceptHierarchyByOntology( + @Parameter(description = "ontology ID", required = true) + @PathVariable("onto") String ontologyId, + @Parameter(description = "infer top concepts by schema (hasTopConcept) or TopConceptOf property or broader/narrower relationships", required = true) + @RequestParam(value = "find_roots", required = true, defaultValue = "SCHEMA") TopConceptEnum topConceptIdentification, + @Parameter(description = "infer from narrower or broader relationships", required = true) + @RequestParam(value = "narrower", required = true, defaultValue = "false") boolean narrower, + @Parameter(description = "Extract the whole tree with children or only the top concepts", required = true) + @RequestParam(value = "with_children", required = true, defaultValue = "false") boolean withChildren, + @Parameter(description = "display related concepts", required = true) + @RequestParam(value = "display_related", required = true, defaultValue = "false") boolean displayRelated, + @RequestParam(value = "obsoletes", required = false) Boolean obsoletes, + @RequestParam(value = "lang", required = false, defaultValue = "en") String lang, + Pageable pageable) { + ontologyId = ontologyId.toLowerCase(); + List> rootIndividuals = null; + if(TopConceptEnum.RELATIONSHIPS == topConceptIdentification) + rootIndividuals = termRepository.conceptTreeWithoutTop(ontologyId,narrower,withChildren,obsoletes,lang,pageable); + else + rootIndividuals = termRepository.conceptTree(ontologyId,TopConceptEnum.SCHEMA == topConceptIdentification,narrower, withChildren,obsoletes,lang,pageable); + StringBuilder sb = new StringBuilder(); + for (TreeNode root : rootIndividuals) { + sb.append(root.getIndex() + " , "+ root.getData().label + " , " + root.getData().iri).append("\n"); + sb.append(generateConceptHierarchyTextByOntology(root, displayRelated)); + } + + return new HttpEntity(sb.toString()); + } + + @Operation(description = "Get partial SKOS concept hierarchy based on the encoded iri of the designated top concept") + @RequestMapping(path = "/{onto}/skos/{iri}/tree", produces = {MediaType.APPLICATION_JSON_VALUE, MediaTypes.HAL_JSON_VALUE}, method = RequestMethod.GET) + HttpEntity> getSKOSConceptHierarchyByOntologyAndIri( + @Parameter(description = "ontology ID", required = true) + @PathVariable("onto") String ontologyId, + @Parameter(description = "encoded concept IRI", required = true) + @PathVariable("iri") String iri, + @Parameter(description = "infer from narrower or broader relationships", required = true) + @RequestParam(value = "narrower", required = true, defaultValue = "false") boolean narrower, + @Parameter(description = "index value for the root term", required = true) + @RequestParam(value = "index", required = true, defaultValue = "1") String index, + @RequestParam(value = "obsoletes", required = false) Boolean obsoletes, + @RequestParam(value = "lang", required = false, defaultValue = "en") String lang, + Pageable pageable) { + ontologyId = ontologyId.toLowerCase(); + TreeNode topConcept = new TreeNode(new V1Term()); + String decodedIri; + decodedIri = UriUtils.decode(iri, "UTF-8"); + topConcept = termRepository.conceptSubTree(ontologyId, decodedIri, narrower, index, obsoletes, lang, pageable); + + if (topConcept.getData().iri == null) + throw new ResourceNotFoundException("No roots could be found for " + ontologyId ); + return new ResponseEntity<>(topConcept, HttpStatus.OK); + } + + @Operation(description = "Display partial SKOS concept hierarchy based on the encoded iri of the designated top concept") + @RequestMapping(path = "/{onto}/skos/{iri}/displaytree", produces = {MediaType.TEXT_PLAIN_VALUE}, method = RequestMethod.GET) + @ResponseBody + HttpEntity displaySKOSConceptHierarchyByOntologyAndIri( + @Parameter(description = "ontology ID", required = true) + @PathVariable("onto") String ontologyId, + @Parameter(description = "encoded concept IRI", required = true) + @PathVariable("iri") String iri, + @Parameter(description = "infer from narrower or broader relationships", required = true) + @RequestParam(value = "narrower", required = true, defaultValue = "false") boolean narrower, + @Parameter(description = "display related concepts", required = true) + @RequestParam(value = "display_related", required = true, defaultValue = "false") boolean displayRelated, + @Parameter(description = "index value for the root term", required = true) + @RequestParam(value = "index", required = true, defaultValue = "1") String index, + @RequestParam(value = "obsoletes", required = false) Boolean obsoletes, + @RequestParam(value = "lang", required = false, defaultValue = "en") String lang, + Pageable pageable) { + ontologyId = ontologyId.toLowerCase(); + TreeNode topConcept = new TreeNode(new V1Term()); + String decodedIri; + StringBuilder sb = new StringBuilder(); + decodedIri = UriUtils.decode(iri, "UTF-8"); + topConcept = termRepository.conceptSubTree(ontologyId, decodedIri, narrower, index, obsoletes, lang, pageable); + + sb.append(topConcept.getIndex() + " , "+ topConcept.getData().label + " , " + topConcept.getData().iri).append("\n"); + sb.append(generateConceptHierarchyTextByOntology(topConcept, displayRelated)); + + return new HttpEntity(sb.toString()); + } + + @Operation(description = "Broader, Narrower and Related concept relations of a concept are listed in JSON if the concept iri is provided in encoded format.") + @RequestMapping(path = "/{onto}/skos/{iri}/relations", produces = {MediaType.APPLICATION_JSON_VALUE, MediaTypes.HAL_JSON_VALUE}, method = RequestMethod.GET) + public HttpEntity> findRelatedConcepts( + @Parameter(description = "ontology ID", required = true) + @PathVariable("onto") String ontologyId, + @Parameter(description = "encoded concept IRI", required = true) + @PathVariable("iri") String iri, + @Parameter(description = "skos based concept relation type", required = true) + @RequestParam(value = "relation_type", required = true, defaultValue = "broader") + @Schema(type = "string", allowableValues = { "broader", "narrower", "related" }) String relationType, + @RequestParam(value = "obsoletes", required = false) Boolean obsoletes, + @RequestParam(value = "lang", required = false, defaultValue = "en") String lang, + Pageable pageable, + PagedResourcesAssembler assembler) { + + ontologyId = ontologyId.toLowerCase(); + List related = new ArrayList(); + String decodedIri = UriUtils.decode(iri, "UTF-8"); + related = termRepository.findRelated(ontologyId, decodedIri, relationType,lang); + + + final int start = (int)pageable.getOffset(); + final int end = Math.min((start + pageable.getPageSize()), related.size()); + Page conceptPage = new PageImpl<>(related.subList(start, end), pageable, related.size()); + + return new ResponseEntity<>( assembler.toModel(conceptPage), HttpStatus.OK); + + } + + @Operation(description = "Broader, Narrower and Related concept relations of a concept are displayed as text if the concept iri is provided in encoded format.") + @RequestMapping(path = "/{onto}/skos/{iri}/displayrelations", produces = {MediaType.TEXT_PLAIN_VALUE}, method = RequestMethod.GET) + @ResponseBody + public HttpEntity displayRelatedConcepts( + @Parameter(description = "ontology ID", required = true) + @PathVariable("onto") String ontologyId, + @Parameter(description = "encoded concept IRI", required = true) + @PathVariable("iri") String iri, + @Parameter(description = "skos based concept relation type", required = true) + @RequestParam(value = "relation_type", required = true, defaultValue = "broader") + @Schema(type = "string", allowableValues = { "broader", "narrower", "related" }) String relationType, + @RequestParam(value = "obsoletes", required = false) Boolean obsoletes, + @RequestParam(value = "lang", required = false, defaultValue = "en") String lang, + Pageable pageable, + PagedResourcesAssembler assembler) { + StringBuilder sb = new StringBuilder(); + ontologyId = ontologyId.toLowerCase(); + List related = new ArrayList(); + String decodedIri = UriUtils.decode(iri, "UTF-8"); + related = termRepository.findRelated(ontologyId, decodedIri, relationType,lang); + + final int start = (int)pageable.getOffset(); + final int end = Math.min((start + pageable.getPageSize()), related.size()); + Page conceptPage = new PageImpl<>(related.subList(start, end), pageable, related.size()); + int count = 0; + for (V1Term individual : conceptPage.getContent()) + sb.append(++count).append(" , ").append(individual.label).append(" , ").append(individual.iri).append("\n"); + + return new HttpEntity<>( sb.toString()); + + } + + @Operation(description = "Broader, Narrower and Related concept relations of a concept are listed in JSON if the concept iri is provided in encoded format. The relationship is identified indirectly based on the related concept's relation to the concept in question. This requires traversing all the available concepts and checking if they are related to the concept in question. For this reason, this method is relatively slower than the displayconceptrelations method. Nevertheless, it enables to identify unforeseen relations of the concept in question") + @RequestMapping(path = "/{onto}/skos/{iri}/indirectrelations", produces = {MediaType.APPLICATION_JSON_VALUE, MediaTypes.HAL_JSON_VALUE}, method = RequestMethod.GET) + public HttpEntity> findRelatedConceptsIndirectly( + @Parameter(description = "ontology ID", required = true) + @PathVariable("onto") String ontologyId, + @Parameter(description = "encoded concept IRI", required = true) + @PathVariable("iri") String iri, + @Parameter(description = "skos based concept relation type", required = true) + @RequestParam(value = "relation_type", required = true, defaultValue = "broader") + @Schema(type = "string", allowableValues = { "broader", "narrower", "related" }) String relationType, + @RequestParam(value = "obsoletes", required = false) Boolean obsoletes, + @RequestParam(value = "lang", required = false, defaultValue = "en") String lang, + Pageable pageable) { + + ontologyId = ontologyId.toLowerCase(); + List related = new ArrayList(); + String decodedIri = UriUtils.decode(iri, "UTF-8"); + related = termRepository.findRelatedIndirectly(ontologyId, decodedIri, relationType, obsoletes,lang,pageable); + + return new ResponseEntity<>( related, HttpStatus.OK); + + } + + @Operation(description = "Broader, Narrower and Related concept relations of a concept are listed in JSON if the concept iri is provided in encoded format. The relationship is identified indirectly based on the related concept's relation to the concept in question. This requires traversing all the available concepts and checking if they are related to the concept in question. For this reason, this method is relatively slower than the displayconceptrelations method. Nevertheless, it enables to identify unforeseen relations of the concept in question") + @RequestMapping(path = "/{onto}/skos/{iri}/displayindirectrelations", produces = {MediaType.TEXT_PLAIN_VALUE}, method = RequestMethod.GET) + @ResponseBody + public HttpEntity displayRelatedConceptsIndirectly( + @Parameter(description = "ontology ID", required = true) + @PathVariable("onto") String ontologyId, + @Parameter(description = "encoded concept IRI", required = true) + @PathVariable("iri") String iri, + @Parameter(description = "skos based concept relation type", required = true) + @RequestParam(value = "relation_type", required = true, defaultValue = "broader") + @Schema(type = "string", allowableValues = { "broader", "narrower", "related" }) String relationType, + @Parameter(description = "Page size to retrieve individuals", required = true) + @RequestParam(value = "obsoletes", required = false) Boolean obsoletes, + @RequestParam(value = "lang", required = false, defaultValue = "en") String lang, + Pageable pageable) { + StringBuilder sb = new StringBuilder(); + ontologyId = ontologyId.toLowerCase(); + List related = new ArrayList(); + String decodedIri = UriUtils.decode(iri, "UTF-8"); + related = termRepository.findRelatedIndirectly(ontologyId, decodedIri, relationType, obsoletes,lang,pageable); + + int count = 0; + for (V1Term individual : related) + sb.append(++count).append(" , ").append(individual.label).append(" , ").append(individual.iri).append("\n"); + + + return new ResponseEntity<>( sb.toString(), HttpStatus.OK); + + } + + @Operation(description = "Node and Edge definitions needed to visualize the nodes that are directly related with the subject term. Ontology ID and encoded iri are required. ") + @RequestMapping(path = "/{onto}/skos/{iri}/graph", produces = {MediaType.APPLICATION_JSON_VALUE, MediaTypes.HAL_JSON_VALUE}, method = RequestMethod.GET) + public HttpEntity retrieveImmediateGraph( + @Parameter(description = "ontology ID", required = true) + @PathVariable("onto") String ontologyId, + @Parameter(description = "encoded concept IRI", required = true) + @PathVariable("iri") String iri, + @RequestParam(value = "lang", required = false, defaultValue = "en") String lang){ + + List related = new ArrayList(); + String decodedIri = UriUtils.decode(iri, "UTF-8"); + + V1Term subjectTerm = termRepository.findByOntologyAndIri(ontologyId, decodedIri, lang); + + related = termRepository.findRelated(ontologyId, decodedIri, "related",lang); + + List narrower = new ArrayList(); + narrower = termRepository.findRelated(ontologyId, decodedIri, "narrower",lang); + + List broader = new ArrayList(); + broader = termRepository.findRelated(ontologyId, decodedIri, "broader",lang); + + Set relatedNodes = new HashSet(); + related.forEach(term -> relatedNodes.add(new Node(term.iri, term.label))); + Set narrowerNodes = new HashSet(); + narrower.forEach(term -> narrowerNodes.add(new Node(term.iri, term.label))); + Set broaderNodes = new HashSet(); + broader.forEach(term -> broaderNodes.add(new Node(term.iri, term.label))); + + Set edges = new HashSet(); + relatedNodes.forEach(node -> edges.add(new Edge(decodedIri, node.getIri(), "related", SKOSRelation.related.getPropertyName()))); + narrowerNodes.forEach(node -> edges.add(new Edge(decodedIri, node.getIri(), "narrower",SKOSRelation.narrower.getPropertyName()))); + broaderNodes.forEach(node -> edges.add(new Edge(decodedIri, node.getIri(), "broader",SKOSRelation.broader.getPropertyName()))); + + Set nodes = new HashSet(); + nodes.add(new Node(decodedIri,subjectTerm.label)); + nodes.addAll(relatedNodes); + nodes.addAll(broaderNodes); + nodes.addAll(narrowerNodes); + + + Map graph = new HashMap(); + graph.put("nodes", nodes); + graph.put("edges", edges); + ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter(); + try { + return new ResponseEntity<>(ow.writeValueAsString(graph),HttpStatus.OK); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + public StringBuilder generateConceptHierarchyTextByOntology(TreeNode rootConcept, boolean displayRelated) { + StringBuilder sb = new StringBuilder(); + for (TreeNode childConcept : rootConcept.getChildren()) { + sb.append(childConcept.getIndex() + " , "+ childConcept.getData().label + " , " + childConcept.getData().iri).append("\n"); + sb.append(generateConceptHierarchyTextByOntology(childConcept,displayRelated)); + } + if(displayRelated) + for (TreeNode relatedConcept : rootConcept.getRelated()) { + sb.append(relatedConcept.getIndex() + " , "+ relatedConcept.getData().label + " , " + relatedConcept.getData().iri).append("\n"); + sb.append(generateConceptHierarchyTextByOntology(relatedConcept,displayRelated)); + } + return sb; + } + + @ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Resource not found") + @ExceptionHandler(ResourceNotFoundException.class) + public void handleError(HttpServletRequest req, Exception exception) { + } +} diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1PropertyController.java b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1PropertyController.java index b6dce598a..3bc5a7fdc 100644 --- a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1PropertyController.java +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1PropertyController.java @@ -23,7 +23,7 @@ import javax.servlet.http.HttpServletRequest; -@Controller +@RestController @RequestMapping("/api/properties") @ExposesResourceFor(V1Property.class) public class V1PropertyController implements @@ -93,8 +93,8 @@ HttpEntity> getPropertiesByIriAndIsDefiningOntology(@Path String decoded = null; decoded = UriUtils.decode(termId, "UTF-8"); return getPropertiesByIdAndIsDefiningOntology(decoded, null, null, lang, pageable, assembler); - } - + } + @RequestMapping(path = "/findByIdAndIsDefiningOntology", produces = {MediaType.APPLICATION_JSON_VALUE, MediaTypes.HAL_JSON_VALUE}, method = RequestMethod.GET) HttpEntity> getPropertiesByIdAndIsDefiningOntology( @RequestParam(value = "iri", required = false) String iri, @@ -121,7 +121,7 @@ else if (oboId != null) { return new ResponseEntity<>( assembler.toModel(terms, termAssembler), HttpStatus.OK); } - + @ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "EntityModel not found") @ExceptionHandler(ResourceNotFoundException.class) public void handleError(HttpServletRequest req, Exception exception) { diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1SearchController.java b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1SearchController.java index a1f4ac641..a55598567 100644 --- a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1SearchController.java +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1SearchController.java @@ -5,15 +5,11 @@ import com.google.gson.JsonParser; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.SolrServerException; -import org.apache.solr.client.solrj.impl.HttpSolrClient; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.SolrDocument; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.*; import uk.ac.ebi.spot.ols.repository.Validation; import uk.ac.ebi.spot.ols.repository.solr.OlsSolrClient; import uk.ac.ebi.spot.ols.repository.transforms.LocalizationTransform; @@ -35,7 +31,7 @@ * @date 02/07/2015 * Samples, Phenotypes and Ontologies Team, EMBL-EBI */ -@Controller +@RestController public class V1SearchController { Gson gson = new Gson(); diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1SelectController.java b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1SelectController.java index eeb0008ed..300640643 100644 --- a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1SelectController.java +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1SelectController.java @@ -11,9 +11,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.*; import uk.ac.ebi.spot.ols.repository.Validation; import uk.ac.ebi.spot.ols.repository.solr.OlsSolrClient; import uk.ac.ebi.spot.ols.repository.transforms.LocalizationTransform; @@ -28,7 +26,7 @@ import java.util.function.Function; import java.util.stream.Collectors; -@Controller +@RestController public class V1SelectController { Gson gson = new Gson(); diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1SuggestController.java b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1SuggestController.java index 2cc170795..74db8821a 100644 --- a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1SuggestController.java +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v1/V1SuggestController.java @@ -10,9 +10,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.*; import uk.ac.ebi.spot.ols.repository.Validation; import uk.ac.ebi.spot.ols.repository.solr.OlsSolrClient; import uk.ac.ebi.spot.ols.repository.v1.V1OntologyRepository; @@ -22,7 +20,7 @@ import java.nio.charset.StandardCharsets; import java.util.*; -@Controller +@RestController public class V1SuggestController { Gson gson = new Gson(); diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2ClassController.java b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2ClassController.java index d8b8b4fde..b35203b4c 100644 --- a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2ClassController.java +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2ClassController.java @@ -15,10 +15,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.util.MultiValueMap; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.*; import org.springframework.web.util.UriUtils; import uk.ac.ebi.spot.ols.controller.api.v2.helpers.DynamicQueryHelper; import uk.ac.ebi.spot.ols.controller.api.v2.responses.V2PagedAndFacetedResponse; @@ -35,7 +32,7 @@ import java.util.List; import java.util.Map; -@Controller +@RestController @RequestMapping("/api/v2") public class V2ClassController { @@ -101,7 +98,7 @@ public HttpEntity getClass( iri = UriUtils.decode(iri, "UTF-8"); - V2Entity entity = classRepository.getByOntologyIdAndIri(ontologyId, iri, lang); + V2Entity entity = classRepository.findByOntologyAndIri(ontologyId, iri, lang); if (entity == null) throw new ResourceNotFoundException(); return new ResponseEntity<>( entity, HttpStatus.OK); } diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2EntityController.java b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2EntityController.java index 6c760ae8e..8d8c254bd 100644 --- a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2EntityController.java +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2EntityController.java @@ -10,10 +10,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.util.MultiValueMap; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.*; import org.springframework.web.util.UriUtils; import uk.ac.ebi.spot.ols.controller.api.v2.helpers.DynamicQueryHelper; import uk.ac.ebi.spot.ols.controller.api.v2.responses.V2PagedAndFacetedResponse; @@ -28,7 +25,7 @@ import java.util.List; import java.util.Map; -@Controller +@RestController @RequestMapping("/api/v2") public class V2EntityController { diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2IndividualController.java b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2IndividualController.java index bff23a360..c381840b5 100644 --- a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2IndividualController.java +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2IndividualController.java @@ -11,10 +11,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.util.MultiValueMap; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.*; import org.springframework.web.util.UriUtils; import uk.ac.ebi.spot.ols.controller.api.v2.helpers.DynamicQueryHelper; import uk.ac.ebi.spot.ols.controller.api.v2.responses.V2PagedAndFacetedResponse; @@ -29,7 +26,7 @@ import java.util.List; import java.util.Map; -@Controller +@RestController @RequestMapping("/api/v2") public class V2IndividualController { diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2OntologyController.java b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2OntologyController.java index 6a9c8501e..1d322cd1d 100644 --- a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2OntologyController.java +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2OntologyController.java @@ -12,10 +12,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.*; import uk.ac.ebi.spot.ols.controller.api.v2.helpers.DynamicQueryHelper; import uk.ac.ebi.spot.ols.controller.api.v2.responses.V2PagedAndFacetedResponse; import uk.ac.ebi.spot.ols.model.v2.V2Entity; @@ -29,7 +26,7 @@ import java.util.List; import java.util.Map; -@Controller +@RestController @RequestMapping("/api/v2/ontologies") public class V2OntologyController { diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2OntologySKOSConceptController.java b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2OntologySKOSConceptController.java new file mode 100644 index 000000000..a8d102245 --- /dev/null +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2OntologySKOSConceptController.java @@ -0,0 +1,341 @@ +package uk.ac.ebi.spot.ols.controller.api.v2; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.google.gson.JsonObject; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.data.web.PagedResourcesAssembler; +import org.springframework.hateoas.MediaTypes; +import org.springframework.hateoas.PagedModel; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.util.UriUtils; +import uk.ac.ebi.spot.ols.controller.api.v1.TopConceptEnum; +import uk.ac.ebi.spot.ols.model.Edge; +import uk.ac.ebi.spot.ols.model.Node; +import uk.ac.ebi.spot.ols.model.SKOSRelation; +import uk.ac.ebi.spot.ols.model.v2.V2Entity; +import uk.ac.ebi.spot.ols.repository.v1.TreeNode; +import uk.ac.ebi.spot.ols.repository.v2.V2SKOSRepository; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.*; + +/** + * @author Erhun Giray TUNCAY + * @email giray.tuncay@tib.eu + * TIB-Leibniz Information Center for Science and Technology + */ +@RestController +@RequestMapping("/api/v2/ontologies") +@Tag(name = "v2-ontology-skos-controller", description = "SKOS concept hierarchies and relations extracted from individuals (instances) from a particular ontology in this service") +public class V2OntologySKOSConceptController { + + @Autowired + V2SKOSRepository skosRepository; + + @Operation(description = "Get complete SKOS concept hierarchy or only top concepts based on alternative top concept identification methods and concept relations. If only top concepts are identified, they can be used to extract the following levels of the concept tree one by one using the /{onto}/conceptrelations/{iri} method with broader or narrower concept relations.") + @RequestMapping(path = "/{onto}/skos/tree", produces = {MediaType.APPLICATION_JSON_VALUE, MediaTypes.HAL_JSON_VALUE}, method = RequestMethod.GET) + HttpEntity>> getSKOSConceptHierarchyByOntology( + @Parameter(description = "ontology ID", required = true) + @PathVariable("onto") String ontologyId, + @Parameter(description = "infer top concepts by schema (hasTopConcept) or TopConceptOf property or broader/narrower relationships", required = true) + @RequestParam(value = "find_roots", required = true, defaultValue = "SCHEMA") TopConceptEnum topConceptIdentification, + @Parameter(description = "infer from narrower or broader relationships", required = true) + @RequestParam(value = "narrower", required = true, defaultValue = "false") boolean narrower, + @Parameter(description = "Extract the whole tree with children or only the top concepts", required = true) + @RequestParam(value = "with_children", required = true, defaultValue = "false") boolean withChildren, + @RequestParam(value = "obsoletes", required = false, defaultValue = "false") Boolean obsoletes, + @RequestParam(value = "lang", required = false, defaultValue = "en") String lang, + Pageable pageable) throws IOException { + ontologyId = ontologyId.toLowerCase(); + if (TopConceptEnum.RELATIONSHIPS == topConceptIdentification) + return new ResponseEntity<>(skosRepository.conceptTreeWithoutTop(ontologyId,narrower,withChildren,obsoletes,lang,pageable), HttpStatus.OK); + else + return new ResponseEntity<>(skosRepository.conceptTree(ontologyId,TopConceptEnum.SCHEMA == topConceptIdentification,narrower, withChildren,obsoletes,lang,pageable), HttpStatus.OK); + } + + @Operation(description = "Display complete SKOS concept hierarchy or only top concepts based on alternative top concept identification methods and concept relations. If only top concepts are identified, they can be used to extract the following levels of the concept tree one by one using the /{onto}/displayconceptrelations/{iri} method with broader or narrower concept relations.") + @RequestMapping(path = "/{onto}/skos/displaytree", produces = {MediaType.TEXT_PLAIN_VALUE}, method = RequestMethod.GET) + @ResponseBody + HttpEntity displaySKOSConceptHierarchyByOntology( + @Parameter(description = "ontology ID", required = true) + @PathVariable("onto") String ontologyId, + @Parameter(description = "infer top concepts by schema (hasTopConcept) or TopConceptOf property or broader/narrower relationships", required = true) + @RequestParam(value = "find_roots", required = true, defaultValue = "SCHEMA") TopConceptEnum topConceptIdentification, + @Parameter(description = "infer from narrower or broader relationships", required = true) + @RequestParam(value = "narrower", required = true, defaultValue = "false") boolean narrower, + @Parameter(description = "Extract the whole tree with children or only the top concepts", required = true) + @RequestParam(value = "with_children", required = true, defaultValue = "false") boolean withChildren, + @Parameter(description = "display related concepts", required = true) + @RequestParam(value = "display_related", required = true, defaultValue = "false") boolean displayRelated, + @RequestParam(value = "obsoletes", required = false, defaultValue = "false") Boolean obsoletes, + @RequestParam(value = "lang", required = false, defaultValue = "en") String lang, + Pageable pageable) throws IOException { + ontologyId = ontologyId.toLowerCase(); + List> rootIndividuals = null; + if(TopConceptEnum.RELATIONSHIPS == topConceptIdentification) + rootIndividuals = skosRepository.conceptTreeWithoutTop(ontologyId,narrower,withChildren,obsoletes,lang,pageable); + else + rootIndividuals = skosRepository.conceptTree(ontologyId,TopConceptEnum.SCHEMA == topConceptIdentification,narrower, withChildren,obsoletes,lang,pageable); + StringBuilder sb = new StringBuilder(); + for (TreeNode root : rootIndividuals) { + sb.append(root.getIndex() + " , "+ root.getData().any().get("label").toString() + " , " + root.getData().any().get("iri").toString()).append("\n"); + sb.append(generateConceptHierarchyTextByOntology(root, displayRelated)); + } + + return new HttpEntity(sb.toString()); + } + + @Operation(description = "Get partial SKOS concept hierarchy based on the encoded iri of the designated top concept") + @RequestMapping(path = "/{onto}/skos/{iri}/tree", produces = {MediaType.APPLICATION_JSON_VALUE, MediaTypes.HAL_JSON_VALUE}, method = RequestMethod.GET) + HttpEntity> getSKOSConceptHierarchyByOntologyAndIri( + @Parameter(description = "ontology ID", required = true) + @PathVariable("onto") String ontologyId, + @Parameter(description = "encoded concept IRI", required = true) + @PathVariable("iri") String iri, + @Parameter(description = "infer from narrower or broader relationships", required = true) + @RequestParam(value = "narrower", required = true, defaultValue = "false") boolean narrower, + @Parameter(description = "index value for the root term", required = true) + @RequestParam(value = "index", required = true, defaultValue = "1") String index, + @RequestParam(value = "obsoletes", required = false, defaultValue = "false") Boolean obsoletes, + @RequestParam(value = "lang", required = false, defaultValue = "en") String lang, + Pageable pageable) throws IOException { + ontologyId = ontologyId.toLowerCase(); + TreeNode topConcept = new TreeNode(new V2Entity(new JsonObject())); + String decodedIri; + decodedIri = UriUtils.decode(iri, "UTF-8"); + topConcept = skosRepository.conceptSubTree(ontologyId, decodedIri, narrower, index, obsoletes, lang, pageable); + + if (topConcept.getData().any().get("iri").toString() == null) + throw new ResourceNotFoundException("No roots could be found for " + ontologyId ); + return new ResponseEntity<>(topConcept, HttpStatus.OK); + } + + @Operation(description = "Display partial SKOS concept hierarchy based on the encoded iri of the designated top concept") + @RequestMapping(path = "/{onto}/skos/{iri}/displaytree", produces = {MediaType.TEXT_PLAIN_VALUE}, method = RequestMethod.GET) + @ResponseBody + HttpEntity displaySKOSConceptHierarchyByOntologyAndIri( + @Parameter(description = "ontology ID", required = true) + @PathVariable("onto") String ontologyId, + @Parameter(description = "encoded concept IRI", required = true) + @PathVariable("iri") String iri, + @Parameter(description = "infer from narrower or broader relationships", required = true) + @RequestParam(value = "narrower", required = true, defaultValue = "false") boolean narrower, + @Parameter(description = "display related concepts", required = true) + @RequestParam(value = "display_related", required = true, defaultValue = "false") boolean displayRelated, + @Parameter(description = "index value for the root term", required = true) + @RequestParam(value = "index", required = true, defaultValue = "1") String index, + @RequestParam(value = "obsoletes", required = false, defaultValue = "false") Boolean obsoletes, + @RequestParam(value = "lang", required = false, defaultValue = "en") String lang, + Pageable pageable) throws IOException { + ontologyId = ontologyId.toLowerCase(); + TreeNode topConcept = new TreeNode(new V2Entity(new JsonObject())); + String decodedIri; + StringBuilder sb = new StringBuilder(); + decodedIri = UriUtils.decode(iri, "UTF-8"); + topConcept = skosRepository.conceptSubTree(ontologyId, decodedIri, narrower, index, obsoletes, lang, pageable); + + sb.append(topConcept.getIndex() + " , "+ topConcept.getData().any().get("label").toString() + " , " + topConcept.getData().any().get("iri").toString()).append("\n"); + sb.append(generateConceptHierarchyTextByOntology(topConcept, displayRelated)); + + return new HttpEntity(sb.toString()); + } + + @Operation(description = "Broader, Narrower and Related concept relations of a concept are listed in JSON if the concept iri is provided in encoded format.") + @RequestMapping(path = "/{onto}/skos/{iri}/relations", produces = {MediaType.APPLICATION_JSON_VALUE, MediaTypes.HAL_JSON_VALUE}, method = RequestMethod.GET) + public HttpEntity> findRelatedConcepts( + @Parameter(description = "ontology ID", required = true) + @PathVariable("onto") String ontologyId, + @Parameter(description = "encoded concept IRI", required = true) + @PathVariable("iri") String iri, + @Parameter(description = "skos based concept relation type", required = true) + @RequestParam(value = "relation_type", required = true, defaultValue = "broader") + @Schema(type = "string", allowableValues = { "broader", "narrower", "related" }) SKOSRelation relationType, + @RequestParam(value = "obsoletes", required = false, defaultValue = "false") Boolean obsoletes, + @RequestParam(value = "lang", required = false, defaultValue = "en") String lang, + Pageable pageable, + PagedResourcesAssembler assembler) { + + ontologyId = ontologyId.toLowerCase(); + List related = new ArrayList(); + String decodedIri = UriUtils.decode(iri, "UTF-8"); + related = skosRepository.findRelated(ontologyId, decodedIri, relationType.getPropertyName(),lang); + + final int start = (int)pageable.getOffset(); + final int end = Math.min((start + pageable.getPageSize()), related.size()); + Page conceptPage = new PageImpl<>(related.subList(start, end), pageable, related.size()); + + return new ResponseEntity<>( assembler.toModel(conceptPage), HttpStatus.OK); + + } + + @Operation(description = "Broader, Narrower and Related concept relations of a concept are displayed as text if the concept iri is provided in encoded format.") + @RequestMapping(path = "/{onto}/skos/{iri}/displayrelations", produces = {MediaType.TEXT_PLAIN_VALUE}, method = RequestMethod.GET) + @ResponseBody + public HttpEntity displayRelatedConcepts( + @Parameter(description = "ontology ID", required = true) + @PathVariable("onto") String ontologyId, + @Parameter(description = "encoded concept IRI", required = true) + @PathVariable("iri") String iri, + @Parameter(description = "skos based concept relation type", required = true) + @RequestParam(value = "relation_type", required = true, defaultValue = "broader") + @Schema(type = "string", allowableValues = { "broader", "narrower", "related" }) SKOSRelation relationType, + @RequestParam(value = "obsoletes", required = false, defaultValue = "false") Boolean obsoletes, + @RequestParam(value = "lang", required = false, defaultValue = "en") String lang, + Pageable pageable, + PagedResourcesAssembler assembler) { + StringBuilder sb = new StringBuilder(); + ontologyId = ontologyId.toLowerCase(); + List related = new ArrayList(); + String decodedIri = UriUtils.decode(iri, "UTF-8"); + related = skosRepository.findRelated(ontologyId, decodedIri, relationType.getPropertyName(),lang); + + final int start = (int)pageable.getOffset(); + final int end = Math.min((start + pageable.getPageSize()), related.size()); + Page conceptPage = new PageImpl<>(related.subList(start, end), pageable, related.size()); + int count = 0; + for (V2Entity individual : conceptPage.getContent()) + sb.append(++count).append(" , ").append(individual.any().get("label").toString()).append(" , ").append(individual.any().get("iri").toString()).append("\n"); + + return new HttpEntity<>( sb.toString()); + + } + + @Operation(description = "Broader, Narrower and Related concept relations of a concept are listed in JSON if the concept iri is provided in encoded format. The relationship is identified indirectly based on the related concept's relation to the concept in question. This requires traversing all the available concepts and checking if they are related to the concept in question. For this reason, this method is relatively slower than the displayconceptrelations method. Nevertheless, it enables to identify unforeseen relations of the concept in question") + @RequestMapping(path = "/{onto}/skos/{iri}/indirectrelations", produces = {MediaType.APPLICATION_JSON_VALUE, MediaTypes.HAL_JSON_VALUE}, method = RequestMethod.GET) + public HttpEntity> findRelatedConceptsIndirectly( + @Parameter(description = "ontology ID", required = true) + @PathVariable("onto") String ontologyId, + @Parameter(description = "encoded concept IRI", required = true) + @PathVariable("iri") String iri, + @Parameter(description = "skos based concept relation type", required = true) + @RequestParam(value = "relation_type", required = true, defaultValue = "broader") + @Schema(type = "string", allowableValues = { "broader", "narrower", "related" }) SKOSRelation relationType, + @RequestParam(value = "obsoletes", required = false, defaultValue = "false") Boolean obsoletes, + @RequestParam(value = "lang", required = false, defaultValue = "en") String lang, + Pageable pageable) throws IOException { + ontologyId = ontologyId.toLowerCase(); + List related = new ArrayList(); + String decodedIri = UriUtils.decode(iri, "UTF-8"); + related = skosRepository.findRelatedIndirectly(ontologyId, decodedIri, relationType.getPropertyName(), obsoletes,lang,pageable); + + return new ResponseEntity<>( related, HttpStatus.OK); + + } + + @Operation(description = "Broader, Narrower and Related concept relations of a concept are listed in JSON if the concept iri is provided in encoded format. The relationship is identified indirectly based on the related concept's relation to the concept in question. This requires traversing all the available concepts and checking if they are related to the concept in question. For this reason, this method is relatively slower than the displayconceptrelations method. Nevertheless, it enables to identify unforeseen relations of the concept in question") + @RequestMapping(path = "/{onto}/skos/{iri}/displayindirectrelations", produces = {MediaType.TEXT_PLAIN_VALUE}, method = RequestMethod.GET) + @ResponseBody + public HttpEntity displayRelatedConceptsIndirectly( + @Parameter(description = "ontology ID", required = true) + @PathVariable("onto") String ontologyId, + @Parameter(description = "encoded concept IRI", required = true) + @PathVariable("iri") String iri, + @Parameter(description = "skos based concept relation type", required = true) + @RequestParam(value = "relation_type", required = true, defaultValue = "broader") + @Schema(type = "string", allowableValues = { "broader", "narrower", "related" }) SKOSRelation relationType, + @Parameter(description = "Page size to retrieve individuals", required = true) + @RequestParam(value = "obsoletes", required = false, defaultValue = "false") Boolean obsoletes, + @RequestParam(value = "lang", required = false, defaultValue = "en") String lang, + Pageable pageable) throws IOException { + StringBuilder sb = new StringBuilder(); + ontologyId = ontologyId.toLowerCase(); + List related = new ArrayList(); + String decodedIri = UriUtils.decode(iri, "UTF-8"); + related = skosRepository.findRelatedIndirectly(ontologyId, decodedIri, relationType.getPropertyName(), obsoletes,lang,pageable); + + int count = 0; + for (V2Entity individual : related) + sb.append(++count).append(" , ").append(individual.any().get("label").toString()).append(" , ").append(individual.any().get("iri").toString()).append("\n"); + + + return new ResponseEntity<>( sb.toString(), HttpStatus.OK); + + } + + @Operation(description = "Node and Edge definitions needed to visualize the nodes that are directly related with the subject term. Ontology ID and encoded iri are required. ") + @RequestMapping(path = "/{onto}/skos/{iri}/graph", produces = {MediaType.APPLICATION_JSON_VALUE, MediaTypes.HAL_JSON_VALUE}, method = RequestMethod.GET) + public HttpEntity retrieveImmediateGraph( + @Parameter(description = "ontology ID", required = true) + @PathVariable("onto") String ontologyId, + @Parameter(description = "encoded concept IRI", required = true) + @PathVariable("iri") String iri, + @RequestParam(value = "lang", required = false, defaultValue = "en") String lang){ + + List related = new ArrayList(); + String decodedIri = UriUtils.decode(iri, "UTF-8"); + + V2Entity subjectTerm = skosRepository.findByOntologyAndIri(ontologyId, decodedIri, lang); + + related = skosRepository.findRelated(ontologyId, decodedIri, SKOSRelation.related.getPropertyName(), lang); + + List narrower = new ArrayList(); + narrower = skosRepository.findRelated(ontologyId, decodedIri, SKOSRelation.narrower.getPropertyName(), lang); + + List broader = new ArrayList(); + broader = skosRepository.findRelated(ontologyId, decodedIri, SKOSRelation.broader.getPropertyName(), lang); + + Set relatedNodes = new HashSet(); + related.forEach(term -> relatedNodes.add(new Node(term.any().get("iri").toString(), term.any().get("label").toString()))); + Set narrowerNodes = new HashSet(); + narrower.forEach(term -> narrowerNodes.add(new Node(term.any().get("iri").toString(), term.any().get("label").toString()))); + Set broaderNodes = new HashSet(); + broader.forEach(term -> broaderNodes.add(new Node(term.any().get("iri").toString(), term.any().get("label").toString()))); + + Set edges = new HashSet(); + relatedNodes.forEach(node -> edges.add(new Edge(decodedIri, node.getIri(), "related",SKOSRelation.related.getPropertyName()))); + narrowerNodes.forEach(node -> edges.add(new Edge(decodedIri, node.getIri(), "narrower",SKOSRelation.narrower.getPropertyName()))); + broaderNodes.forEach(node -> edges.add(new Edge(decodedIri, node.getIri(), "broader",SKOSRelation.broader.getPropertyName()))); + + Set nodes = new HashSet(); + nodes.add(new Node(decodedIri,subjectTerm.any().get("label").toString())); + nodes.addAll(relatedNodes); + nodes.addAll(broaderNodes); + nodes.addAll(narrowerNodes); + + + Map graph = new HashMap(); + graph.put("nodes", nodes); + graph.put("edges", edges); + ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter(); + try { + return new ResponseEntity<>(ow.writeValueAsString(graph),HttpStatus.OK); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + public StringBuilder generateConceptHierarchyTextByOntology(TreeNode rootConcept, boolean displayRelated) { + StringBuilder sb = new StringBuilder(); + for (TreeNode childConcept : rootConcept.getChildren()) { + sb.append(childConcept.getIndex() + " , "+ childConcept.getData().any().get("label").toString() + " , " + childConcept.getData().any().get("iri").toString()).append("\n"); + sb.append(generateConceptHierarchyTextByOntology(childConcept,displayRelated)); + } + if(displayRelated) + for (TreeNode relatedConcept : rootConcept.getRelated()) { + sb.append(relatedConcept.getIndex() + " , "+ relatedConcept.getData().any().get("label").toString() + " , " + relatedConcept.getData().any().get("iri").toString()).append("\n"); + sb.append(generateConceptHierarchyTextByOntology(relatedConcept,displayRelated)); + } + return sb; + } + + @ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Resource not found") + @ExceptionHandler(ResourceNotFoundException.class) + public void handleError(HttpServletRequest req, Exception exception) { + } + +} diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2PropertyController.java b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2PropertyController.java index ee847f3cd..9d690a53c 100644 --- a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2PropertyController.java +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2PropertyController.java @@ -25,7 +25,7 @@ import java.util.List; import java.util.Map; -@Controller +@RestController @RequestMapping("/api/v2") public class V2PropertyController { diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2StatisticsController.java b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2StatisticsController.java index 0af7b2460..f676af1dc 100644 --- a/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2StatisticsController.java +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/controller/api/v2/V2StatisticsController.java @@ -11,8 +11,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.*; import uk.ac.ebi.spot.ols.model.v2.V2Statistics; import uk.ac.ebi.spot.ols.repository.solr.OlsSolrClient; @@ -20,7 +19,7 @@ import java.util.HashMap; import java.util.Map; -@Controller +@RestController @RequestMapping("/api/v2/stats") public class V2StatisticsController { diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/model/Edge.java b/backend/src/main/java/uk/ac/ebi/spot/ols/model/Edge.java new file mode 100644 index 000000000..ba2a19984 --- /dev/null +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/model/Edge.java @@ -0,0 +1,37 @@ +package uk.ac.ebi.spot.ols.model; + +/** + * @author Erhun Giray TUNCAY + * @email giray.tuncay@tib.eu + * TIB-Leibniz Information Center for Science and Technology + */ +public class Edge { + String source; + String target; + String label; + String uri; + + public Edge(String source, String target, String label, String uri) { + this.source = source; + this.target = target; + this.label = label; + this.uri = uri; + } + + public String getSource() { + return source; + } + + public String getTarget() { + return target; + } + + public String getLabel() { + return label; + } + + public String getUri() { + return uri; + } + +} diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/model/Node.java b/backend/src/main/java/uk/ac/ebi/spot/ols/model/Node.java new file mode 100644 index 000000000..a73bba608 --- /dev/null +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/model/Node.java @@ -0,0 +1,25 @@ +package uk.ac.ebi.spot.ols.model; + +/** + * @author Erhun Giray TUNCAY + * @email giray.tuncay@tib.eu + * TIB-Leibniz Information Center for Science and Technology + */ +public class Node { + String iri; + String label; + + public Node(String iri, String label) { + this.iri = iri; + this.label = label; + } + + public String getIri() { + return iri; + } + + public String getLabel() { + return label; + } + +} diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/model/SKOSRelation.java b/backend/src/main/java/uk/ac/ebi/spot/ols/model/SKOSRelation.java new file mode 100644 index 000000000..f127360d6 --- /dev/null +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/model/SKOSRelation.java @@ -0,0 +1,37 @@ +package uk.ac.ebi.spot.ols.model; + +/** + * @author Erhun Giray TUNCAY + * @email giray.tuncay@tib.eu + * TIB-Leibniz Information Center for Science and Technology + */ +public enum SKOSRelation { + + broader("http://www.w3.org/2004/02/skos/core#broader"), + + narrower("http://www.w3.org/2004/02/skos/core#narrower"), + + related("http://www.w3.org/2004/02/skos/core#related"), + + hasTopConcept("http://www.w3.org/2004/02/skos/core#hasTopConcept"), + + topConceptOf("http://www.w3.org/2004/02/skos/core#topConceptOf"); + + private final String propertyName; + + SKOSRelation(String propertyName) { + this.propertyName = propertyName; + } + + public static String[] getNames() { + String[] commands = new String[SKOSRelation.values().length]; + for (int i = 0;i preferredRootTerms = new HashSet<>(); public boolean isSkos; + public boolean skosNarrower; + + public TopConceptEnum skosRoot; + public boolean allowDownload; } diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/repository/solr/OlsSolrClient.java b/backend/src/main/java/uk/ac/ebi/spot/ols/repository/solr/OlsSolrClient.java index a1cb659e4..6168d4f31 100644 --- a/backend/src/main/java/uk/ac/ebi/spot/ols/repository/solr/OlsSolrClient.java +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/repository/solr/OlsSolrClient.java @@ -2,6 +2,7 @@ import com.google.gson.Gson; import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.google.gson.JsonParser; import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; @@ -95,6 +96,7 @@ public JsonElement getFirst(OlsSolrQuery query) { if(qr.getResults().getNumFound() < 1) { logger.debug("Expected at least 1 result for solr getFirst for solr query = {}", query.constructQuery().jsonStr()); + //return new JsonObject(); throw new RuntimeException("Expected at least 1 result for solr getFirst"); } diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/repository/v1/TreeNode.java b/backend/src/main/java/uk/ac/ebi/spot/ols/repository/v1/TreeNode.java new file mode 100644 index 000000000..1ca07e9b3 --- /dev/null +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/repository/v1/TreeNode.java @@ -0,0 +1,108 @@ +package uk.ac.ebi.spot.ols.repository.v1; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; + +public class TreeNode implements Serializable { + + /** + * + */ + private static final long serialVersionUID = -343190255910189166L; + private Collection> children = new ArrayList>(); + private Collection> related = new ArrayList>(); + private Collection> parent = new ArrayList>(); + private String index; + private T data = null; + + public TreeNode(T data) { + this.data = data; + } + + public TreeNode(T data, Collection> parent) { + this.data = data; + this.parent = parent; + } + + public Collection> getChildren() { + return children; + } + public void setChildren(Collection> children) { + this.children = children; + } + + public void addChild(T data) { + TreeNode child = new TreeNode(data); + this.children.add(child); + } + + public void addChild(TreeNode child) { + this.children.add(child); + } + + public void addRelated(T data) { + TreeNode related = new TreeNode(data); + this.related.add(related); + } + + public void addRelated(TreeNode related) { + this.related.add(related); + } + + public void addParent(T data) { + TreeNode parent = new TreeNode(data); + this.parent.add(parent); + } + + public void addParent(TreeNode parent) { + this.parent.add(parent); + } + + public Collection> getRelated() { + return related; + } + public void setRelated(Collection> related) { + this.related = related; + } + public Collection> getParent() { + return parent; + } + public void setParent(Collection> parent) { + this.parent = parent; + } + public String getIndex() { + return index; + } + public void setIndex(String index) { + this.index = index; + } + + public T getData() { + return this.data; + } + + public void setData(T data) { + this.data = data; + } + + public boolean isRoot() { + return this.parent.size() == 0; + } + + public boolean isLeaf() { + return this.children.size() == 0; + } + + public void resetParent() { + this.parent = new ArrayList>(); + } + + public void resetChildren() { + this.children = new ArrayList>(); + } + + public void resetRelated() { + this.related = new ArrayList>(); + } +} diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/repository/v1/V1TermRepository.java b/backend/src/main/java/uk/ac/ebi/spot/ols/repository/v1/V1TermRepository.java index f4965be37..6be9713db 100644 --- a/backend/src/main/java/uk/ac/ebi/spot/ols/repository/v1/V1TermRepository.java +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/repository/v1/V1TermRepository.java @@ -4,6 +4,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.Cacheable; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Component; @@ -16,9 +17,13 @@ import uk.ac.ebi.spot.ols.repository.solr.SearchType; import uk.ac.ebi.spot.ols.repository.v1.mappers.V1TermMapper; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; @Component public class V1TermRepository { @@ -291,4 +296,225 @@ public Page getInstances(String ontologyId, String iri, Pageable p throw new RuntimeException(); } + + + @Cacheable(value = "concepttree", key="#ontologyId.concat('-').concat(#schema).concat('-').concat(#narrower).concat('-').concat(#withChildren)") + public List> conceptTree (String ontologyId, boolean schema, boolean narrower, boolean withChildren, Boolean obsoletes, String lang, Pageable pageable){ + Page terms = this.findAllByOntology(ontologyId, obsoletes, lang, pageable); + List listOfTerms = new ArrayList(); + listOfTerms.addAll(terms.getContent()); + + while(terms.hasNext()) { + terms = this.findAllByOntology(ontologyId, obsoletes, lang, terms.nextPageable()); + listOfTerms.addAll(terms.getContent()); + } + + List> rootTerms = new ArrayList>(); + int count = 0; + + if(schema) { + for (V1Term term : listOfTerms) + if (term.annotation.get("hasTopConcept") != null) { + for (String iriTopConcept : (LinkedHashSet) term.annotation.get("hasTopConcept")) { + V1Term topConceptTerm = findTerm(listOfTerms,iriTopConcept); + TreeNode topConcept = new TreeNode(topConceptTerm); + topConcept.setIndex(String.valueOf(++count)); + if(withChildren) { + if(narrower) + populateChildrenandRelatedByNarrower(topConceptTerm,topConcept,listOfTerms); + else + populateChildrenandRelatedByBroader(topConceptTerm,topConcept,listOfTerms); + } + rootTerms.add(topConcept); + } + } + } else for (V1Term term : listOfTerms) { + TreeNode tree = new TreeNode(term); + + if (tree.isRoot() && term.annotation.get("topConceptOf") != null) { + tree.setIndex(String.valueOf(++count)); + if(withChildren) { + if(narrower) + populateChildrenandRelatedByNarrower(term,tree,listOfTerms); + else + populateChildrenandRelatedByBroader(term,tree,listOfTerms); + } + rootTerms.add(tree); + } + } + + return rootTerms; + } + + @Cacheable(value = "concepttree", key="#ontologyId.concat('-').concat(#narrower).concat('-').concat(#withChildren)") + public List> conceptTreeWithoutTop (String ontologyId, boolean narrower, boolean withChildren, Boolean obsoletes, String lang, Pageable pageable){ + Page terms = this.findAllByOntology(ontologyId, obsoletes, lang, pageable); + List listOfTerms = new ArrayList(); + listOfTerms.addAll(terms.getContent()); + + while(terms.hasNext()) { + terms = this.findAllByOntology(ontologyId, obsoletes, lang, terms.nextPageable()); + listOfTerms.addAll(terms.getContent()); + } + + Set rootIRIs = new HashSet(); + List> rootTerms = new ArrayList>(); + int count = 0; + if(!narrower) { + for (V1Term term : listOfTerms) { + if(term.annotation != null && term.annotation.get("broader") != null) { + for (String iriBroader : (LinkedHashSet) term.annotation.get("broader")) { + V1Term broaderTerm = findTerm(listOfTerms, iriBroader); + if (broaderTerm.annotation != null && broaderTerm.annotation.get("broader") == null) { + rootIRIs.add(iriBroader); + } + + } + } + } + + for (String iri : rootIRIs) { + V1Term topConceptTerm = findTerm(listOfTerms, iri); + TreeNode topConcept = new TreeNode(topConceptTerm); + topConcept.setIndex(String.valueOf(++count)); + if(withChildren) + populateChildrenandRelatedByBroader(topConceptTerm,topConcept,listOfTerms); + rootTerms.add(topConcept); + } + + } else { + for (V1Term term : listOfTerms) { + if (term.annotation != null && term.annotation.get("narrower") != null) { + boolean root = true; + for (V1Term v1Term : listOfTerms) { + if (v1Term.annotation != null && v1Term.annotation.get("narrower") != null) { + for (String iriNarrower : (LinkedHashSet) v1Term.annotation.get("narrower")) { + if (term.iri.equals(iriNarrower)) + root = false; + } + } + } + + if (root) { + TreeNode topConcept = new TreeNode(term); + topConcept.setIndex(String.valueOf(++count)); + if (withChildren) + populateChildrenandRelatedByNarrower(term, topConcept, listOfTerms); + rootTerms.add(topConcept); + } + } + } + } + + return rootTerms; + } + + @Cacheable(value = "concepttree", key="#ontologyId.concat('-').concat('s').concat('-').concat(#iri).concat('-').concat(#narrower).concat('-').concat(#index)") + public TreeNode conceptSubTree(String ontologyId, String iri, boolean narrower, String index, Boolean obsoletes, String lang, Pageable pageable){ + Page terms = this.findAllByOntology(ontologyId, obsoletes, lang, pageable); + List listOfTerms = new ArrayList(); + listOfTerms.addAll(terms.getContent()); + + while(terms.hasNext()) { + terms = this.findAllByOntology(ontologyId, obsoletes, lang, terms.nextPageable()); + listOfTerms.addAll(terms.getContent()); + } + + V1Term topConceptTerm = findTerm(listOfTerms,iri); + TreeNode topConcept = new TreeNode(topConceptTerm); + topConcept.setIndex(index); + if(narrower) + populateChildrenandRelatedByNarrower(topConceptTerm,topConcept,listOfTerms); + else + populateChildrenandRelatedByBroader(topConceptTerm,topConcept,listOfTerms); + + return topConcept; + } + + public V1Term findTerm(List wholeList, String iri) { + for (V1Term term : wholeList) + if(term.iri.equals(iri)) + return term; + return new V1Term(); + } + + public List findRelated(String ontologyId, String iri, String relationType, String lang) { + List related = new ArrayList(); + V1Term term = this.findByOntologyAndIri(ontologyId, iri, lang); + if (term != null) + if (term.annotation.get(relationType) != null) + for (String iriBroader : (LinkedHashSet) term.annotation.get(relationType)) + related.add(this.findByOntologyAndIri(ontologyId, iriBroader, lang)); + + return related; + } + + public ListfindRelatedIndirectly(String ontologyId, String iri, String relationType, Boolean obsoletes, String lang, Pageable pageable){ + List related = new ArrayList(); + + V1Term v1Term = this.findByOntologyAndIri(ontologyId, iri, lang); + if(v1Term == null) + return related; + if(v1Term.iri == null) + return related; + + Page terms = this.findAllByOntology(ontologyId, obsoletes, lang, pageable); + List listOfTerms = new ArrayList(); + listOfTerms.addAll(terms.getContent()); + + while(terms.hasNext()) { + terms = this.findAllByOntology(ontologyId, obsoletes, lang, terms.nextPageable()); + listOfTerms.addAll(terms.getContent()); + } + + for (V1Term term : listOfTerms) { + if (term != null) + if (term.annotation.get(relationType) != null) + for (String iriRelated : (LinkedHashSet) term.annotation.get(relationType)) + if(iriRelated.equals(iri)) + related.add(term); + } + + return related; + } + + public void populateChildrenandRelatedByNarrower(V1Term term, TreeNode tree, List listOfTerms ) { + + if (term.annotation != null) + for (String iriRelated : (LinkedHashSet) term.annotation.getOrDefault("related", new LinkedHashSet())) { + TreeNode related = new TreeNode(findTerm(listOfTerms, iriRelated)); + related.setIndex(tree.getIndex() + ".related"); + tree.addRelated(related); + } + int count = 0; + if (term.annotation != null) + for (String iriChild : (LinkedHashSet) term.annotation.getOrDefault("narrower", new LinkedHashSet())) { + V1Term childTerm = findTerm(listOfTerms, iriChild); + TreeNode child = new TreeNode(childTerm); + child.setIndex(tree.getIndex() + "." + ++count); + populateChildrenandRelatedByNarrower(childTerm, child, listOfTerms); + tree.addChild(child); + } + } + + public void populateChildrenandRelatedByBroader(V1Term term, TreeNode tree, List listOfTerms) { + if (term.annotation != null) + for (String iriRelated : (LinkedHashSet) term.annotation.getOrDefault("related", new LinkedHashSet())) { + TreeNode related = new TreeNode(findTerm(listOfTerms, iriRelated)); + related.setIndex(tree.getIndex() + ".related"); + tree.addRelated(related); + } + int count = 0; + for ( V1Term v1Term : listOfTerms) { + if (v1Term.annotation != null) + for (String iriBroader : (LinkedHashSet) v1Term.annotation.getOrDefault("broader",new LinkedHashSet())) + if(term.iri != null) + if (term.iri.equals(iriBroader)) { + TreeNode child = new TreeNode(v1Term); + child.setIndex(tree.getIndex()+"."+ ++count); + populateChildrenandRelatedByBroader(v1Term,child,listOfTerms); + tree.addChild(child); + } + } + } } diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/repository/v1/mappers/V1OntologyMapper.java b/backend/src/main/java/uk/ac/ebi/spot/ols/repository/v1/mappers/V1OntologyMapper.java index 22f62e4db..0aa47ffab 100644 --- a/backend/src/main/java/uk/ac/ebi/spot/ols/repository/v1/mappers/V1OntologyMapper.java +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/repository/v1/mappers/V1OntologyMapper.java @@ -3,6 +3,7 @@ import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import uk.ac.ebi.spot.ols.controller.api.v1.TopConceptEnum; import uk.ac.ebi.spot.ols.model.v1.V1Ontology; import uk.ac.ebi.spot.ols.model.v1.V1OntologyConfig; import uk.ac.ebi.spot.ols.repository.transforms.LocalizationTransform; @@ -61,9 +62,12 @@ public static V1Ontology mapOntology(JsonElement json, String lang) { ontology.config.preferredRootTerms = JsonHelper.getStrings(localizedJson, "preferredRootTerms"); ontology.config.isSkos = localizedJson.has("isSkos") && localizedJson.get("isSkos").getAsBoolean(); + if(ontology.config.isSkos) { + ontology.config.skosNarrower = localizedJson.has("skosNarrower") && localizedJson.get("skosNarrower").getAsBoolean(); + if (localizedJson.has("skosRoot")) + ontology.config.skosRoot = TopConceptEnum.valueOf(localizedJson.get("skosRoot").getAsString()); + } ontology.config.allowDownload = localizedJson.has("allowDownload") && localizedJson.get("allowDownload").getAsBoolean(); - - ontology.status = "LOADED"; ontology.numberOfTerms = Integer.parseInt(JsonHelper.getString(localizedJson, "numberOfClasses")); diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/repository/v2/V2ClassRepository.java b/backend/src/main/java/uk/ac/ebi/spot/ols/repository/v2/V2ClassRepository.java index 9e621e734..74287ccd3 100644 --- a/backend/src/main/java/uk/ac/ebi/spot/ols/repository/v2/V2ClassRepository.java +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/repository/v2/V2ClassRepository.java @@ -2,10 +2,12 @@ package uk.ac.ebi.spot.ols.repository.v2; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Primary; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.stereotype.Component; +import uk.ac.ebi.spot.ols.controller.api.v2.helpers.DynamicQueryHelper; import uk.ac.ebi.spot.ols.model.v2.V2Entity; import uk.ac.ebi.spot.ols.repository.neo4j.OlsNeo4jClient; import uk.ac.ebi.spot.ols.repository.solr.SearchType; @@ -19,11 +21,8 @@ import uk.ac.ebi.spot.ols.repository.v2.helpers.V2SearchFieldsParser; import java.io.IOException; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map; - +import java.util.*; +@Primary @Component public class V2ClassRepository { @@ -33,6 +32,7 @@ public class V2ClassRepository { @Autowired OlsNeo4jClient neo4jClient; + public OlsFacetedResultsPage find( Pageable pageable, String lang, String search, String searchFields, String boostFields, boolean exactMatch, Map> properties) throws IOException { @@ -82,7 +82,7 @@ public OlsFacetedResultsPage findByOntologyId( .map(V2Entity::new); } - public V2Entity getByOntologyIdAndIri(String ontologyId, String iri, String lang) throws ResourceNotFoundException { + public V2Entity findByOntologyAndIri(String ontologyId, String iri, String lang) throws ResourceNotFoundException { Validation.validateOntologyId(ontologyId); Validation.validateLang(lang); @@ -169,4 +169,29 @@ public Page getIndividualAncestorsByOntologyId(String ontologyId, Page .map(RemoveLiteralDatatypesTransform::transform) .map(V2Entity::new); } + + + public List allClassesOfOntology(String ontologyId, Boolean obsoletes, Pageable pageable, String lang) throws IOException { + Map> properties = new HashMap<>(); + if(!obsoletes) + properties.put("isObsolete", List.of("false")); + + Page terms = this.findByOntologyId(ontologyId, pageable, lang, null, null, null, false, DynamicQueryHelper.filterProperties(properties)); + List listOfTerms = new ArrayList(); + listOfTerms.addAll(terms.getContent()); + + while(terms.hasNext()) { + terms = findByOntologyId(ontologyId, terms.nextPageable(), lang, null, null, null, false, DynamicQueryHelper.filterProperties(properties)); + listOfTerms.addAll(terms.getContent()); + } + + return listOfTerms; + } + + public List getRelationsAsList(V2Entity entity, String relationType){ + if(entity.any().get(relationType) instanceof String) + return Arrays.asList((String) entity.any().get(relationType)); + else + return (ArrayList) entity.any().getOrDefault(relationType, new ArrayList()); + } } diff --git a/backend/src/main/java/uk/ac/ebi/spot/ols/repository/v2/V2SKOSRepository.java b/backend/src/main/java/uk/ac/ebi/spot/ols/repository/v2/V2SKOSRepository.java new file mode 100644 index 000000000..f20f4f183 --- /dev/null +++ b/backend/src/main/java/uk/ac/ebi/spot/ols/repository/v2/V2SKOSRepository.java @@ -0,0 +1,217 @@ +package uk.ac.ebi.spot.ols.repository.v2; + +import com.google.gson.JsonObject; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Component; +import uk.ac.ebi.spot.ols.model.v2.V2Entity; +import uk.ac.ebi.spot.ols.repository.v1.TreeNode; + +import java.io.IOException; +import java.util.*; + +import static uk.ac.ebi.spot.ols.model.SKOSRelation.*; + +/** + * @author Erhun Giray TUNCAY + * @email giray.tuncay@tib.eu + * TIB-Leibniz Information Center for Science and Technology + */ +@Component +public class V2SKOSRepository extends V2ClassRepository { + + @Cacheable(value = "concepttree", key="#ontologyId.concat('-').concat(#schema).concat('-').concat(#narrower).concat('-').concat(#withChildren)") + public List> conceptTree (String ontologyId, boolean schema, boolean narrower, boolean withChildren, Boolean obsoletes, String lang, Pageable pageable) throws IOException { + + Map> properties = new HashMap<>(); + if(!obsoletes) + properties.put("isObsolete", List.of("false")); + + List listOfTerms = allClassesOfOntology(ontologyId, obsoletes, pageable, lang); + List> rootTerms = new ArrayList>(); + int count = 0; + + if(schema) { + for (V2Entity term : listOfTerms) + if (term.any().get(hasTopConcept.getPropertyName()) != null) { + for (String iriTopConcept : (ArrayList) term.any().get(hasTopConcept.getPropertyName())) { + V2Entity topConceptTerm = findTerm(listOfTerms,iriTopConcept); + TreeNode topConcept = new TreeNode(topConceptTerm); + topConcept.setIndex(String.valueOf(++count)); + if(withChildren) { + if(narrower) + populateChildrenandRelatedByNarrower(topConceptTerm,topConcept,listOfTerms); + else + populateChildrenandRelatedByBroader(topConceptTerm,topConcept,listOfTerms); + } + rootTerms.add(topConcept); + } + } + } else for (V2Entity term : listOfTerms) { + TreeNode tree = new TreeNode(term); + + if (tree.isRoot() && term.any().get(topConceptOf.getPropertyName()) != null) { + tree.setIndex(String.valueOf(++count)); + if(withChildren) { + if(narrower) + populateChildrenandRelatedByNarrower(term,tree,listOfTerms); + else + populateChildrenandRelatedByBroader(term,tree,listOfTerms); + } + rootTerms.add(tree); + } + } + + return rootTerms; + } + + @Cacheable(value = "concepttree", key="#ontologyId.concat('-').concat(#narrower).concat('-').concat(#withChildren)") + public List> conceptTreeWithoutTop (String ontologyId, boolean isNarrower, boolean withChildren, Boolean obsoletes, String lang, Pageable pageable) throws IOException { + + List listOfTerms = allClassesOfOntology(ontologyId, obsoletes, pageable, lang); + + Set rootIRIs = new HashSet(); + List> rootTerms = new ArrayList>(); + int count = 0; + if(!isNarrower) { + for (V2Entity term : listOfTerms) { + if(term.any() != null && term.any().get(broader.getPropertyName()) != null) { + for (String iriBroader : getRelationsAsList(term,broader.getPropertyName())) { + V2Entity broaderTerm = findTerm(listOfTerms, iriBroader); + if (broaderTerm.any() != null && broaderTerm.any().get(broader.getPropertyName()) == null) { + rootIRIs.add(iriBroader); + } + + } + } + } + + for (String iri : rootIRIs) { + V2Entity topConceptTerm = findTerm(listOfTerms, iri); + TreeNode topConcept = new TreeNode(topConceptTerm); + topConcept.setIndex(String.valueOf(++count)); + if(withChildren) + populateChildrenandRelatedByBroader(topConceptTerm,topConcept,listOfTerms); + rootTerms.add(topConcept); + } + + } else { + for (V2Entity term : listOfTerms) { + if (term.any() != null && term.any().get(narrower) != null) { + boolean root = true; + for (V2Entity V2Entity : listOfTerms) { + if (V2Entity.any() != null && V2Entity.any().get(narrower) != null) { + for (String iriNarrower : getRelationsAsList(V2Entity,narrower.getPropertyName())) { + if (term.any().get("iri").equals(iriNarrower)) + root = false; + } + } + } + + if (root) { + TreeNode topConcept = new TreeNode(term); + topConcept.setIndex(String.valueOf(++count)); + if (withChildren) + populateChildrenandRelatedByNarrower(term, topConcept, listOfTerms); + rootTerms.add(topConcept); + } + } + } + } + + return rootTerms; + } + + @Cacheable(value = "concepttree", key="#ontologyId.concat('-').concat('s').concat('-').concat(#iri).concat('-').concat(#narrower).concat('-').concat(#index)") + public TreeNode conceptSubTree(String ontologyId, String iri, boolean narrower, String index, Boolean obsoletes, String lang, Pageable pageable) throws IOException { + List listOfTerms = allClassesOfOntology(ontologyId, obsoletes, pageable, lang); + V2Entity topConceptTerm = findTerm(listOfTerms,iri); + TreeNode topConcept = new TreeNode(topConceptTerm); + topConcept.setIndex(index); + if(narrower) + populateChildrenandRelatedByNarrower(topConceptTerm,topConcept,listOfTerms); + else + populateChildrenandRelatedByBroader(topConceptTerm,topConcept,listOfTerms); + + return topConcept; + } + + public V2Entity findTerm(List wholeList, String iri) { + for (V2Entity term : wholeList) + if(term.any().get("iri").equals(iri)) + return term; + return new V2Entity(new JsonObject()); + } + + public List findRelated(String ontologyId, String iri, String relationType, String lang) { + List related = new ArrayList(); + V2Entity term = this.findByOntologyAndIri(ontologyId, iri, lang); + if (term != null) + if (term.any().get(relationType) != null) + for (String iriBroader : getRelationsAsList(term,relationType)) + related.add(this.findByOntologyAndIri(ontologyId, iriBroader, lang)); + return related; + } + + public ListfindRelatedIndirectly(String ontologyId, String iri, String relationType, Boolean obsoletes, String lang, Pageable pageable) throws IOException { + List related = new ArrayList(); + + V2Entity V2Entity = this.findByOntologyAndIri(ontologyId, iri, lang); + if(V2Entity == null) + return related; + if(V2Entity.any().get("iri") == null) + return related; + + List listOfTerms = allClassesOfOntology(ontologyId, obsoletes, pageable, lang); + + for (V2Entity term : listOfTerms) { + if (term != null) + if (term.any().get(relationType) != null) + for (String iriRelated : getRelationsAsList(term,relationType)) + if(iriRelated.equals(iri)) + related.add(term); + } + + return related; + } + + public void populateChildrenandRelatedByNarrower(V2Entity term, TreeNode tree, List listOfTerms ) { + + if (term.any() != null) + for (String iriRelated : getRelationsAsList(term,related.getPropertyName())) { + TreeNode related = new TreeNode(findTerm(listOfTerms, iriRelated)); + related.setIndex(tree.getIndex() + ".related"); + tree.addRelated(related); + } + int count = 0; + if (term.any() != null) + for (String iriChild : getRelationsAsList(term,narrower.getPropertyName())) { + V2Entity childTerm = findTerm(listOfTerms, iriChild); + TreeNode child = new TreeNode(childTerm); + child.setIndex(tree.getIndex() + "." + ++count); + populateChildrenandRelatedByNarrower(childTerm, child, listOfTerms); + tree.addChild(child); + } + } + + public void populateChildrenandRelatedByBroader(V2Entity term, TreeNode tree, List listOfTerms) { + if (term.any() != null) + for (String iriRelated : getRelationsAsList(term,related.getPropertyName())) { + TreeNode related = new TreeNode(findTerm(listOfTerms, iriRelated)); + related.setIndex(tree.getIndex() + ".related"); + tree.addRelated(related); + } + int count = 0; + for ( V2Entity V2Entity : listOfTerms) { + if (V2Entity.any() != null) + for (String iriBroader : getRelationsAsList(V2Entity,broader.getPropertyName())) + if(term.any().get("iri") != null) + if (term.any().get("iri").equals(iriBroader)) { + TreeNode child = new TreeNode(V2Entity); + child.setIndex(tree.getIndex()+"."+ ++count); + populateChildrenandRelatedByBroader(V2Entity,child,listOfTerms); + tree.addChild(child); + } + } + } +} diff --git a/dataload/configs/skos_ontologies.json b/dataload/configs/skos_ontologies.json new file mode 100644 index 000000000..15f626247 --- /dev/null +++ b/dataload/configs/skos_ontologies.json @@ -0,0 +1,148 @@ +{ + "ontologies": [ + { + "ontology_purl": "https://raw.githubusercontent.com/physh-org/PhySH/master/physh.ttl", + "description": "PhySH (Physics Subject Headings) is a physics classification scheme developed by APS to organize journal, meeting, and other content by topic.", + "homepage": "https://physh.org/", + "id": "PhySH", + "license": { + "label": "CC-0 1.0", + "url": "https://creativecommons.org/publicdomain/zero/1.0/" + }, + "title": "PhySH - Physics Subject Headings", + "tracker": "https://github.com/physh-org/PhySH/issues", + "definition_property": [ + "http://www.w3.org/2004/02/skos/core#definition", + "http://purl.org/dc/terms/description" + ], + "creator": [ + "American Physical Society (https://www.aps.org/)" + ], + "preferredPrefix": "physh", + "hierarchical_property": [ + "http://www.w3.org/2004/02/skos/core#broader", + "https://physh.org/rdf/2018/01/01/core#inDiscipline", + "https://physh.org/rdf/2018/01/01/core#inFacet" + ], + "label_property": "https://physh.org/rdf/2018/01/01/core#prefLabel", + "synonym_property": [ + "http://www.w3.org/2004/02/skos/core#altLabel" + ], + "base_uri": [ + "https://doi.org/10.29172" + ], + "repo_url": "https://github.com/physh-org/PhySH", + "isSkos": true, + "skosNarrower": false, + "skosRoot": "RELATIONSHIPS" + }, + { + "ontology_purl": "https://raw.githubusercontent.com/astrothesaurus/UAT/master/UAT.rdf", + "title": "Unified Astronomy Thesaurus (UAT)", + "id": "uat", + "preferredPrefix": "uat", + "license": { + "label": "Creative Commons Attribution-ShareAlike 3.0 Unported License", + "url": "https://github.com/astrothesaurus/UAT/blob/master/LICENSE.md" + }, + "mailing_list": "sio-ontology@googlegroups.com", + "description": "The Unified Astronomy Thesaurus (UAT) is an open, interoperable and community-supported thesaurus which unifies existing, divergent, and isolated controlled vocabularies in astronomy and astrophysics into a single high-quality, freely-available open thesaurus formalizing astronomical concepts and their inter-relationships. The UAT builds upon the IAU Thesaurus with major contributions from the Astronomy portions of the thesauri developed by the Institute of Physics Publishing and the American Institute of Physics. The Unified Astronomy Thesaurus will be further enhanced and updated through a collaborative effort involving broad community participation.", + "homepage": "http://astrothesaurus.org", + "creator": [ + "Frey Katie" + ], + "is_foundary": false, + "tracker": "https://github.com/astrothesaurus/UAT/issues", + "label_property": "http://www.w3.org/2004/02/skos/core#prefLabel", + "base_uri": [ + "http://astrothesaurus.org/uat" + ], + "synonym_property": [ + "http://www.w3.org/2004/02/skos/core#altLabel" + ], + "definition_property": [ + "http://www.w3.org/2004/02/skos/core#definition" + ], + "repo_url": "https://github.com/astrothesaurus/UAT", + "isSkos": true, + "skosNarrower": false, + "skosRoot": "TOPCONCEPTOF_PROPERTY" + }, +{ + "ontology_purl": "https://raw.githubusercontent.com/dini-ag-kim/hochschulfaechersystematik/master/hochschulfaechersystematik.ttl", + "id": "hsfs", + "title": "Hochschulfächersystematik", + "description": "Diese Hochschulfächersystematik basiert auf der Destatis-Systematik der Fächergruppen, Studienbereiche und Studienfächer (http://bartoc.org/node/18919) und wird gepflegt von der OER-Metadatengruppe der DINI-AG KIM. Die Systematik ist Bestandteil der Spezifikationen LOM for Higher Education OER Repositories und LRMI-Profil (Entwurf).", + "repo_url": "https://github.com/dini-ag-kim/hochschulfaechersystematik", + "preferredPrefix": "hsfs", + "allow_download": true, + "homepage": "https://bartoc.org/en/node/18919", + "base_uri": [ + "https://w3id.org/kim/hochschulfaechersystematik/" + ], + "label_property": "http://www.w3.org/2004/02/skos/core#prefLabel", + "isSkos": true, + "skosNarrower": false, + "skosRoot": "TOPCONCEPTOF_PROPERTY", + "creator": [ + "Michael Menzel", + "Adrian Pohl" + ], + "license": { + "label": "freely available", + "url": "http://bartoc.org/en/Access/Free" + } +}, +{ + "ontology_purl": "https://purl.org/fidbaudigital/subjects", + "title": "FID BAUdigital Subject Headings", + "id": "bdsubj", + "preferredPrefix": "bdsubj", + "license": { + "label": "CC-BY 4.0", + "url": "https://creativecommons.org/licenses/by/4.0/" + }, + "description": "This subject heading system has beeen developed for use in FID BAUdigital and its future web services. It covers scientific fields of Civil Engineering, Architecture and Urban Studies with a special section on digitization. This subject classification has been mapped to several other classification systems. The latest version of the subject classification including these mappings is available at https://gitlab.com/fid-bau/terminologie/fid-baudigital-faecherklassifikation/-/raw/main/Subject_Headings_all_mappings.owl.", + "homepage": "https://gitlab.com/fid-bau/terminologie/fid-baudigital-faecherklassifikation", + "tracker": "https://gitlab.com/fid-bau/terminologie/fid-baudigital-faecherklassifikation/-/issues", + "definition_property": [ + "http://www.w3.org/2004/02/skos/core#definition" + ], + "label_property": "http://www.w3.org/2004/02/skos/core#prefLabel", + "creator": [ + "Fraunhofer-Informationszentrum Raum und Bau IRB" + ], + "base_uri": [ + "https://purl.org/fidbaudigital/subjects" + ], + "isSkos": true, + "skosNarrower": false, + "skosRoot": "TOPCONCEPTOF_PROPERTY", + "repo_url": "https://gitlab.com/fid-bau/terminologie/fid-baudigital-faecherklassifikation" +}, +{ + "ontology_purl": "https://vocabs-downloads.acdh.oeaw.ac.at/vocabs-main/GeneralConcepts/OeFOS/oefos_disciplines.ttl", + "id": "oefos", + "license": { + "label": "Creative Commons Attribution 4.0 International License.", + "url": "https://creativecommons.org/licenses/by/4.0/" + }, + "title": "The Austrian Fields of Science and Technology Classification (ÖFOS 2012)", + "description": "The Austrian Fields of Science and Technology Classification (ÖFOS 2012) is the Austrian version of the revised international Fields of Science and Technology Classification of the OECD (FOS) published in the Frascati Manual 2015 as Fields of Research and Development (FORD). These fields are adjusted to national needs, whose application for international comparisons is binding, particularly within the European Statistical System. The six major Fields of Science: Natural Sciences; Technical Sciences; Human Medicine, Health Sciences; Agricultural Sciences, Veterinary Medicine; Social Sciences and Humanities remained unchanged in comparison to ÖFOS 2002. In order to ensure international comparability, the previous 2-digit levels from 2002, which are no longer applicable, were replaced by new 3-digit levels (groups) according to the international FOS respectively FORD. These 3-digit levels were provided with further sub-groups (4-digits) taking into account the comments of the international classification. It is therefore feasible that the new Austrian Fields of Science adapt to national peculiarities of the Austrian research activities. The research area with the corresponding 6-digits in alphabetical order serves as a description of the fields of activities and research projects and/or for the coverage of the main scientific activities of a statistical unit in the research and development surveys. (Current revision status: August 2017)", + "homepage": "https://vocabs.dariah.eu/oefos/en/", + "base_uri": [ + "https://vocabs.acdh.oeaw.ac.at/oefosdisciplines/" + ], + "allow_download": true, + "preferredPrefix": "oefos", + "isSkos": true, + "skosNarrower": false, + "skosRoot": "SCHEMA", + "label_property": "http://www.w3.org/2004/02/skos/core#prefLabel", + "creator": [ + "Christoph Hoffmann" + ] +} + ] +} + diff --git a/dataload/rdf2json/src/main/java/uk/ac/ebi/rdf2json/OntologyGraph.java b/dataload/rdf2json/src/main/java/uk/ac/ebi/rdf2json/OntologyGraph.java index 275e6a511..c0b69a493 100644 --- a/dataload/rdf2json/src/main/java/uk/ac/ebi/rdf2json/OntologyGraph.java +++ b/dataload/rdf2json/src/main/java/uk/ac/ebi/rdf2json/OntologyGraph.java @@ -641,6 +641,7 @@ public void handleType(OntologyNode subjNode, Node type) { case "http://www.w3.org/2002/07/owl#Class": case "http://www.w3.org/2000/01/rdf-schema#Class": case "http://www.w3.org/2004/02/skos/core#Concept": + case "http://www.w3.org/2004/02/skos/core#ConceptScheme": subjNode.types.add(OntologyNode.NodeType.CLASS); if(subjNode.uri != null) { ++ numberOfClasses;