diff --git a/src/main/java/org/snomed/snowstorm/core/data/domain/Concept.java b/src/main/java/org/snomed/snowstorm/core/data/domain/Concept.java index c6e2be274..2dda814ab 100644 --- a/src/main/java/org/snomed/snowstorm/core/data/domain/Concept.java +++ b/src/main/java/org/snomed/snowstorm/core/data/domain/Concept.java @@ -1,6 +1,7 @@ package org.snomed.snowstorm.core.data.domain; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonView; import com.google.common.collect.Sets; @@ -24,7 +25,7 @@ @Document(indexName = "concept") @JsonPropertyOrder({"conceptId", "descendantCount", "fsn", "pt", "active", "effectiveTime", "released", "releasedEffectiveTime", "inactivationIndicator", "associationTargets", - "moduleId", "definitionStatus", "definitionStatusId", "descriptions", "classAxioms", "gciAxioms", "relationships", "validationResults"}) + "moduleId", "definitionStatus", "definitionStatusId", "descriptions", "classAxioms", "gciAxioms", "relationships", "alternateIdentifiers", "validationResults"}) public class Concept extends SnomedComponent implements ConceptView, SnomedComponentWithInactivationIndicator, SnomedComponentWithAssociations { public interface Fields extends SnomedComponent.Fields { @@ -81,6 +82,9 @@ public interface Fields extends SnomedComponent.Fields { @Transient private Long descendantCount; + @Transient + private List identifiers; + @Transient private List validationResults; @@ -93,6 +97,7 @@ public Concept() { classAxioms = new HashSet<>(); generalConceptInclusionAxioms = new HashSet<>(); inactivationIndicatorMembers = new ArrayList<>(); + identifiers = new ArrayList<>(); validationResults = new ArrayList<>(); } @@ -230,6 +235,11 @@ public Concept addDescription(Description description) { return this; } + public Concept addIdentifier(Identifier identifier) { + identifiers.add(identifier); + return this; + } + public Concept addRelationship(String typeId, String destinationId) { return addRelationship(new Relationship(typeId, destinationId)); } @@ -427,6 +437,17 @@ public void setValidationResults(final List validationResults) { this.validationResults = validationResults; } + @JsonProperty("alternateIdentifiers") + @JsonView(value = View.Component.class) + @Override + public List getIdentifiers() { + return identifiers; + } + + public void setIdentifiers(List identifiers) { + this.identifiers = identifiers; + } + public void clone(Concept concept) { setConceptId(concept.getConceptId()); setEffectiveTimeI(concept.getEffectiveTimeI()); diff --git a/src/main/java/org/snomed/snowstorm/core/data/domain/ConceptView.java b/src/main/java/org/snomed/snowstorm/core/data/domain/ConceptView.java index 820bec32f..5f6a23e27 100644 --- a/src/main/java/org/snomed/snowstorm/core/data/domain/ConceptView.java +++ b/src/main/java/org/snomed/snowstorm/core/data/domain/ConceptView.java @@ -33,5 +33,7 @@ public interface ConceptView { Set getGciAxioms(); + List getIdentifiers(); + List getValidationResults(); } diff --git a/src/main/java/org/snomed/snowstorm/core/data/domain/Identifier.java b/src/main/java/org/snomed/snowstorm/core/data/domain/Identifier.java index b5c29e976..4f48f47eb 100644 --- a/src/main/java/org/snomed/snowstorm/core/data/domain/Identifier.java +++ b/src/main/java/org/snomed/snowstorm/core/data/domain/Identifier.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonView; import org.snomed.snowstorm.rest.View; -import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.Transient; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; @@ -14,7 +14,7 @@ import java.util.Objects; @Document(indexName = "identifier") -@JsonPropertyOrder({"alternateIdentifier", "effectiveTime", "active", "moduleId", "identifierSchemeId", "referencedComponentId", "released", "releasedEffectiveTime"}) +@JsonPropertyOrder({"alternateIdentifier", "effectiveTime", "active", "moduleId", "identifierSchemeId", "identifierScheme", "referencedComponentId", "released", "releasedEffectiveTime"}) public class Identifier extends SnomedComponent { public interface Fields extends SnomedComponent.Fields { @@ -44,6 +44,9 @@ public interface Fields extends SnomedComponent.Fields { @Size(min = 5, max = 18) private String referencedComponentId; + @Transient + private ConceptMini identifierScheme; + public Identifier() { active = true; setModuleId(Concepts.CORE_MODULE); @@ -100,6 +103,15 @@ public String getIdentifierSchemeId() { return identifierSchemeId; } + public void setIdentifierScheme(ConceptMini identifierScheme) { + this.identifierScheme = identifierScheme; + } + + @JsonView(value = View.Component.class) + public ConceptMini getIdentifierScheme() { + return identifierScheme; + } + @Override protected Object[] getReleaseHashObjects() { return new Object[]{alternateIdentifier, active, getModuleId(), identifierSchemeId, referencedComponentId}; diff --git a/src/main/java/org/snomed/snowstorm/core/data/services/ConceptService.java b/src/main/java/org/snomed/snowstorm/core/data/services/ConceptService.java index 559eb6848..72ce79b8f 100644 --- a/src/main/java/org/snomed/snowstorm/core/data/services/ConceptService.java +++ b/src/main/java/org/snomed/snowstorm/core/data/services/ConceptService.java @@ -92,6 +92,9 @@ public class ConceptService extends ComponentService { @Autowired private DescriptionService descriptionService; + @Autowired + private IdentifierComponentService identifierComponentService; + @Autowired @Lazy private ReferenceSetMemberService referenceSetMemberService; @@ -159,7 +162,7 @@ public Collection find(BranchCriteria branchCriteria, String path, Coll if (isEmpty(conceptIds)) { return Collections.emptySet(); } - return doFind(conceptIds, languageDialects, branchCriteria, PageRequest.of(0, conceptIds.size()), true, true, path).getContent(); + return doFind(conceptIds, languageDialects, branchCriteria, PageRequest.of(0, conceptIds.size()), true, true, true, path).getContent(); } public Page find(List conceptIds, List languageDialects, String path, PageRequest pageRequest) { @@ -271,7 +274,7 @@ public Page findAll(String path, List languageDialects private Page doFind(Collection conceptIds, List languageDialects, BranchTimepoint branchTimepoint, PageRequest pageRequest) { final BranchCriteria branchCriteria = getBranchCriteria(branchTimepoint); - return doFind(conceptIds, languageDialects, branchCriteria, pageRequest, true, true, branchTimepoint.getBranchPath()); + return doFind(conceptIds, languageDialects, branchCriteria, pageRequest, true, true, true, branchTimepoint.getBranchPath()); } protected BranchCriteria getBranchCriteria(String branchPath) { @@ -323,7 +326,7 @@ private ResultMapPage findConceptMinis(BranchCriteria branc if (conceptIds != null && conceptIds.isEmpty()) { return new ResultMapPage<>(new HashMap<>(), 0); } - Page concepts = doFind(conceptIds, languageDialects, branchCriteria, pageRequest, false, false, null); + Page concepts = doFind(conceptIds, languageDialects, branchCriteria, pageRequest, false, false, false, null); Map conceptMap = new HashMap<>(); for (Concept concept : concepts) { String id = concept.getId(); @@ -343,7 +346,7 @@ private ResultMapPage findConceptMinis(BranchCriteria branc private void populateConceptMinis(BranchCriteria branchCriteria, Map minisToPopulate, List languageDialects) { if (!minisToPopulate.isEmpty()) { Set conceptIds = minisToPopulate.keySet(); - Page concepts = doFind(conceptIds, languageDialects, branchCriteria, PageRequest.of(0, conceptIds.size()), false, false, null); + Page concepts = doFind(conceptIds, languageDialects, branchCriteria, PageRequest.of(0, conceptIds.size()), false, false, false, null); concepts.getContent().forEach(c -> { ConceptMini conceptMini = minisToPopulate.get(c.getConceptId()); conceptMini.setDefinitionStatus(c.getDefinitionStatus()); @@ -359,6 +362,7 @@ private Page doFind( PageRequest pageRequest, boolean includeRelationships, boolean includeDescriptionInactivationInfo, + boolean includeIdentifiers, String branchPath) { final TimerUtil timer = new TimerUtil("Find concept", Level.DEBUG); @@ -422,6 +426,10 @@ private Page doFind( timer.checkpoint("get axioms " + getFetchCount(conceptIdMap.size())); } + if (includeIdentifiers) { + identifierComponentService.joinIdentifiers(branchCriteria, conceptIdMap, conceptMiniMap, languageDialects, timer); + } + // Fetch ConceptMini definition statuses for (List conceptIds : Iterables.partition(conceptMiniMap.keySet(), CLAUSE_LIMIT)) { queryBuilder.withQuery(boolQuery() @@ -744,7 +752,7 @@ Map getExistingConceptsForSave(Collection concepts, Co if (!conceptIds.isEmpty()) { for (List conceptIdPartition : Iterables.partition(conceptIds, 500)) { final BranchCriteria branchCriteria = versionControlHelper.getBranchCriteriaIncludingOpenCommit(commit); - final List existingConcepts = doFind(conceptIdPartition, DEFAULT_LANGUAGE_DIALECTS, branchCriteria, PageRequest.of(0, conceptIds.size()), true, true, null).getContent(); + final List existingConcepts = doFind(conceptIdPartition, DEFAULT_LANGUAGE_DIALECTS, branchCriteria, PageRequest.of(0, conceptIds.size()), true, true, true, null).getContent(); for (Concept existingConcept : existingConcepts) { existingConceptsMap.put(existingConcept.getConceptId(), existingConcept); } diff --git a/src/main/java/org/snomed/snowstorm/core/data/services/IdentifierComponentService.java b/src/main/java/org/snomed/snowstorm/core/data/services/IdentifierComponentService.java index d94191d7e..76cb40fed 100644 --- a/src/main/java/org/snomed/snowstorm/core/data/services/IdentifierComponentService.java +++ b/src/main/java/org/snomed/snowstorm/core/data/services/IdentifierComponentService.java @@ -1,6 +1,7 @@ package org.snomed.snowstorm.core.data.services; import com.google.common.base.Strings; +import com.google.common.collect.Iterables; import io.kaicode.elasticvc.api.BranchCriteria; import io.kaicode.elasticvc.api.BranchService; import io.kaicode.elasticvc.api.ComponentService; @@ -12,10 +13,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.snomed.snowstorm.config.Config; -import org.snomed.snowstorm.core.data.domain.Concepts; -import org.snomed.snowstorm.core.data.domain.Identifier; +import org.snomed.snowstorm.core.data.domain.*; import org.snomed.snowstorm.core.data.repositories.IdentifierRepository; import org.snomed.snowstorm.core.data.services.pojo.IdentifierSearchRequest; +import org.snomed.snowstorm.core.pojo.LanguageDialect; +import org.snomed.snowstorm.core.util.TimerUtil; import org.snomed.snowstorm.ecl.ECLQueryService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -24,6 +26,7 @@ import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.SearchHit; import org.springframework.data.elasticsearch.core.SearchHits; +import org.springframework.data.elasticsearch.core.SearchHitsIterator; import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.stereotype.Service; @@ -150,4 +153,45 @@ private QueryBuilder buildIdentifierQuery(IdentifierSearchRequest searchRequest, return query; } + void joinIdentifiers(BranchCriteria branchCriteria, Map conceptIdMap, Map conceptMiniMap, List languageDialects, TimerUtil timer) { + final NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); + + final Set allConceptIds = new HashSet<>(); + if (conceptIdMap != null) { + allConceptIds.addAll(conceptIdMap.keySet()); + } + if (allConceptIds.isEmpty()) { + return; + } + + // Fetch Identifier + for (List conceptIds : Iterables.partition(allConceptIds, CLAUSE_LIMIT)) { + queryBuilder.withQuery(boolQuery() + .must(branchCriteria.getEntityBranchCriteria(Identifier.class)) + .must(termQuery(Identifier.Fields.ACTIVE, true)) + .must(termsQuery(Identifier.Fields.REFERENCED_COMPONENT_ID, conceptIds))) + .withPageable(LARGE_PAGE); + try (final SearchHitsIterator identifiers = elasticsearchTemplate.searchForStream(queryBuilder.build(), Identifier.class)) { + identifiers.forEachRemaining(hit -> { + Identifier identifier = hit.getContent(); + identifier.setIdentifierScheme(getConceptMini(conceptMiniMap, identifier.getIdentifierSchemeId(), languageDialects)); + + // Join Identifiers to concepts for loading whole concepts use case. + final String referencedComponentId = identifier.getReferencedComponentId(); + if (conceptIdMap != null) { + final Concept concept = conceptIdMap.get(referencedComponentId); + if (concept != null) { + concept.addIdentifier(identifier); + } + } + }); + } + } + if (timer != null) timer.checkpoint("get Identifier " + getFetchCount(allConceptIds.size())); + } + + private static ConceptMini getConceptMini(Map conceptMiniMap, String id, List languageDialects) { + if (id == null) return new ConceptMini((String)null, languageDialects); + return conceptMiniMap.computeIfAbsent(id, i -> new ConceptMini(id, languageDialects)); + } } diff --git a/src/test/java/org/snomed/snowstorm/rest/ConceptControllerTest.java b/src/test/java/org/snomed/snowstorm/rest/ConceptControllerTest.java index d3807fb52..a2ee11d65 100644 --- a/src/test/java/org/snomed/snowstorm/rest/ConceptControllerTest.java +++ b/src/test/java/org/snomed/snowstorm/rest/ConceptControllerTest.java @@ -196,7 +196,7 @@ void testConceptEndpointFields() throws IOException, ServiceException { checkFields(responseBody); LinkedHashMap properties = objectMapper.readValue(responseBody, LinkedHashMap.class); assertEquals("[conceptId, fsn, pt, active, effectiveTime, released, releasedEffectiveTime, moduleId, definitionStatus, " + - "descriptions, classAxioms, gciAxioms, relationships, validationResults]", properties.keySet().toString()); + "descriptions, classAxioms, gciAxioms, relationships, alternateIdentifiers, validationResults]", properties.keySet().toString()); Object fsn = properties.get("fsn"); assertEquals("LinkedHashMap", fsn.getClass().getSimpleName()); assertEquals("{term=Wallace \"69\" side-to-end anastomosis - action (qualifier value), lang=en}", fsn.toString());