From badcbfc0eea1571ca4feacbf8943d368c6b0cb9e Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Thu, 26 May 2016 07:53:36 -0400 Subject: [PATCH] More work on SNOMED CT terminology loader --- .../jpa/config/dstu3/BaseDstu3Config.java | 6 +- .../ca/uhn/fhir/jpa/dao/SearchBuilder.java | 2 +- .../dao/data/ITermCodeSystemVersionDao.java | 5 + .../fhir/jpa/dao/data/ITermConceptDao.java | 10 + .../data/ITermConceptParentChildLinkDao.java | 9 +- .../dstu3/FhirResourceDaoCodeSystemDstu3.java | 127 +++--- .../jpa/entity/TermCodeSystemVersion.java | 6 +- .../fhir/jpa/provider/BaseJpaProvider.java | 1 + ...aseJpaResourceProviderCodeSystemDstu3.java | 1 - .../TerminologyUploaderProviderDstu3.java | 55 +++ .../fhir/jpa/term/BaseHapiTerminologySvc.java | 50 ++- .../jpa/term/HapiTerminologySvcDstu3.java | 102 ++++- .../jpa/term/IHapiTerminologyLoaderSvc.java | 11 + .../jpa/term/IHapiTerminologySvcDstu3.java | 7 + .../fhir/jpa/term/TerminologyLoaderSvc.java | 406 +++++++++--------- .../JpaValidationSupportChainDstu3.java | 6 + .../FhirResourceDaoTerminologyDstu3Test.java | 89 ++++ .../TerminologyLoaderSvcIntegrationTest.java | 1 - .../fhir/jpa/term/TerminologySvcImplTest.java | 70 ++- 19 files changed, 630 insertions(+), 334 deletions(-) create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/TerminologyUploaderProviderDstu3.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologyLoaderSvc.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvcDstu3.java diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java index c27b1683696..0f68518adfc 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java @@ -3,8 +3,6 @@ package ca.uhn.fhir.jpa.config.dstu3; import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator; import org.hl7.fhir.dstu3.hapi.validation.HapiWorkerContext; import org.hl7.fhir.dstu3.hapi.validation.IValidationSupport; -import org.hl7.fhir.dstu3.terminologies.ValueSetExpander; -import org.hl7.fhir.dstu3.utils.IWorkerContext; import org.hl7.fhir.dstu3.validation.IResourceValidator.BestPracticeWarningLevel; /* @@ -41,7 +39,7 @@ import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.dao.dstu3.SearchParamExtractorDstu3; import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu3; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; +import ca.uhn.fhir.jpa.term.IHapiTerminologySvcDstu3; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3; import ca.uhn.fhir.validation.IValidatorModule; @@ -50,7 +48,7 @@ import ca.uhn.fhir.validation.IValidatorModule; public class BaseDstu3Config extends BaseConfig { @Bean(autowire = Autowire.BY_TYPE) - public IHapiTerminologySvc terminologyService() { + public IHapiTerminologySvcDstu3 terminologyService() { return new HapiTerminologySvcDstu3(); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java index eee30fba545..f6d10836cd7 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java @@ -67,7 +67,6 @@ import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; -import com.google.common.collect.HashBasedTable; import com.google.common.collect.Lists; import com.google.common.collect.Sets; @@ -1384,6 +1383,7 @@ public class SearchBuilder { } else if (modifier == TokenParamModifier.BELOW) { codes = myTerminologySvc.findCodesBelow(system, code); } + if (codes != null) { if (codes.isEmpty()) { return null; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermCodeSystemVersionDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermCodeSystemVersionDao.java index 0c30371a4e2..233fd34840f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermCodeSystemVersionDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermCodeSystemVersionDao.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.jpa.dao.data; +import java.util.List; + /* * #%L * HAPI FHIR JPA Server @@ -28,6 +30,9 @@ import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; public interface ITermCodeSystemVersionDao extends JpaRepository { + @Query("SELECT cs FROM TermCodeSystemVersion cs WHERE cs.myResource.myId = :resource_id") + List findByCodeSystemResource(@Param("resource_id") Long theCodeSystemResourcePid); + @Query("SELECT cs FROM TermCodeSystemVersion cs WHERE cs.myResource.myId = :resource_id AND cs.myResourceVersionId = :version_id") TermCodeSystemVersion findByCodeSystemResourceAndVersion(@Param("resource_id") Long theCodeSystemResourcePid, @Param("version_id") Long theCodeSystemVersionPid); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptDao.java index 41ec4c1f31a..1c70f123ced 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptDao.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.jpa.dao.data; +import java.util.List; + /* * #%L * HAPI FHIR JPA Server @@ -21,6 +23,7 @@ package ca.uhn.fhir.jpa.dao.data; */ import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -32,4 +35,11 @@ public interface ITermConceptDao extends JpaRepository { @Query("SELECT c FROM TermConcept c WHERE c.myCodeSystem = :code_system AND c.myCode = :code") TermConcept findByCodeSystemAndCode(@Param("code_system") TermCodeSystemVersion theCodeSystem, @Param("code") String theCode); + @Query("SELECT c FROM TermConcept c WHERE c.myCodeSystem = :code_system") + List findByCodeSystemVersion(@Param("code_system") TermCodeSystemVersion theCodeSystem); + + @Query("DELETE FROM TermConcept t WHERE t.myCodeSystem.myId = :cs_pid") + @Modifying + void deleteByCodeSystemVersion(@Param("cs_pid") Long thePid); + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptParentChildLinkDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptParentChildLinkDao.java index 6216eb19f4c..130e5329ad1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptParentChildLinkDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermConceptParentChildLinkDao.java @@ -21,9 +21,16 @@ package ca.uhn.fhir.jpa.dao.data; */ import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; public interface ITermConceptParentChildLinkDao extends JpaRepository { - // nothing + + @Query("DELETE FROM TermConceptParentChildLink t WHERE t.myCodeSystem.myId = :cs_pid") + @Modifying + void deleteByCodeSystemVersion(@Param("cs_pid") Long thePid); + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java index d37a436ff4c..53d8144b41d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java @@ -27,7 +27,7 @@ import java.util.Date; import java.util.List; import java.util.Set; -import org.hl7.fhir.dstu3.hapi.validation.HapiWorkerContext; +import org.hl7.fhir.dstu3.hapi.validation.IValidationSupport.CodeValidationResult; import org.hl7.fhir.dstu3.hapi.validation.ValidationSupportChain; import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode; @@ -35,10 +35,7 @@ import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent; import org.hl7.fhir.dstu3.model.CodeableConcept; import org.hl7.fhir.dstu3.model.Coding; import org.hl7.fhir.dstu3.model.IdType; -import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent; -import org.hl7.fhir.dstu3.terminologies.ValueSetExpander; -import org.hl7.fhir.dstu3.terminologies.ValueSetExpander.ValueSetExpansionOutcome; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; @@ -53,7 +50,6 @@ import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; import ca.uhn.fhir.jpa.util.LogicUtil; import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.param.TokenParam; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; public class FhirResourceDaoCodeSystemDstu3 extends FhirResourceDaoDstu3 implements IFhirResourceDaoCodeSystem { @@ -77,29 +73,29 @@ public class FhirResourceDaoCodeSystemDstu3 extends FhirResourceDaoDstu3 theContains, String theSystem, String theCode) { - for (ValueSetExpansionContainsComponent nextCode : theContains) { - - String system = nextCode.getSystem(); - String code = nextCode.getCode(); - if (theSystem.equals(system) && theCode.equals(code)) { - LookupCodeResult retVal = new LookupCodeResult(); - retVal.setSearchedForCode(code); - retVal.setSearchedForSystem(system); - retVal.setFound(true); - if (nextCode.getAbstractElement().getValue() != null) { - retVal.setCodeIsAbstract(nextCode.getAbstractElement().booleanValue()); - } - retVal.setCodeDisplay(nextCode.getDisplay()); - retVal.setCodeSystemVersion(nextCode.getVersion()); - retVal.setCodeSystemDisplayName("Unknown"); // TODO: implement - return retVal; - } - - } - - return null; - } +// private LookupCodeResult lookup(List theContains, String theSystem, String theCode) { +// for (ValueSetExpansionContainsComponent nextCode : theContains) { +// +// String system = nextCode.getSystem(); +// String code = nextCode.getCode(); +// if (theSystem.equals(system) && theCode.equals(code)) { +// LookupCodeResult retVal = new LookupCodeResult(); +// retVal.setSearchedForCode(code); +// retVal.setSearchedForSystem(system); +// retVal.setFound(true); +// if (nextCode.getAbstractElement().getValue() != null) { +// retVal.setCodeIsAbstract(nextCode.getAbstractElement().booleanValue()); +// } +// retVal.setCodeDisplay(nextCode.getDisplay()); +// retVal.setCodeSystemVersion(nextCode.getVersion()); +// retVal.setCodeSystemDisplayName("Unknown"); // TODO: implement +// return retVal; +// } +// +// } +// +// return null; +// } @Override public LookupCodeResult lookupCode(IPrimitiveType theCode, IPrimitiveType theSystem, Coding theCoding, RequestDetails theRequestDetails) { @@ -138,47 +134,40 @@ public class FhirResourceDaoCodeSystemDstu3 extends FhirResourceDaoDstu3 contains = expansion.getValueset().getExpansion().getContains(); - LookupCodeResult result = lookup(contains, system, code); - if (result != null) { - return result; - } - } - - } else { - - /* - * If it's not a built-in code system, use ones from the database - */ - - List valueSetIds = findCodeSystemIdsContainingSystemAndCode(code, system); - for (IIdType nextId : valueSetIds) { - CodeSystem expansion = read(nextId, theRequestDetails); - for (ConceptDefinitionComponent next : expansion.getConcept()) { - if (code.equals(next.getCode())) { - LookupCodeResult retVal = new LookupCodeResult(); - retVal.setSearchedForCode(code); - retVal.setSearchedForSystem(system); - retVal.setFound(true); - retVal.setCodeDisplay(next.getDisplay()); - retVal.setCodeSystemDisplayName("Unknown"); // TODO: implement - return retVal; - } + + CodeValidationResult result = myValidationSupport.validateCode(getContext(), system, code, null); + if (result != null) { + if (result.isOk()) { + LookupCodeResult retVal = new LookupCodeResult(); + retVal.setFound(true); + retVal.setSearchedForCode(code); + retVal.setSearchedForSystem(system); + retVal.setCodeDisplay(result.getDisplay()); + retVal.setCodeSystemDisplayName("Unknown"); + retVal.setCodeSystemVersion(""); + return retVal; } } + +// HapiWorkerContext ctx = new HapiWorkerContext(getContext(), myValidationSupport); +// ValueSetExpander expander = ctx.getExpander(); +// ValueSet source = new ValueSet(); +// source.getCompose().addInclude().setSystem(system).addConcept().setCode(code); +// +// ValueSetExpansionOutcome expansion; +// try { +// expansion = expander.expand(source); +// } catch (Exception e) { +// throw new InternalErrorException(e); +// } +// +// if (expansion.getValueset() != null) { +// List contains = expansion.getValueset().getExpansion().getContains(); +// LookupCodeResult result = lookup(contains, system, code); +// if (result != null) { +// return result; +// } +// } } @@ -217,8 +206,8 @@ public class FhirResourceDaoCodeSystemDstu3 extends FhirResourceDaoDstu3 findCodesAbove(String theSystem, String theCode) { - TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(theSystem); + TermCodeSystem cs = getCodeSystem(theSystem); + if (cs == null) { + return Collections.emptyList(); + } TermCodeSystemVersion csv = cs.getCurrentVersion(); Set codes = findCodesAbove(cs.getResource().getId(), csv.getResourceVersionId(), theCode); @@ -158,7 +159,10 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc { @Override public List findCodesBelow(String theSystem, String theCode) { - TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(theSystem); + TermCodeSystem cs = getCodeSystem(theSystem); + if (cs == null) { + return Collections.emptyList(); + } TermCodeSystemVersion csv = cs.getCurrentVersion(); Set codes = findCodesBelow(cs.getResource().getId(), csv.getResourceVersionId(), theCode); @@ -194,7 +198,10 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc { ValidateUtil.isNotNullOrThrowInvalidRequest(theCodeSystem.getResource() != null, "No resource supplied"); ValidateUtil.isNotBlankOrThrowInvalidRequest(theSystemUri, "No system URI supplied"); - TermCodeSystem codeSystem = myCodeSystemDao.findByCodeSystemUri(theSystemUri); + // Grab the existing versions so we can delete them later + List existing = myCodeSystemVersionDao.findByCodeSystemResource(theCodeSystemResourcePid); + + TermCodeSystem codeSystem = getCodeSystem(theSystemUri); if (codeSystem == null) { codeSystem = myCodeSystemDao.findByResourcePid(theCodeSystemResourcePid); if (codeSystem == null) { @@ -233,14 +240,33 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc { for (TermConcept next : theCodeSystem.getConcepts()) { persistChildren(next, theCodeSystem, conceptsStack); } + + /* + * For now we always delete old versions.. At some point it would be + * nice to allow configuration to keep old versions + */ + + ourLog.info("Deleting old sode system versions"); + for (TermCodeSystemVersion next : existing) { + ourLog.info(" * Deleting code system version {}", next.getPid()); + myConceptParentChildLinkDao.deleteByCodeSystemVersion(next.getPid()); + myConceptDao.deleteByCodeSystemVersion(next.getPid()); + } + + ourLog.info("Done saving code system"); } @Override public boolean supportsSystem(String theSystem) { - TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(theSystem); + TermCodeSystem cs = getCodeSystem(theSystem); return cs != null; } + private TermCodeSystem getCodeSystem(String theSystem) { + TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(theSystem); + return cs; + } + private ArrayList toVersionIndependentConcepts(String theSystem, Set codes) { ArrayList retVal = new ArrayList(codes.size()); for (TermConcept next : codes) { @@ -265,5 +291,15 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc { theConceptsStack.remove(theConcept); } + public TermConcept findCode(String theCodeSystem, String theCode) { + TermCodeSystem cs = getCodeSystem(theCodeSystem); + if (cs == null || cs.getCurrentVersion() == null) { + return null; + } + TermCodeSystemVersion csv = cs.getCurrentVersion(); + + return myConceptDao.findByCodeSystemAndCode(csv, theCode); + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java index b1a98e1e53e..63175d33052 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.jpa.term; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + /* * #%L * HAPI FHIR JPA Server @@ -21,37 +23,46 @@ package ca.uhn.fhir.jpa.term; */ import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import org.hl7.fhir.dstu3.hapi.validation.IValidationSupport; import org.hl7.fhir.dstu3.model.CodeSystem; +import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode; +import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent; import org.hl7.fhir.dstu3.model.CodeableConcept; import org.hl7.fhir.dstu3.model.Coding; +import org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity; +import org.hl7.fhir.dstu3.model.StructureDefinition; import org.hl7.fhir.dstu3.model.ValueSet; -import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode; +import org.hl7.fhir.dstu3.model.ValueSet.ConceptReferenceComponent; +import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; +import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetFilterComponent; +import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent; import org.hl7.fhir.dstu3.terminologies.ValueSetExpander; import org.hl7.fhir.dstu3.terminologies.ValueSetExpander.ValueSetExpansionOutcome; -import org.hl7.fhir.dstu3.utils.IWorkerContext; +import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.springframework.beans.factory.annotation.Autowired; +import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; -import ca.uhn.fhir.jpa.entity.BaseHasResource; import ca.uhn.fhir.jpa.entity.ResourceTable; +import ca.uhn.fhir.jpa.entity.TermCodeSystem; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; +import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.util.CoverageIgnore; import ca.uhn.fhir.util.UrlUtil; -public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvc { +public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvc implements IValidationSupport, IHapiTerminologySvcDstu3 { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(HapiTerminologySvcDstu3.class); @Autowired private IFhirResourceDaoCodeSystem myCodeSystemResourceDao; - @Autowired - private IWorkerContext myWorkerContext; - @Autowired private ValueSetExpander myValueSetExpander; @@ -92,4 +103,81 @@ public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvc { } + @Override + public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { + String system = theInclude.getSystem(); + TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(system); + TermCodeSystemVersion csv = cs.getCurrentVersion(); + + ValueSetExpansionComponent retVal = new ValueSetExpansionComponent(); + + boolean haveSpecificWantedCode = false; + for (ConceptReferenceComponent next : theInclude.getConcept()) { + String nextCode = next.getCode(); + if (isNotBlank(nextCode)) { + haveSpecificWantedCode = true; + TermConcept termCode = myConceptDao.findByCodeSystemAndCode(csv, nextCode); + if (termCode != null) { + addCodeIfFilterMatches(retVal, termCode, theInclude.getFilter(), nextCode); + } + } + } + + if (!haveSpecificWantedCode) { + for(TermConcept next : myConceptDao.findByCodeSystemVersion(csv)) { + addCodeIfFilterMatches(retVal, next, theInclude.getFilter(), system); + } + } + + return retVal; + } + + private void addCodeIfFilterMatches(ValueSetExpansionComponent retVal, TermConcept termCode, List theFilters, String theSystem) { + ValueSetExpansionContainsComponent contains = retVal.addContains(); + contains.setSystem(theSystem); + contains.setCode(termCode.getCode()); + contains.setDisplay(termCode.getDisplay()); + } + + @Override + public List fetchAllStructureDefinitions(FhirContext theContext) { + return Collections.emptyList(); + } + + @CoverageIgnore + @Override + public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { + throw new UnsupportedOperationException(); + } + + @Override + public T fetchResource(FhirContext theContext, Class theClass, String theUri) { + return null; + } + + @CoverageIgnore + @Override + public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { + return null; + } + + @Override + public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { + return super.supportsSystem(theSystem); + } + + @CoverageIgnore + @Override + public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) { + TermConcept code = super.findCode(theCodeSystem, theCode); + if (code != null) { + ConceptDefinitionComponent def = new ConceptDefinitionComponent(); + def.setCode(code.getCode()); + def.setDisplay(code.getDisplay()); + return new CodeValidationResult(def); + } + + return new CodeValidationResult(IssueSeverity.ERROR, "Unkonwn code {" + theCodeSystem +"}" + theCode); + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologyLoaderSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologyLoaderSvc.java new file mode 100644 index 00000000000..3c0fb2487a4 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologyLoaderSvc.java @@ -0,0 +1,11 @@ +package ca.uhn.fhir.jpa.term; + +import ca.uhn.fhir.rest.method.RequestDetails; + +public interface IHapiTerminologyLoaderSvc { + + String SCT_URL = "http://snomed.info/sct"; + + void loadSnomedCt(byte[] theZipBytes, RequestDetails theRequestDetails); + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvcDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvcDstu3.java new file mode 100644 index 00000000000..c691718de5b --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvcDstu3.java @@ -0,0 +1,7 @@ +package ca.uhn.fhir.jpa.term; + +import org.hl7.fhir.dstu3.hapi.validation.IValidationSupport; + +public interface IHapiTerminologySvcDstu3 extends IHapiTerminologySvc, IValidationSupport { + // nothing +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvc.java index b5ce18d81a2..5fa58aad8dc 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvc.java @@ -21,20 +21,16 @@ package ca.uhn.fhir.jpa.term; */ import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; -import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -50,7 +46,6 @@ import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.builder.HashCodeBuilder; import org.springframework.beans.factory.annotation.Autowired; import com.google.common.annotations.VisibleForTesting; @@ -62,101 +57,20 @@ import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -public class TerminologyLoaderSvc { - static final String SCT_FILE_RELATIONSHIP = "Terminology/sct2_Relationship_Full"; - static final String SCT_FILE_DESCRIPTION = "Terminology/sct2_Description_Full"; - static final String SCT_FILE_CONCEPT = "Terminology/sct2_Concept_Full"; - +public class TerminologyLoaderSvc implements IHapiTerminologyLoaderSvc { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TerminologyLoaderSvc.class); + static final String SCT_FILE_CONCEPT = "Terminology/sct2_Concept_Full"; + static final String SCT_FILE_DESCRIPTION = "Terminology/sct2_Description_Full"; + + static final String SCT_FILE_RELATIONSHIP = "Terminology/sct2_Relationship_Full"; + @Autowired private IHapiTerminologySvc myTermSvc; - @VisibleForTesting - void setTermSvcForUnitTests(IHapiTerminologySvc theTermSvc) { - myTermSvc = theTermSvc; - } - - public void loadSnomedCt(byte[] theZipBytes, RequestDetails theRequestDetails) { - List allFilenames = Arrays.asList(SCT_FILE_DESCRIPTION, SCT_FILE_RELATIONSHIP, SCT_FILE_CONCEPT); - - Map filenameToFile = new HashMap(); - ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new ByteArrayInputStream(theZipBytes))); - try { - for (ZipEntry nextEntry; (nextEntry = zis.getNextEntry()) != null;) { - ZippedFileInputStream inputStream = new ZippedFileInputStream(zis); - - boolean want = false; - for (String next : allFilenames) { - if (nextEntry.getName().contains(next)) { - want = true; - } - } - - if (!want) { - ourLog.info("Ignoring zip entry: {}", nextEntry.getName()); - continue; - } - - ourLog.debug("Streaming ZIP entry {} into temporary file", nextEntry.getName()); - - File nextOutFile = File.createTempFile("hapi_fhir", ".csv"); - nextOutFile.deleteOnExit(); - OutputStream outputStream = new SinkOutputStream(new FileOutputStream(nextOutFile, false), nextEntry.getName()); - try { - IOUtils.copyLarge(inputStream, outputStream); - } finally { - IOUtils.closeQuietly(outputStream); - } - - filenameToFile.put(nextEntry.getName(), nextOutFile); - } - } catch (IOException e) { - throw new InternalErrorException(e); - } finally { - IOUtils.closeQuietly(zis); - } - - ourLog.info("Beginning SNOMED CT processing"); - - processSnomedCtFiles(filenameToFile,theRequestDetails); - } - - void processSnomedCtFiles(Map filenameToFile, RequestDetails theRequestDetails) { - final TermCodeSystemVersion codeSystemVersion = new TermCodeSystemVersion(); - final Map id2concept = new HashMap(); - final Map code2concept = new HashMap(); - final Set validConceptIds = new HashSet(); - final List links = new ArrayList(); - - IRecordHandler handler = new SctHandlerConcept(validConceptIds); - iterateOverZipFile(filenameToFile, SCT_FILE_CONCEPT, handler); - - ourLog.info("Have {} valid concept IDs", validConceptIds.size()); - - handler = new SctHandlerDescription(validConceptIds, code2concept, id2concept, codeSystemVersion); - iterateOverZipFile(filenameToFile, SCT_FILE_DESCRIPTION, handler); - - ourLog.info("Got {} concepts, cloning map", code2concept.size()); - final HashMap rootConcepts = new HashMap(code2concept); - - handler = new SctHandlerRelationship(codeSystemVersion, rootConcepts, code2concept); - iterateOverZipFile(filenameToFile, SCT_FILE_RELATIONSHIP, handler); - - ourLog.info("Done loading SNOMED CT files - {} root codes, {} total codes", rootConcepts.size(), code2concept.size()); - - - for (TermConcept next : rootConcepts.values()){ - dropCircularRefs(next, new HashSet()); - } - - codeSystemVersion.getConcepts().addAll(rootConcepts.values()); - myTermSvc.storeNewCodeSystemVersion("http://snomed.info/sct", codeSystemVersion, theRequestDetails); - } - private void dropCircularRefs(TermConcept theConcept, HashSet theChain) { - - for (Iterator childIter = theConcept.getChildren().iterator(); childIter.hasNext(); ) { + + for (Iterator childIter = theConcept.getChildren().iterator(); childIter.hasNext();) { TermConceptParentChildLink next = childIter.next(); TermConcept nextChild = next.getChild(); if (theChain.contains(nextChild.getCode())) { @@ -170,6 +84,16 @@ public class TerminologyLoaderSvc { } } + private TermConcept getOrCreateConcept(TermCodeSystemVersion codeSystemVersion, Map id2concept, String id) { + TermConcept concept = id2concept.get(id); + if (concept == null) { + concept = new TermConcept(); + id2concept.put(id, concept); + concept.setCodeSystem(codeSystemVersion); + } + return concept; + } + private void iterateOverZipFile(Map theFilenameToFile, String fileNamePart, IRecordHandler handler) { for (Entry nextEntry : theFilenameToFile.entrySet()) { @@ -206,79 +130,134 @@ public class TerminologyLoaderSvc { } } - private TermConcept getOrCreateConcept(TermCodeSystemVersion codeSystemVersion, Map id2concept, String id) { - TermConcept concept = id2concept.get(id); - if (concept == null) { - concept = new TermConcept(); - id2concept.put(id, concept); - concept.setCodeSystem(codeSystemVersion); + @Override + public void loadSnomedCt(byte[] theZipBytes, RequestDetails theRequestDetails) { + List allFilenames = Arrays.asList(SCT_FILE_DESCRIPTION, SCT_FILE_RELATIONSHIP, SCT_FILE_CONCEPT); + + Map filenameToFile = new HashMap(); + ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new ByteArrayInputStream(theZipBytes))); + try { + for (ZipEntry nextEntry; (nextEntry = zis.getNextEntry()) != null;) { + ZippedFileInputStream inputStream = new ZippedFileInputStream(zis); + + boolean want = false; + for (String next : allFilenames) { + if (nextEntry.getName().contains(next)) { + want = true; + } + } + + if (!want) { + ourLog.info("Ignoring zip entry: {}", nextEntry.getName()); + continue; + } + + ourLog.debug("Streaming ZIP entry {} into temporary file", nextEntry.getName()); + + File nextOutFile = File.createTempFile("hapi_fhir", ".csv"); + nextOutFile.deleteOnExit(); + OutputStream outputStream = new SinkOutputStream(new FileOutputStream(nextOutFile, false), nextEntry.getName()); + try { + IOUtils.copyLarge(inputStream, outputStream); + } finally { + IOUtils.closeQuietly(outputStream); + } + + filenameToFile.put(nextEntry.getName(), nextOutFile); + } + } catch (IOException e) { + throw new InternalErrorException(e); + } finally { + IOUtils.closeQuietly(zis); + } + + ourLog.info("Beginning SNOMED CT processing"); + + try { + processSnomedCtFiles(filenameToFile, theRequestDetails); + } finally { + ourLog.info("Finished SNOMED CT file import, cleaning up temporary files"); + for (File nextFile : filenameToFile.values()) { + nextFile.delete(); + } } - return concept; } - private final class SctHandlerRelationship implements IRecordHandler { - private final TermCodeSystemVersion myCodeSystemVersion; - private final Map myRootConcepts; - private final Map myCode2concept; + void processSnomedCtFiles(Map filenameToFile, RequestDetails theRequestDetails) { + final TermCodeSystemVersion codeSystemVersion = new TermCodeSystemVersion(); + final Map id2concept = new HashMap(); + final Map code2concept = new HashMap(); + final Set validConceptIds = new HashSet(); - private SctHandlerRelationship(TermCodeSystemVersion theCodeSystemVersion, HashMap theRootConcepts, Map theCode2concept) { - myCodeSystemVersion = theCodeSystemVersion; - myRootConcepts = theRootConcepts; - myCode2concept = theCode2concept; + IRecordHandler handler = new SctHandlerConcept(validConceptIds); + iterateOverZipFile(filenameToFile, SCT_FILE_CONCEPT, handler); + + ourLog.info("Have {} valid concept IDs", validConceptIds.size()); + + handler = new SctHandlerDescription(validConceptIds, code2concept, id2concept, codeSystemVersion); + iterateOverZipFile(filenameToFile, SCT_FILE_DESCRIPTION, handler); + + ourLog.info("Got {} concepts, cloning map", code2concept.size()); + final HashMap rootConcepts = new HashMap(code2concept); + + handler = new SctHandlerRelationship(codeSystemVersion, rootConcepts, code2concept); + iterateOverZipFile(filenameToFile, SCT_FILE_RELATIONSHIP, handler); + + ourLog.info("Done loading SNOMED CT files - {} root codes, {} total codes", rootConcepts.size(), code2concept.size()); + + for (TermConcept next : rootConcepts.values()) { + dropCircularRefs(next, new HashSet()); + } + + codeSystemVersion.getConcepts().addAll(rootConcepts.values()); + myTermSvc.storeNewCodeSystemVersion(SCT_URL, codeSystemVersion, theRequestDetails); + } + + @VisibleForTesting + void setTermSvcForUnitTests(IHapiTerminologySvc theTermSvc) { + myTermSvc = theTermSvc; + } + + public static void main(String[] args) throws Exception { + TerminologyLoaderSvc svc = new TerminologyLoaderSvc(); + + // byte[] bytes = IOUtils.toByteArray(new FileInputStream("/Users/james/Downloads/SnomedCT_Release_INT_20160131_Full.zip")); + // svc.loadSnomedCt(bytes); + + Map files = new HashMap(); + files.put(SCT_FILE_CONCEPT, new File("/Users/james/tmp/sct/SnomedCT_Release_INT_20160131_Full/Terminology/sct2_Concept_Full_INT_20160131.txt")); + files.put(SCT_FILE_DESCRIPTION, new File("/Users/james/tmp/sct/SnomedCT_Release_INT_20160131_Full/Terminology/sct2_Description_Full-en_INT_20160131.txt")); + files.put(SCT_FILE_RELATIONSHIP, new File("/Users/james/tmp/sct/SnomedCT_Release_INT_20160131_Full/Terminology/sct2_Relationship_Full_INT_20160131.txt")); + svc.processSnomedCtFiles(files, null); + } + + private interface IRecordHandler { + void accept(CSVRecord theRecord); + } + + private final class SctHandlerConcept implements IRecordHandler { + + private Set myValidConceptIds; + + public SctHandlerConcept(Set theValidConceptIds) { + myValidConceptIds = theValidConceptIds; } @Override public void accept(CSVRecord theRecord) { - Set ignoredTypes = new HashSet(); - ignoredTypes.add("Method (attribute)"); - ignoredTypes.add("Direct device (attribute)"); - ignoredTypes.add("Has focus (attribute)"); - ignoredTypes.add("Access instrument"); - ignoredTypes.add("Procedure site (attribute)"); - ignoredTypes.add("Causative agent (attribute)"); - ignoredTypes.add("Course (attribute)"); - ignoredTypes.add("Finding site (attribute)"); - ignoredTypes.add("Has definitional manifestation (attribute)"); - - String sourceId = theRecord.get("sourceId"); - String destinationId = theRecord.get("destinationId"); - String typeId = theRecord.get("typeId"); + String id = theRecord.get("id"); boolean active = "1".equals(theRecord.get("active")); if (!active) { return; } - TermConcept typeConcept = findConcept(myCode2concept, typeId); - TermConcept sourceConcept = findConcept(myCode2concept, sourceId); - TermConcept targetConcept = findConcept(myCode2concept, destinationId); - if (typeConcept.getDisplay().equals("Is a (attribute)")) { - TermConceptParentChildLink link = new TermConceptParentChildLink(); - link.setChild(sourceConcept); - link.setParent(targetConcept); - link.setRelationshipType(TermConceptParentChildLink.RelationshipTypeEnum.ISA); - link.setCodeSystem(myCodeSystemVersion); - myRootConcepts.remove(link.getChild().getCode()); - - targetConcept.addChild(sourceConcept, RelationshipTypeEnum.ISA); - } else if (ignoredTypes.contains(typeConcept.getDisplay())) { - // ignore - } else { -// ourLog.warn("Unknown relationship type: {}/{}", typeId, typeConcept.getDisplay()); - } - } - - private TermConcept findConcept(final Map code2concept, String typeId) { - TermConcept typeConcept = code2concept.get(typeId); - if (typeConcept == null) { - throw new InternalErrorException("Unknown type ID: " + typeId); - } - return typeConcept; + myValidConceptIds.add(id); } } private final class SctHandlerDescription implements IRecordHandler { private final Map myCode2concept; - private final Map myId2concept; private final TermCodeSystemVersion myCodeSystemVersion; + private final Map myId2concept; private Set myValidConceptIds; private SctHandlerDescription(Set theValidConceptIds, Map theCode2concept, Map theId2concept, TermCodeSystemVersion theCodeSystemVersion) { @@ -309,80 +288,78 @@ public class TerminologyLoaderSvc { } } - private final class SctHandlerConcept implements IRecordHandler { + private final class SctHandlerRelationship implements IRecordHandler { + private final Map myCode2concept; + private final TermCodeSystemVersion myCodeSystemVersion; + private final Map myRootConcepts; - private Set myValidConceptIds; - - public SctHandlerConcept(Set theValidConceptIds) { - myValidConceptIds = theValidConceptIds; + private SctHandlerRelationship(TermCodeSystemVersion theCodeSystemVersion, HashMap theRootConcepts, Map theCode2concept) { + myCodeSystemVersion = theCodeSystemVersion; + myRootConcepts = theRootConcepts; + myCode2concept = theCode2concept; } @Override public void accept(CSVRecord theRecord) { - String id = theRecord.get("id"); + Set ignoredTypes = new HashSet(); + ignoredTypes.add("Method (attribute)"); + ignoredTypes.add("Direct device (attribute)"); + ignoredTypes.add("Has focus (attribute)"); + ignoredTypes.add("Access instrument"); + ignoredTypes.add("Procedure site (attribute)"); + ignoredTypes.add("Causative agent (attribute)"); + ignoredTypes.add("Course (attribute)"); + ignoredTypes.add("Finding site (attribute)"); + ignoredTypes.add("Has definitional manifestation (attribute)"); + + String sourceId = theRecord.get("sourceId"); + String destinationId = theRecord.get("destinationId"); + String typeId = theRecord.get("typeId"); boolean active = "1".equals(theRecord.get("active")); if (!active) { return; } - myValidConceptIds.add(id); - } - } + TermConcept typeConcept = findConcept(myCode2concept, typeId); + TermConcept sourceConcept = findConcept(myCode2concept, sourceId); + TermConcept targetConcept = findConcept(myCode2concept, destinationId); + if (typeConcept.getDisplay().equals("Is a (attribute)")) { + TermConceptParentChildLink link = new TermConceptParentChildLink(); + link.setChild(sourceConcept); + link.setParent(targetConcept); + link.setRelationshipType(TermConceptParentChildLink.RelationshipTypeEnum.ISA); + link.setCodeSystem(myCodeSystemVersion); + myRootConcepts.remove(link.getChild().getCode()); - private static class ZippedFileInputStream extends InputStream { - - private ZipInputStream is; - - public ZippedFileInputStream(ZipInputStream is) { - this.is = is; + targetConcept.addChild(sourceConcept, RelationshipTypeEnum.ISA); + } else if (ignoredTypes.contains(typeConcept.getDisplay())) { + // ignore + } else { + // ourLog.warn("Unknown relationship type: {}/{}", typeId, typeConcept.getDisplay()); + } } - @Override - public int read() throws IOException { - return is.read(); + private TermConcept findConcept(final Map code2concept, String typeId) { + TermConcept typeConcept = code2concept.get(typeId); + if (typeConcept == null) { + throw new InternalErrorException("Unknown type ID: " + typeId); + } + return typeConcept; } - - @Override - public void close() throws IOException { - is.closeEntry(); - } - } - - private interface IRecordHandler { - void accept(CSVRecord theRecord); - } - - public static void main(String[] args) throws Exception { - TerminologyLoaderSvc svc = new TerminologyLoaderSvc(); - -// byte[] bytes = IOUtils.toByteArray(new FileInputStream("/Users/james/Downloads/SnomedCT_Release_INT_20160131_Full.zip")); -// svc.loadSnomedCt(bytes); - - Map files = new HashMap(); - files.put(SCT_FILE_CONCEPT, new File("/Users/james/tmp/sct/SnomedCT_Release_INT_20160131_Full/Terminology/sct2_Concept_Full_INT_20160131.txt")); - files.put(SCT_FILE_DESCRIPTION, new File("/Users/james/tmp/sct/SnomedCT_Release_INT_20160131_Full/Terminology/sct2_Description_Full-en_INT_20160131.txt")); - files.put(SCT_FILE_RELATIONSHIP, new File("/Users/james/tmp/sct/SnomedCT_Release_INT_20160131_Full/Terminology/sct2_Relationship_Full_INT_20160131.txt")); - svc.processSnomedCtFiles(files, null); } private static class SinkOutputStream extends OutputStream { private static final long LOG_INCREMENT = 10 * FileUtils.ONE_MB; - private FileOutputStream myWrap; private int myBytes; - private long myNextLogCount = LOG_INCREMENT; private String myFilename; + private long myNextLogCount = LOG_INCREMENT; + private FileOutputStream myWrap; public SinkOutputStream(FileOutputStream theWrap, String theFilename) { myWrap = theWrap; myFilename = theFilename; } - @Override - public void write(int theB) throws IOException { - myWrap.write(theB); - addCount(1); - } - private void addCount(int theCount) { myBytes += theCount; if (myBytes > myNextLogCount) { @@ -391,6 +368,16 @@ public class TerminologyLoaderSvc { } } + @Override + public void close() throws IOException { + myWrap.close(); + } + + @Override + public void flush() throws IOException { + myWrap.flush(); + } + @Override public void write(byte[] theB) throws IOException { myWrap.write(theB); @@ -404,15 +391,30 @@ public class TerminologyLoaderSvc { } @Override - public void flush() throws IOException { - myWrap.flush(); + public void write(int theB) throws IOException { + myWrap.write(theB); + addCount(1); + } + + } + + private static class ZippedFileInputStream extends InputStream { + + private ZipInputStream is; + + public ZippedFileInputStream(ZipInputStream is) { + this.is = is; } @Override public void close() throws IOException { - myWrap.close(); + is.closeEntry(); + } + + @Override + public int read() throws IOException { + return is.read(); } - } - + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChainDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChainDstu3.java index fcfbe0780f7..feeda55845a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChainDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChainDstu3.java @@ -28,6 +28,8 @@ import org.hl7.fhir.dstu3.hapi.validation.ValidationSupportChain; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import ca.uhn.fhir.jpa.term.IHapiTerminologySvcDstu3; + public class JpaValidationSupportChainDstu3 extends ValidationSupportChain { private DefaultProfileValidationSupport myDefaultProfileValidationSupport = new DefaultProfileValidationSupport(); @@ -36,6 +38,9 @@ public class JpaValidationSupportChainDstu3 extends ValidationSupportChain { @Qualifier("myJpaValidationSupportDstu3") public ca.uhn.fhir.jpa.dao.dstu3.IJpaValidationSupportDstu3 myJpaValidationSupportDstu3; + @Autowired + private IHapiTerminologySvcDstu3 myTerminologyService; + public JpaValidationSupportChainDstu3() { super(); } @@ -48,6 +53,7 @@ public class JpaValidationSupportChainDstu3 extends ValidationSupportChain { public void postConstruct() { addValidationSupport(myDefaultProfileValidationSupport); addValidationSupport(myJpaValidationSupportDstu3); + addValidationSupport(myTerminologyService); } @PreDestroy diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoTerminologyDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoTerminologyDstu3Test.java index 8967fb22f47..9e1f6944867 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoTerminologyDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoTerminologyDstu3Test.java @@ -22,7 +22,10 @@ import org.junit.Ignore; import org.junit.Test; import ca.uhn.fhir.jpa.dao.SearchParameterMap; +import ca.uhn.fhir.jpa.entity.ResourceTable; +import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; +import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParamModifier; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -121,6 +124,87 @@ public class FhirResourceDaoTerminologyDstu3Test extends BaseJpaDstu3Test { } + @Test + public void testSearchCodeInExternalCodesystem() { + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setUrl(URL_MY_CODE_SYSTEM); + codeSystem.setContent(CodeSystemContentMode.NOTPRESENT); + IIdType id = myCodeSystemDao.create(codeSystem, new ServletRequestDetails()).getId().toUnqualified(); + + ResourceTable table = myResourceTableDao.findOne(id.getIdPartAsLong()); + + TermCodeSystemVersion cs = new TermCodeSystemVersion(); + cs.setResource(table); + cs.setResourceVersionId(table.getVersion()); + + TermConcept parentA = new TermConcept(cs, "ParentA"); + cs.getConcepts().add(parentA); + + TermConcept childAA = new TermConcept(cs, "childAA"); + parentA.addChild(childAA, RelationshipTypeEnum.ISA); + + TermConcept childAAA = new TermConcept(cs, "childAAA"); + childAA.addChild(childAAA, RelationshipTypeEnum.ISA); + + TermConcept childAAB = new TermConcept(cs, "childAAB"); + childAA.addChild(childAAB, RelationshipTypeEnum.ISA); + + TermConcept childAB = new TermConcept(cs, "childAB"); + parentA.addChild(childAB, RelationshipTypeEnum.ISA); + + TermConcept parentB = new TermConcept(cs, "ParentB"); + cs.getConcepts().add(parentB); + + myTermSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, cs); + + createLocalVs(codeSystem); + + Observation obsPA = new Observation(); + obsPA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("ParentA"); + IIdType idPA = myObservationDao.create(obsPA, mySrd).getId().toUnqualifiedVersionless(); + + Observation obsAAA = new Observation(); + obsAAA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("childAAA"); + IIdType idAAA = myObservationDao.create(obsAAA, mySrd).getId().toUnqualifiedVersionless(); + + Observation obsAAB = new Observation(); + obsAAB.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("childAAB"); + IIdType idAAB = myObservationDao.create(obsAAB, mySrd).getId().toUnqualifiedVersionless(); + + Observation obsCA = new Observation(); + obsCA.getCode().addCoding().setSystem(URL_MY_CODE_SYSTEM).setCode("CA"); + IIdType idCA = myObservationDao.create(obsCA, mySrd).getId().toUnqualifiedVersionless(); + + SearchParameterMap params = new SearchParameterMap(); + params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "childAA").setModifier(TokenParamModifier.BELOW)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), containsInAnyOrder(idAAA.getValue(), idAAB.getValue())); + + params = new SearchParameterMap(); + params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "childAA").setModifier(TokenParamModifier.ABOVE)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), containsInAnyOrder(idPA.getValue())); + + params = new SearchParameterMap(); + params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), containsInAnyOrder(idPA.getValue(), idAAA.getValue(), idAAB.getValue())); + + } + + @Test + public void testSearchCodeBelowAndAboveUnknownCodeSystem() { + + SearchParameterMap params = new SearchParameterMap(); + + params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "childAA").setModifier(TokenParamModifier.BELOW)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty()); + + params.add(Observation.SP_CODE, new TokenParam(URL_MY_CODE_SYSTEM, "childAA").setModifier(TokenParamModifier.ABOVE)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty()); + + params.add(Observation.SP_CODE, new TokenParam(null, URL_MY_VALUE_SET).setModifier(TokenParamModifier.IN)); + assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(params)), empty()); + + } + @Test public void testSearchCodeInFhirCodesystem() { createLocalCsAndVs(); @@ -255,6 +339,11 @@ public class FhirResourceDaoTerminologyDstu3Test extends BaseJpaDstu3Test { //@formatter:on myCodeSystemDao.create(codeSystem, new ServletRequestDetails()); + createLocalVs(codeSystem); + } + + + private void createLocalVs(CodeSystem codeSystem) { ValueSet valueSet = new ValueSet(); valueSet.setUrl(URL_MY_VALUE_SET); valueSet.getCompose().addInclude().setSystem(codeSystem.getUrl()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationTest.java index 9be141ea3b0..4b6bfaab01c 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationTest.java @@ -35,7 +35,6 @@ public class TerminologyLoaderSvcIntegrationTest extends BaseJpaDstu3Test { files.put(TerminologyLoaderSvc.SCT_FILE_DESCRIPTION, new File("/Users/james/tmp/sct/SnomedCT_Release_INT_20160131_Full/Terminology/sct2_Description_Full-en_INT_20160131.txt")); files.put(TerminologyLoaderSvc.SCT_FILE_RELATIONSHIP, new File("/Users/james/tmp/sct/SnomedCT_Release_INT_20160131_Full/Terminology/sct2_Relationship_Full_INT_20160131.txt")); myLoader.processSnomedCtFiles(files, mySrd); - } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplTest.java index 71eab850f89..61099941aa9 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplTest.java @@ -27,6 +27,9 @@ import ca.uhn.fhir.util.TestUtil; public class TerminologySvcImplTest extends BaseJpaDstu3Test { + private static final String CS_URL = "http://example.com/my_code_system"; + + @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); @@ -36,7 +39,7 @@ public class TerminologySvcImplTest extends BaseJpaDstu3Test { @Test public void testStoreCodeSystemInvalidCyclicLoop() { CodeSystem codeSystem = new CodeSystem(); - codeSystem.setUrl("http://example.com/my_code_system"); + codeSystem.setUrl(CS_URL); codeSystem.setContent(CodeSystemContentMode.NOTPRESENT); IIdType id = myCodeSystemDao.create(codeSystem, new ServletRequestDetails()).getId().toUnqualified(); @@ -66,38 +69,19 @@ public class TerminologySvcImplTest extends BaseJpaDstu3Test { } } + @Test + public void testFindCodesAboveAndBelowUnknown() { + createCodeSystem(); + + assertThat(myTermSvc.findCodesBelow("http://foo", "code"), empty()); + assertThat(myTermSvc.findCodesBelow(CS_URL, "code"), empty()); + assertThat(myTermSvc.findCodesAbove("http://foo", "code"), empty()); + assertThat(myTermSvc.findCodesAbove(CS_URL, "code"), empty()); + } + @Test public void testFindCodesBelowA() { - CodeSystem codeSystem = new CodeSystem(); - codeSystem.setUrl("http://example.com/my_code_system"); - codeSystem.setContent(CodeSystemContentMode.NOTPRESENT); - IIdType id = myCodeSystemDao.create(codeSystem, new ServletRequestDetails()).getId().toUnqualified(); - - ResourceTable table = myResourceTableDao.findOne(id.getIdPartAsLong()); - - TermCodeSystemVersion cs = new TermCodeSystemVersion(); - cs.setResource(table); - cs.setResourceVersionId(table.getVersion()); - - TermConcept parentA = new TermConcept(cs, "ParentA"); - cs.getConcepts().add(parentA); - - TermConcept childAA = new TermConcept(cs, "childAA"); - parentA.addChild(childAA, RelationshipTypeEnum.ISA); - - TermConcept childAAA = new TermConcept(cs, "childAAA"); - childAA.addChild(childAAA, RelationshipTypeEnum.ISA); - - TermConcept childAAB = new TermConcept(cs, "childAAB"); - childAA.addChild(childAAB, RelationshipTypeEnum.ISA); - - TermConcept childAB = new TermConcept(cs, "childAB"); - parentA.addChild(childAB, RelationshipTypeEnum.ISA); - - TermConcept parentB = new TermConcept(cs, "ParentB"); - cs.getConcepts().add(parentB); - - myTermSvc.storeNewCodeSystemVersion(table.getId(), "http://foo", cs); + IIdType id = createCodeSystem(); Set concepts; Set codes; @@ -116,11 +100,11 @@ public class TerminologySvcImplTest extends BaseJpaDstu3Test { assertThat(codes, empty()); } - - @Test - public void testFindCodesAbove() { + + + private IIdType createCodeSystem() { CodeSystem codeSystem = new CodeSystem(); - codeSystem.setUrl("http://example.com/my_code_system"); + codeSystem.setUrl(CS_URL); codeSystem.setContent(CodeSystemContentMode.NOTPRESENT); IIdType id = myCodeSystemDao.create(codeSystem, new ServletRequestDetails()).getId().toUnqualified(); @@ -149,6 +133,12 @@ public class TerminologySvcImplTest extends BaseJpaDstu3Test { cs.getConcepts().add(parentB); myTermSvc.storeNewCodeSystemVersion(table.getId(), "http://foo", cs); + return id; + } + + @Test + public void testFindCodesAbove() { + IIdType id = createCodeSystem(); Set concepts; Set codes; @@ -171,7 +161,7 @@ public class TerminologySvcImplTest extends BaseJpaDstu3Test { @Test public void testCreateDuplicateCodeSystemUri() { CodeSystem codeSystem = new CodeSystem(); - codeSystem.setUrl("http://example.com/my_code_system"); + codeSystem.setUrl(CS_URL); codeSystem.setContent(CodeSystemContentMode.NOTPRESENT); IIdType id = myCodeSystemDao.create(codeSystem, new ServletRequestDetails()).getId().toUnqualified(); @@ -181,7 +171,7 @@ public class TerminologySvcImplTest extends BaseJpaDstu3Test { cs.setResource(table); cs.setResourceVersionId(table.getVersion()); - myTermSvc.storeNewCodeSystemVersion(table.getId(), "http://example.com/my_code_system", cs); + myTermSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, cs); // Update cs = new TermCodeSystemVersion(); @@ -191,18 +181,18 @@ public class TerminologySvcImplTest extends BaseJpaDstu3Test { table = myResourceTableDao.findOne(id.getIdPartAsLong()); cs.setResource(table); cs.setResourceVersionId(table.getVersion()); - myTermSvc.storeNewCodeSystemVersion(table.getId(), "http://example.com/my_code_system", cs); + myTermSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, cs); // Try to update to a different resource codeSystem = new CodeSystem(); - codeSystem.setUrl("http://example.com/my_code_system"); + codeSystem.setUrl(CS_URL); codeSystem.setContent(CodeSystemContentMode.NOTPRESENT); id = myCodeSystemDao.create(codeSystem, new ServletRequestDetails()).getId().toUnqualified(); table = myResourceTableDao.findOne(id.getIdPartAsLong()); cs.setResource(table); cs.setResourceVersionId(table.getVersion()); try { - myTermSvc.storeNewCodeSystemVersion(table.getId(), "http://example.com/my_code_system", cs); + myTermSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, cs); fail(); } catch (UnprocessableEntityException e) { assertThat(e.getMessage(), containsString("Can not create multiple code systems with URI \"http://example.com/my_code_system\", already have one with resource ID: CodeSystem/"));