More work on SNOMED CT terminology loader

This commit is contained in:
jamesagnew 2016-05-26 07:53:36 -04:00
parent 7b024d4611
commit badcbfc0ee
19 changed files with 630 additions and 334 deletions

View File

@ -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();
}

View File

@ -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;

View File

@ -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<TermCodeSystemVersion, Long> {
@Query("SELECT cs FROM TermCodeSystemVersion cs WHERE cs.myResource.myId = :resource_id")
List<TermCodeSystemVersion> 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);

View File

@ -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<TermConcept, Long> {
@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<TermConcept> 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);
}

View File

@ -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<TermConceptParentChildLink, Long> {
// nothing
@Query("DELETE FROM TermConceptParentChildLink t WHERE t.myCodeSystem.myId = :cs_pid")
@Modifying
void deleteByCodeSystemVersion(@Param("cs_pid") Long thePid);
}

View File

@ -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<CodeSystem> implements IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> {
@ -77,29 +73,29 @@ public class FhirResourceDaoCodeSystemDstu3 extends FhirResourceDaoDstu3<CodeSys
return valueSetIds;
}
private LookupCodeResult lookup(List<ValueSetExpansionContainsComponent> 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<ValueSetExpansionContainsComponent> 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<String> theCode, IPrimitiveType<String> theSystem, Coding theCoding, RequestDetails theRequestDetails) {
@ -138,47 +134,40 @@ public class FhirResourceDaoCodeSystemDstu3 extends FhirResourceDaoDstu3<CodeSys
// return result;
if (myValidationSupport.isCodeSystemSupported(getContext(), system)) {
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<ValueSetExpansionContainsComponent> 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<IIdType> 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<ValueSetExpansionContainsComponent> 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<CodeSys
if (cs != null && isNotBlank(cs.getUrl())) {
String codeSystemUrl = cs.getUrl();
if (cs.getContent() == CodeSystemContentMode.COMPLETE) {
ourLog.info("CodeSystem {} has a status of {}, going to store concepts in terminology tables", retVal.getIdDt().getValue(), cs.getContent().toCode());
if (cs.getContent() == CodeSystemContentMode.COMPLETE || cs.getContent() == null) {
ourLog.info("CodeSystem {} has a status of {}, going to store concepts in terminology tables", retVal.getIdDt().getValue(), cs.getContentElement().getValueAsString());
TermCodeSystemVersion persCs = new TermCodeSystemVersion();
persCs.setResource(retVal);
persCs.setResourceVersionId(retVal.getVersion());

View File

@ -54,7 +54,7 @@ public class TermCodeSystemVersion implements Serializable {
@SequenceGenerator(name = "SEQ_CODESYSTEMVER_PID", sequenceName = "SEQ_CODESYSTEMVER_PID")
@GeneratedValue(strategy=GenerationType.AUTO, generator="SEQ_CODESYSTEMVER_PID")
@Column(name = "PID")
private Long myPid;
private Long myId;
@OneToOne()
@JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false, updatable = false, foreignKey=@ForeignKey(name="FK_CODESYSVER_RES_ID"))
@ -70,6 +70,10 @@ public class TermCodeSystemVersion implements Serializable {
return myConcepts;
}
public Long getPid() {
return myId;
}
public ResourceTable getResource() {
return myResource;
}

View File

@ -28,6 +28,7 @@ import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.jboss.logging.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;

View File

@ -79,5 +79,4 @@ public class BaseJpaResourceProviderCodeSystemDstu3 extends JpaResourceProviderD
}
}
}

View File

@ -0,0 +1,55 @@
package ca.uhn.fhir.jpa.provider.dstu3;
import javax.servlet.http.HttpServletRequest;
import org.hl7.fhir.dstu3.model.Attachment;
import org.hl7.fhir.dstu3.model.IntegerType;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.UriType;
import org.springframework.beans.factory.annotation.Autowired;
import ca.uhn.fhir.jpa.provider.BaseJpaProvider;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class TerminologyUploaderProviderDstu3 extends BaseJpaProvider {
@Autowired
private IHapiTerminologyLoaderSvc myTerminologyLoaderSvc;
//@formatter:off
@Operation(name = "$upload-external-code-system", idempotent = false, returnParameters= {
@OperationParam(name="conceptCount", type=IntegerType.class, min=1)
})
public Parameters lookup(
HttpServletRequest theServletRequest,
@OperationParam(name="url", min=1) UriType theUrl,
@OperationParam(name="package", min=1) Attachment thePackage,
RequestDetails theRequestDetails
) {
//@formatter:on
startRequest(theServletRequest);
try {
byte[] data = thePackage.getData();
String url = theUrl.getValueAsString();
if (IHapiTerminologyLoaderSvc.SCT_URL.equals(url)) {
myTerminologyLoaderSvc.loadSnomedCt(data, theRequestDetails);
} else {
throw new InvalidRequestException("Unknown URL: " + url);
}
Parameters retVal = new Parameters();
retVal.addParameter().setName("conceptCount").setValue(new IntegerType(0));
return retVal;
} finally {
endRequest(theServletRequest);
}
}
}

View File

@ -36,7 +36,6 @@ import com.google.common.base.Stopwatch;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao;
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemVersionDao;
import ca.uhn.fhir.jpa.dao.data.ITermConceptDao;
@ -45,7 +44,6 @@ 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.jpa.entity.TermConceptParentChildLink;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.ObjectUtil;
@ -62,7 +60,7 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
private ITermCodeSystemVersionDao myCodeSystemVersionDao;
@Autowired
private ITermConceptDao myConceptDao;
protected ITermConceptDao myConceptDao;
@Autowired
private DaoConfig myDaoConfig;
@ -129,7 +127,10 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
@Override
public List<VersionIndependentConcept> 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<TermConcept> codes = findCodesAbove(cs.getResource().getId(), csv.getResourceVersionId(), theCode);
@ -158,7 +159,10 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
@Override
public List<VersionIndependentConcept> 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<TermConcept> 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<TermCodeSystemVersion> 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<VersionIndependentConcept> toVersionIndependentConcepts(String theSystem, Set<TermConcept> codes) {
ArrayList<VersionIndependentConcept> retVal = new ArrayList<VersionIndependentConcept>(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);
}
}

View File

@ -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<CodeSystem, Coding, CodeableConcept> 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<ConceptSetFilterComponent> theFilters, String theSystem) {
ValueSetExpansionContainsComponent contains = retVal.addContains();
contains.setSystem(theSystem);
contains.setCode(termCode.getCode());
contains.setDisplay(termCode.getDisplay());
}
@Override
public List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext) {
return Collections.emptyList();
}
@CoverageIgnore
@Override
public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) {
throw new UnsupportedOperationException();
}
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> 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);
}
}

View File

@ -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);
}

View File

@ -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
}

View File

@ -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<String> allFilenames = Arrays.asList(SCT_FILE_DESCRIPTION, SCT_FILE_RELATIONSHIP, SCT_FILE_CONCEPT);
Map<String, File> filenameToFile = new HashMap<String, File>();
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<String, File> filenameToFile, RequestDetails theRequestDetails) {
final TermCodeSystemVersion codeSystemVersion = new TermCodeSystemVersion();
final Map<String, TermConcept> id2concept = new HashMap<String, TermConcept>();
final Map<String, TermConcept> code2concept = new HashMap<String, TermConcept>();
final Set<String> validConceptIds = new HashSet<String>();
final List<TermConceptParentChildLink> links = new ArrayList<TermConceptParentChildLink>();
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<String, TermConcept> rootConcepts = new HashMap<String, TermConcept>(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<String>());
}
codeSystemVersion.getConcepts().addAll(rootConcepts.values());
myTermSvc.storeNewCodeSystemVersion("http://snomed.info/sct", codeSystemVersion, theRequestDetails);
}
private void dropCircularRefs(TermConcept theConcept, HashSet<String> theChain) {
for (Iterator<TermConceptParentChildLink> childIter = theConcept.getChildren().iterator(); childIter.hasNext(); ) {
for (Iterator<TermConceptParentChildLink> 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<String, TermConcept> 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<String, File> theFilenameToFile, String fileNamePart, IRecordHandler handler) {
for (Entry<String, File> nextEntry : theFilenameToFile.entrySet()) {
@ -206,79 +130,134 @@ public class TerminologyLoaderSvc {
}
}
private TermConcept getOrCreateConcept(TermCodeSystemVersion codeSystemVersion, Map<String, TermConcept> 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<String> allFilenames = Arrays.asList(SCT_FILE_DESCRIPTION, SCT_FILE_RELATIONSHIP, SCT_FILE_CONCEPT);
Map<String, File> filenameToFile = new HashMap<String, File>();
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<String, TermConcept> myRootConcepts;
private final Map<String, TermConcept> myCode2concept;
void processSnomedCtFiles(Map<String, File> filenameToFile, RequestDetails theRequestDetails) {
final TermCodeSystemVersion codeSystemVersion = new TermCodeSystemVersion();
final Map<String, TermConcept> id2concept = new HashMap<String, TermConcept>();
final Map<String, TermConcept> code2concept = new HashMap<String, TermConcept>();
final Set<String> validConceptIds = new HashSet<String>();
private SctHandlerRelationship(TermCodeSystemVersion theCodeSystemVersion, HashMap<String,TermConcept> theRootConcepts, Map<String, TermConcept> 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<String, TermConcept> rootConcepts = new HashMap<String, TermConcept>(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<String>());
}
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<String, File> files = new HashMap<String, File>();
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<String> myValidConceptIds;
public SctHandlerConcept(Set<String> theValidConceptIds) {
myValidConceptIds = theValidConceptIds;
}
@Override
public void accept(CSVRecord theRecord) {
Set<String> ignoredTypes = new HashSet<String>();
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<String, TermConcept> 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<String, TermConcept> myCode2concept;
private final Map<String, TermConcept> myId2concept;
private final TermCodeSystemVersion myCodeSystemVersion;
private final Map<String, TermConcept> myId2concept;
private Set<String> myValidConceptIds;
private SctHandlerDescription(Set<String> theValidConceptIds, Map<String, TermConcept> theCode2concept, Map<String, TermConcept> 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<String, TermConcept> myCode2concept;
private final TermCodeSystemVersion myCodeSystemVersion;
private final Map<String, TermConcept> myRootConcepts;
private Set<String> myValidConceptIds;
public SctHandlerConcept(Set<String> theValidConceptIds) {
myValidConceptIds = theValidConceptIds;
private SctHandlerRelationship(TermCodeSystemVersion theCodeSystemVersion, HashMap<String, TermConcept> theRootConcepts, Map<String, TermConcept> theCode2concept) {
myCodeSystemVersion = theCodeSystemVersion;
myRootConcepts = theRootConcepts;
myCode2concept = theCode2concept;
}
@Override
public void accept(CSVRecord theRecord) {
String id = theRecord.get("id");
Set<String> ignoredTypes = new HashSet<String>();
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<String, TermConcept> 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<String, File> files = new HashMap<String, File>();
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();
}
}
}

View File

@ -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

View File

@ -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());

View File

@ -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);
}
}

View File

@ -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<TermConcept> concepts;
Set<String> 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<TermConcept> concepts;
Set<String> 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/"));