Merge remote-tracking branch 'remotes/origin/feature_20200830_term_multi_version_support' into feature_2020_09_02_term_multi_version_support

This commit is contained in:
ianmarshall 2020-09-15 12:35:31 -04:00
commit 7b15f85fa1
46 changed files with 2007 additions and 157 deletions

View File

@ -183,6 +183,24 @@ public interface IValidationSupport {
return null; return null;
} }
/**
* Validates that the given code exists and if possible returns a display
* name. This method is called to check codes which are found in "example"
* binding fields (e.g. <code>Observation.code</code> in the default profile.
*
* @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to
* other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter.
* @param theOptions Provides options controlling the validation
* @param theCodeSystem The code system, e.g. "<code>http://loinc.org</code>"
* @param theCode The code, e.g. "<code>1234-5</code>"
* @param theDisplay The display name, if it should also be validated
* @param theSystemVersion The code system version.
* @return Returns a validation result object
*/
default CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl, String theSystemVersion) {
return null;
}
/** /**
* Validates that the given code exists and if possible returns a display * Validates that the given code exists and if possible returns a display
* name. This method is called to check codes which are found in "example" * name. This method is called to check codes which are found in "example"
@ -212,6 +230,19 @@ public interface IValidationSupport {
return null; return null;
} }
/**
* Look up a code using the system, system version and code value
*
* @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to
* other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter.
* @param theSystem The CodeSystem URL
* @param theCode The code
* @param theSystemVersion The CodeSystem version
*/
default LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theSystemVersion) {
return null;
}
/** /**
* Returns <code>true</code> if the given valueset can be validated by the given * Returns <code>true</code> if the given valueset can be validated by the given
* validation support module * validation support module

View File

@ -143,6 +143,7 @@ ca.uhn.fhir.jpa.binstore.BinaryAccessProvider.unknownType=Content in resource of
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateCodeSystemUrl=Can not create multiple CodeSystem resources with CodeSystem.url "{0}", already have one with resource ID: {1} ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateCodeSystemUrl=Can not create multiple CodeSystem resources with CodeSystem.url "{0}", already have one with resource ID: {1}
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateCodeSystemUrlAndVersion=Can not create multiple CodeSystem resources with CodeSystem.url "{0}" and CodeSystem.version "{1}", already have one with resource ID: {2}
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateConceptMapUrl=Can not create multiple ConceptMap resources with ConceptMap.url "{0}", already have one with resource ID: {1} ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateConceptMapUrl=Can not create multiple ConceptMap resources with ConceptMap.url "{0}", already have one with resource ID: {1}
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateConceptMapUrlAndVersion=Can not create multiple ConceptMap resources with ConceptMap.url "{0}", ConceptMap.version "{1}", already have one with resource ID: {2} ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateConceptMapUrlAndVersion=Can not create multiple ConceptMap resources with ConceptMap.url "{0}", ConceptMap.version "{1}", already have one with resource ID: {2}
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateValueSetUrl=Can not create multiple ValueSet resources with ValueSet.url "{0}", already have one with resource ID: {1} ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateValueSetUrl=Can not create multiple ValueSet resources with ValueSet.url "{0}", already have one with resource ID: {1}

View File

@ -42,6 +42,11 @@ public interface IFhirResourceDaoCodeSystem<T extends IBaseResource, CD, CC> ext
SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, CD theCodingA, CD theCodingB, RequestDetails theRequestDetails); SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, CD theCodingA, CD theCodingB, RequestDetails theRequestDetails);
@Nonnull
IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, CD theCoding, IPrimitiveType<String> theVersion, RequestDetails theRequestDetails);
SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, CD theCodingA, CD theCodingB, IPrimitiveType<String> theVersion, RequestDetails theRequestDetails);
class SubsumesResult { class SubsumesResult {
private final ConceptSubsumptionOutcome myOutcome; private final ConceptSubsumptionOutcome myOutcome;

View File

@ -43,7 +43,6 @@ import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.UriParam; import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.apache.commons.codec.binary.StringUtils;
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport; import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
@ -235,6 +234,11 @@ public class FhirResourceDaoValueSetDstu2 extends BaseHapiFhirResourceDao<ValueS
return null; return null;
} }
@Override
public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, CodingDt theCoding, IPrimitiveType<String> theVersion, RequestDetails theRequestDetails) {
throw new UnsupportedOperationException();
}
@Override @Override
public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, CodingDt theCoding, RequestDetails theRequest) { public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, CodingDt theCoding, RequestDetails theRequest) {
boolean haveCoding = theCoding != null && isNotBlank(theCoding.getSystem()) && isNotBlank(theCoding.getCode()); boolean haveCoding = theCoding != null && isNotBlank(theCoding.getSystem()) && isNotBlank(theCoding.getCode());
@ -280,6 +284,11 @@ public class FhirResourceDaoValueSetDstu2 extends BaseHapiFhirResourceDao<ValueS
return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB); return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB);
} }
@Override
public SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, CodingDt theCodingA, CodingDt theCodingB, IPrimitiveType<String> theVersion, RequestDetails theRequestDetails) {
throw new UnsupportedOperationException();
}
@Override @Override
@PostConstruct @PostConstruct
public void postConstruct() { public void postConstruct() {

View File

@ -0,0 +1,28 @@
package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao;
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemVersionDao;
import ca.uhn.fhir.jpa.entity.TermCodeSystem;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class HapiFhirResourceDaoCodeSystemUtil {
static public void deleteCodeSystemEntities(ITermCodeSystemDao theCsDao, ITermCodeSystemVersionDao theCsvDao,
ITermDeferredStorageSvc theTermDeferredStorageSvc, String theCodeSystemUrl,
String theCodeSystemVersion) {
if (isNotBlank(theCodeSystemUrl)) {
TermCodeSystem persCs = theCsDao.findByCodeSystemUri(theCodeSystemUrl);
if (persCs != null) {
if (theCodeSystemVersion != null) {
TermCodeSystemVersion persCsVersion = theCsvDao.findByCodeSystemPidAndVersion(persCs.getPid(), theCodeSystemVersion);
theTermDeferredStorageSvc.deleteCodeSystemVersion(persCsVersion);
} else {
theTermDeferredStorageSvc.deleteCodeSystem(persCs);
}
}
}
}
}

View File

@ -35,9 +35,19 @@ public interface ITermCodeSystemVersionDao extends JpaRepository<TermCodeSystemV
@Query("DELETE FROM TermCodeSystemVersion csv WHERE csv.myCodeSystem = :cs") @Query("DELETE FROM TermCodeSystemVersion csv WHERE csv.myCodeSystem = :cs")
void deleteForCodeSystem(@Param("cs") TermCodeSystem theCodeSystem); void deleteForCodeSystem(@Param("cs") TermCodeSystem theCodeSystem);
@Modifying
@Query("DELETE FROM TermCodeSystemVersion csv WHERE csv.myId = :pid")
void delete(@Param("pid") Long codesystemversion_pid);
@Query("SELECT cs FROM TermCodeSystemVersion cs WHERE cs.myCodeSystemPid = :codesystem_pid") @Query("SELECT cs FROM TermCodeSystemVersion cs WHERE cs.myCodeSystemPid = :codesystem_pid")
List<TermCodeSystemVersion> findByCodeSystemPid(@Param("codesystem_pid") Long theCodeSystemPid); List<TermCodeSystemVersion> findByCodeSystemPid(@Param("codesystem_pid") Long theCodeSystemPid);
@Query("SELECT cs FROM TermCodeSystemVersion cs WHERE cs.myCodeSystemPid = :codesystem_pid AND cs.myCodeSystemVersionId = :codesystem_version_id")
TermCodeSystemVersion findByCodeSystemPidAndVersion(@Param("codesystem_pid") Long theCodeSystemPid, @Param("codesystem_version_id") String theCodeSystemVersionId);
@Query("SELECT cs FROM TermCodeSystemVersion cs WHERE cs.myCodeSystemPid = :codesystem_pid AND cs.myCodeSystemVersionId IS NULL")
TermCodeSystemVersion findByCodeSystemPidVersionIsNull(@Param("codesystem_pid") Long theCodeSystemPid);
@Query("SELECT cs FROM TermCodeSystemVersion cs WHERE cs.myResourcePid = :resource_id") @Query("SELECT cs FROM TermCodeSystemVersion cs WHERE cs.myResourcePid = :resource_id")
List<TermCodeSystemVersion> findByCodeSystemResourcePid(@Param("resource_id") Long theCodeSystemResourcePid); List<TermCodeSystemVersion> findByCodeSystemResourcePid(@Param("resource_id") Long theCodeSystemResourcePid);

View File

@ -25,7 +25,9 @@ import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.HapiFhirResourceDaoCodeSystemUtil;
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao;
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemVersionDao;
import ca.uhn.fhir.jpa.entity.TermCodeSystem; import ca.uhn.fhir.jpa.entity.TermCodeSystem;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
@ -67,6 +69,8 @@ public class FhirResourceDaoCodeSystemDstu3 extends BaseHapiFhirResourceDao<Code
@Autowired @Autowired
private ITermCodeSystemDao myCsDao; private ITermCodeSystemDao myCsDao;
@Autowired @Autowired
private ITermCodeSystemVersionDao myCsvDao;
@Autowired
private IValidationSupport myValidationSupport; private IValidationSupport myValidationSupport;
@Autowired @Autowired
private FhirContext myFhirContext; private FhirContext myFhirContext;
@ -89,6 +93,12 @@ public class FhirResourceDaoCodeSystemDstu3 extends BaseHapiFhirResourceDao<Code
@Nonnull @Nonnull
@Override @Override
public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, Coding theCoding, RequestDetails theRequestDetails) { public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, Coding theCoding, RequestDetails theRequestDetails) {
return lookupCode(theCode, theSystem, theCoding, null, theRequestDetails);
}
@Nonnull
@Override
public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, Coding theCoding, IPrimitiveType<String> theVersion, RequestDetails theRequestDetails) {
boolean haveCoding = theCoding != null && isNotBlank(theCoding.getSystem()) && isNotBlank(theCoding.getCode()); boolean haveCoding = theCoding != null && isNotBlank(theCoding.getSystem()) && isNotBlank(theCoding.getCode());
boolean haveCode = theCode != null && theCode.isEmpty() == false; boolean haveCode = theCode != null && theCode.isEmpty() == false;
boolean haveSystem = theSystem != null && theSystem.isEmpty() == false; boolean haveSystem = theSystem != null && theSystem.isEmpty() == false;
@ -102,19 +112,24 @@ public class FhirResourceDaoCodeSystemDstu3 extends BaseHapiFhirResourceDao<Code
String code; String code;
String system; String system;
String codeSystemVersion = null;
if (haveCoding) { if (haveCoding) {
code = theCoding.getCode(); code = theCoding.getCode();
system = theCoding.getSystem(); system = theCoding.getSystem();
codeSystemVersion = theCoding.getVersion();
} else { } else {
code = theCode.getValue(); code = theCode.getValue();
system = theSystem.getValue(); system = theSystem.getValue();
if (theVersion != null) {
codeSystemVersion = theVersion.getValue();
}
} }
ourLog.debug("Looking up {} / {}", system, code); ourLog.debug("Looking up {} / {}, version {}", system, code, codeSystemVersion);
if (myValidationSupport.isCodeSystemSupported(new ValidationSupportContext(myValidationSupport), system)) { if (myValidationSupport.isCodeSystemSupported(new ValidationSupportContext(myValidationSupport), system)) {
ourLog.debug("Code system {} is supported", system); ourLog.debug("Code system {} is supported", system);
IValidationSupport.LookupCodeResult result = myValidationSupport.lookupCode(new ValidationSupportContext(myValidationSupport), system, code); IValidationSupport.LookupCodeResult result = myValidationSupport.lookupCode(new ValidationSupportContext(myValidationSupport), system, code, codeSystemVersion);
if (result != null) { if (result != null) {
return result; return result;
} }
@ -124,6 +139,13 @@ public class FhirResourceDaoCodeSystemDstu3 extends BaseHapiFhirResourceDao<Code
return IValidationSupport.LookupCodeResult.notFound(system, code); return IValidationSupport.LookupCodeResult.notFound(system, code);
} }
@Override
public SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, Coding theCodingA, Coding theCodingB, IPrimitiveType<String> theVersion, RequestDetails theRequestDetails) {
String codingBVersion = theCodingB != null ? theCodingB.getVersion() : null;
String codingAVersion = theCodingA != null ? theCodingA.getVersion() : null;
return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theVersion, codingAVersion, codingBVersion);
}
@Override @Override
public SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, Coding theCodingA, Coding theCodingB, RequestDetails theRequestDetails) { public SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, Coding theCodingA, Coding theCodingB, RequestDetails theRequestDetails) {
return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB); return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB);
@ -133,13 +155,9 @@ public class FhirResourceDaoCodeSystemDstu3 extends BaseHapiFhirResourceDao<Code
protected void preDelete(CodeSystem theResourceToDelete, ResourceTable theEntityToDelete) { protected void preDelete(CodeSystem theResourceToDelete, ResourceTable theEntityToDelete) {
super.preDelete(theResourceToDelete, theEntityToDelete); super.preDelete(theResourceToDelete, theEntityToDelete);
String codeSystemUrl = theResourceToDelete.getUrl(); HapiFhirResourceDaoCodeSystemUtil.deleteCodeSystemEntities(myCsDao, myCsvDao, myTermDeferredStorageSvc, theResourceToDelete.getUrl(),
if (isNotBlank(codeSystemUrl)) { theResourceToDelete.getVersion());
TermCodeSystem persCs = myCsDao.findByCodeSystemUri(codeSystemUrl);
if (persCs != null) {
myTermDeferredStorageSvc.deleteCodeSystem(persCs);
}
}
} }
@Override @Override

View File

@ -25,7 +25,9 @@ import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.HapiFhirResourceDaoCodeSystemUtil;
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao;
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemVersionDao;
import ca.uhn.fhir.jpa.entity.TermCodeSystem; import ca.uhn.fhir.jpa.entity.TermCodeSystem;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
@ -61,6 +63,8 @@ public class FhirResourceDaoCodeSystemR4 extends BaseHapiFhirResourceDao<CodeSys
@Autowired @Autowired
private ITermCodeSystemDao myCsDao; private ITermCodeSystemDao myCsDao;
@Autowired @Autowired
private ITermCodeSystemVersionDao myCsvDao;
@Autowired
private IValidationSupport myValidationSupport; private IValidationSupport myValidationSupport;
@Autowired @Autowired
protected ITermCodeSystemStorageSvc myTerminologyCodeSystemStorageSvc; protected ITermCodeSystemStorageSvc myTerminologyCodeSystemStorageSvc;
@ -84,6 +88,12 @@ public class FhirResourceDaoCodeSystemR4 extends BaseHapiFhirResourceDao<CodeSys
@Nonnull @Nonnull
@Override @Override
public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, Coding theCoding, RequestDetails theRequestDetails) { public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, Coding theCoding, RequestDetails theRequestDetails) {
return lookupCode(theCode, theSystem, theCoding, null, theRequestDetails);
}
@Nonnull
@Override
public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, Coding theCoding, IPrimitiveType<String> theVersion, RequestDetails theRequestDetails) {
boolean haveCoding = theCoding != null && isNotBlank(theCoding.getSystem()) && isNotBlank(theCoding.getCode()); boolean haveCoding = theCoding != null && isNotBlank(theCoding.getSystem()) && isNotBlank(theCoding.getCode());
boolean haveCode = theCode != null && theCode.isEmpty() == false; boolean haveCode = theCode != null && theCode.isEmpty() == false;
boolean haveSystem = theSystem != null && theSystem.isEmpty() == false; boolean haveSystem = theSystem != null && theSystem.isEmpty() == false;
@ -97,20 +107,25 @@ public class FhirResourceDaoCodeSystemR4 extends BaseHapiFhirResourceDao<CodeSys
String code; String code;
String system; String system;
String codeSystemVersion = null;
if (haveCoding) { if (haveCoding) {
code = theCoding.getCode(); code = theCoding.getCode();
system = theCoding.getSystem(); system = theCoding.getSystem();
codeSystemVersion = theCoding.getVersion();
} else { } else {
code = theCode.getValue(); code = theCode.getValue();
system = theSystem.getValue(); system = theSystem.getValue();
if (theVersion != null) {
codeSystemVersion = theVersion.getValue();
}
} }
ourLog.debug("Looking up {} / {}", system, code); ourLog.debug("Looking up {} / {}, version {}", system, code, codeSystemVersion);
if (myValidationSupport.isCodeSystemSupported(new ValidationSupportContext(myValidationSupport), system)) { if (myValidationSupport.isCodeSystemSupported(new ValidationSupportContext(myValidationSupport), system)) {
ourLog.debug("Code system {} is supported", system); ourLog.debug("Code system {} is supported", system);
IValidationSupport.LookupCodeResult retVal = myValidationSupport.lookupCode(new ValidationSupportContext(myValidationSupport), system, code); IValidationSupport.LookupCodeResult retVal = myValidationSupport.lookupCode(new ValidationSupportContext(myValidationSupport), system, code, codeSystemVersion);
if (retVal != null) { if (retVal != null) {
return retVal; return retVal;
} }
@ -122,6 +137,13 @@ public class FhirResourceDaoCodeSystemR4 extends BaseHapiFhirResourceDao<CodeSys
} }
@Override
public SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, Coding theCodingA, Coding theCodingB, IPrimitiveType<String> theVersion, RequestDetails theRequestDetails) {
String codingBVersion = theCodingB != null ? theCodingB.getVersion() : null;
String codingAVersion = theCodingA != null ? theCodingA.getVersion() : null;
return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theVersion, codingAVersion, codingBVersion);
}
@Override @Override
public SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, Coding theCodingA, Coding theCodingB, RequestDetails theRequestDetails) { public SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, Coding theCodingA, Coding theCodingB, RequestDetails theRequestDetails) {
return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB); return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB);
@ -131,13 +153,9 @@ public class FhirResourceDaoCodeSystemR4 extends BaseHapiFhirResourceDao<CodeSys
protected void preDelete(CodeSystem theResourceToDelete, ResourceTable theEntityToDelete) { protected void preDelete(CodeSystem theResourceToDelete, ResourceTable theEntityToDelete) {
super.preDelete(theResourceToDelete, theEntityToDelete); super.preDelete(theResourceToDelete, theEntityToDelete);
String codeSystemUrl = theResourceToDelete.getUrl(); HapiFhirResourceDaoCodeSystemUtil.deleteCodeSystemEntities(myCsDao, myCsvDao, myTermDeferredStorageSvc, theResourceToDelete.getUrl(),
if (isNotBlank(codeSystemUrl)) { theResourceToDelete.getVersion());
TermCodeSystem persCs = myCsDao.findByCodeSystemUri(codeSystemUrl);
if (persCs != null) {
myTermDeferredStorageSvc.deleteCodeSystem(persCs);
}
}
} }
@Override @Override

View File

@ -113,14 +113,17 @@ public class FhirResourceDaoValueSetR4 extends BaseHapiFhirResourceDao<ValueSet>
ValueSet source = new ValueSet(); ValueSet source = new ValueSet();
source.setUrl(theUri); source.setUrl(theUri);
source.getCompose().addInclude().addValueSet(theUri); // source.getCompose().addInclude().addValueSet(theUri);
if (isNotBlank(theFilter)) { if (isNotBlank(theFilter)) {
ConceptSetComponent include = source.getCompose().addInclude(); // ConceptSetComponent include = source.getCompose().addInclude();
ConceptSetFilterComponent filter = include.addFilter(); // ConceptSetFilterComponent filter = include.addFilter();
ConceptSetFilterComponent filter = source.getCompose().addInclude().addValueSet(theUri).addFilter();
filter.setProperty("display"); filter.setProperty("display");
filter.setOp(FilterOperator.EQUAL); filter.setOp(FilterOperator.EQUAL);
filter.setValue(theFilter); filter.setValue(theFilter);
} else {
source.getCompose().addInclude().addValueSet(theUri);
} }
ValueSet retVal = doExpand(source); ValueSet retVal = doExpand(source);
@ -148,14 +151,17 @@ public class FhirResourceDaoValueSetR4 extends BaseHapiFhirResourceDao<ValueSet>
ValueSet source = new ValueSet(); ValueSet source = new ValueSet();
source.setUrl(theUri); source.setUrl(theUri);
source.getCompose().addInclude().addValueSet(theUri); // source.getCompose().addInclude().addValueSet(theUri);
if (isNotBlank(theFilter)) { if (isNotBlank(theFilter)) {
ConceptSetComponent include = source.getCompose().addInclude(); // ConceptSetComponent include = source.getCompose().addInclude();
ConceptSetFilterComponent filter = include.addFilter(); // ConceptSetFilterComponent filter = include.addFilter();
ConceptSetFilterComponent filter = source.getCompose().addInclude().addValueSet(theUri).addFilter();
filter.setProperty("display"); filter.setProperty("display");
filter.setOp(FilterOperator.EQUAL); filter.setOp(FilterOperator.EQUAL);
filter.setValue(theFilter); filter.setValue(theFilter);
} else {
source.getCompose().addInclude().addValueSet(theUri);
} }
ValueSet retVal = doExpand(source, theOffset, theCount); ValueSet retVal = doExpand(source, theOffset, theCount);

View File

@ -25,7 +25,9 @@ import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.HapiFhirResourceDaoCodeSystemUtil;
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao;
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemVersionDao;
import ca.uhn.fhir.jpa.dao.index.IdHelperService; import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.entity.TermCodeSystem; import ca.uhn.fhir.jpa.entity.TermCodeSystem;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
@ -65,6 +67,8 @@ public class FhirResourceDaoCodeSystemR5 extends BaseHapiFhirResourceDao<CodeSys
@Autowired @Autowired
private ITermCodeSystemDao myCsDao; private ITermCodeSystemDao myCsDao;
@Autowired @Autowired
private ITermCodeSystemVersionDao myCsvDao;
@Autowired
private IValidationSupport myValidationSupport; private IValidationSupport myValidationSupport;
@Autowired @Autowired
private FhirContext myFhirContext; private FhirContext myFhirContext;
@ -86,6 +90,12 @@ public class FhirResourceDaoCodeSystemR5 extends BaseHapiFhirResourceDao<CodeSys
@Nonnull @Nonnull
@Override @Override
public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, Coding theCoding, RequestDetails theRequestDetails) { public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, Coding theCoding, RequestDetails theRequestDetails) {
return lookupCode(theCode, theSystem, theCoding, null, theRequestDetails);
}
@Nonnull
@Override
public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, Coding theCoding, IPrimitiveType<String> theVersion, RequestDetails theRequestDetails) {
boolean haveCoding = theCoding != null && isNotBlank(theCoding.getSystem()) && isNotBlank(theCoding.getCode()); boolean haveCoding = theCoding != null && isNotBlank(theCoding.getSystem()) && isNotBlank(theCoding.getCode());
boolean haveCode = theCode != null && theCode.isEmpty() == false; boolean haveCode = theCode != null && theCode.isEmpty() == false;
boolean haveSystem = theSystem != null && theSystem.isEmpty() == false; boolean haveSystem = theSystem != null && theSystem.isEmpty() == false;
@ -99,20 +109,25 @@ public class FhirResourceDaoCodeSystemR5 extends BaseHapiFhirResourceDao<CodeSys
String code; String code;
String system; String system;
String codeSystemVersion = null;
if (haveCoding) { if (haveCoding) {
code = theCoding.getCode(); code = theCoding.getCode();
system = theCoding.getSystem(); system = theCoding.getSystem();
codeSystemVersion = theCoding.getVersion();
} else { } else {
code = theCode.getValue(); code = theCode.getValue();
system = theSystem.getValue(); system = theSystem.getValue();
if (theVersion != null) {
codeSystemVersion = theVersion.getValue();
}
} }
ourLog.info("Looking up {} / {}", system, code); ourLog.debug("Looking up {} / {}, version {}", system, code, codeSystemVersion);
if (myValidationSupport.isCodeSystemSupported(new ValidationSupportContext(myValidationSupport), system)) { if (myValidationSupport.isCodeSystemSupported(new ValidationSupportContext(myValidationSupport), system)) {
ourLog.info("Code system {} is supported", system); ourLog.debug("Code system {} is supported", system);
IValidationSupport.LookupCodeResult retVal = myValidationSupport.lookupCode(new ValidationSupportContext(myValidationSupport), system, code); IValidationSupport.LookupCodeResult retVal = myValidationSupport.lookupCode(new ValidationSupportContext(myValidationSupport), system, code, codeSystemVersion);
if (retVal != null) { if (retVal != null) {
return retVal; return retVal;
} }
@ -124,6 +139,13 @@ public class FhirResourceDaoCodeSystemR5 extends BaseHapiFhirResourceDao<CodeSys
} }
@Override
public SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, Coding theCodingA, Coding theCodingB, IPrimitiveType<String> theVersion, RequestDetails theRequestDetails) {
String codingBVersion = theCodingB != null ? theCodingB.getVersion() : null;
String codingAVersion = theCodingA != null ? theCodingA.getVersion() : null;
return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theVersion, codingAVersion, codingBVersion);
}
@Override @Override
public SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, Coding theCodingA, Coding theCodingB, RequestDetails theRequestDetails) { public SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, Coding theCodingA, Coding theCodingB, RequestDetails theRequestDetails) {
return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB); return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB);
@ -133,13 +155,9 @@ public class FhirResourceDaoCodeSystemR5 extends BaseHapiFhirResourceDao<CodeSys
protected void preDelete(CodeSystem theResourceToDelete, ResourceTable theEntityToDelete) { protected void preDelete(CodeSystem theResourceToDelete, ResourceTable theEntityToDelete) {
super.preDelete(theResourceToDelete, theEntityToDelete); super.preDelete(theResourceToDelete, theEntityToDelete);
String codeSystemUrl = theResourceToDelete.getUrl(); HapiFhirResourceDaoCodeSystemUtil.deleteCodeSystemEntities(myCsDao, myCsvDao, myTermDeferredStorageSvc, theResourceToDelete.getUrl(),
if (isNotBlank(codeSystemUrl)) { theResourceToDelete.getVersion());
TermCodeSystem persCs = myCsDao.findByCodeSystemUri(codeSystemUrl);
if (persCs != null) {
myTermDeferredStorageSvc.deleteCodeSystem(persCs);
}
}
} }
@Override @Override

View File

@ -58,7 +58,7 @@ public class TermCodeSystem implements Serializable {
@Column(name = "PID") @Column(name = "PID")
private Long myPid; private Long myPid;
@OneToOne(fetch = FetchType.LAZY) @OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false, updatable = false, foreignKey = @ForeignKey(name = "FK_TRMCODESYSTEM_RES")) @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false, updatable = true, foreignKey = @ForeignKey(name = "FK_TRMCODESYSTEM_RES"))
private ResourceTable myResource; private ResourceTable myResource;
@Column(name = "RES_ID", insertable = false, updatable = false) @Column(name = "RES_ID", insertable = false, updatable = false)
private Long myResourcePid; private Long myResourcePid;

View File

@ -40,17 +40,21 @@ import javax.persistence.OneToMany;
import javax.persistence.OneToOne; import javax.persistence.OneToOne;
import javax.persistence.SequenceGenerator; import javax.persistence.SequenceGenerator;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import static org.apache.commons.lang3.StringUtils.length; import static org.apache.commons.lang3.StringUtils.length;
@Table(name = "TRM_CODESYSTEM_VER" @Table(name = "TRM_CODESYSTEM_VER",
// Note, we used to have a constraint named IDX_CSV_RESOURCEPID_AND_VER (don't reuse this) // Note, we used to have a constraint named IDX_CSV_RESOURCEPID_AND_VER (don't reuse this)
) uniqueConstraints = {
@UniqueConstraint(name = TermCodeSystemVersion.IDX_CODESYSTEM_AND_VER, columnNames = {"CODESYSTEM_PID", "CS_VERSION_ID"})
})
@Entity() @Entity()
public class TermCodeSystemVersion implements Serializable { public class TermCodeSystemVersion implements Serializable {
public static final String IDX_CODESYSTEM_AND_VER = "IDX_CODESYSTEM_AND_VER";
public static final int MAX_VERSION_LENGTH = 200; public static final int MAX_VERSION_LENGTH = 200;
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "myCodeSystem") @OneToMany(fetch = FetchType.LAZY, mappedBy = "myCodeSystem")

View File

@ -53,6 +53,7 @@ public class BaseJpaResourceProviderCodeSystemDstu3 extends JpaResourceProviderD
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode, @OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
@OperationParam(name = "system", min = 0, max = 1) UriType theSystem, @OperationParam(name = "system", min = 0, max = 1) UriType theSystem,
@OperationParam(name = "coding", min = 0, max = 1) Coding theCoding, @OperationParam(name = "coding", min = 0, max = 1) Coding theCoding,
@OperationParam(name="version", min=0, max=1) StringType theVersion,
@OperationParam(name = "property", min = 0, max = OperationParam.MAX_UNLIMITED) List<CodeType> theProperties, @OperationParam(name = "property", min = 0, max = OperationParam.MAX_UNLIMITED) List<CodeType> theProperties,
RequestDetails theRequestDetails RequestDetails theRequestDetails
) { ) {
@ -60,7 +61,7 @@ public class BaseJpaResourceProviderCodeSystemDstu3 extends JpaResourceProviderD
startRequest(theServletRequest); startRequest(theServletRequest);
try { try {
IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> dao = (IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept>) getDao(); IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> dao = (IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept>) getDao();
IValidationSupport.LookupCodeResult result = dao.lookupCode(theCode, theSystem, theCoding, theRequestDetails); IValidationSupport.LookupCodeResult result = dao.lookupCode(theCode, theSystem, theCoding, theVersion, theRequestDetails);
result.throwNotFoundIfAppropriate(); result.throwNotFoundIfAppropriate();
return (Parameters) result.toParameters(theRequestDetails.getFhirContext(), theProperties); return (Parameters) result.toParameters(theRequestDetails.getFhirContext(), theProperties);
} catch (FHIRException e) { } catch (FHIRException e) {
@ -84,13 +85,14 @@ public class BaseJpaResourceProviderCodeSystemDstu3 extends JpaResourceProviderD
@OperationParam(name = "system", min = 0, max = 1) UriType theSystem, @OperationParam(name = "system", min = 0, max = 1) UriType theSystem,
@OperationParam(name = "codingA", min = 0, max = 1) Coding theCodingA, @OperationParam(name = "codingA", min = 0, max = 1) Coding theCodingA,
@OperationParam(name = "codingB", min = 0, max = 1) Coding theCodingB, @OperationParam(name = "codingB", min = 0, max = 1) Coding theCodingB,
@OperationParam(name="version", min=0, max=1) StringType theVersion,
RequestDetails theRequestDetails RequestDetails theRequestDetails
) { ) {
startRequest(theServletRequest); startRequest(theServletRequest);
try { try {
IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> dao = (IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept>) getDao(); IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> dao = (IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept>) getDao();
IFhirResourceDaoCodeSystem.SubsumesResult result = dao.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theRequestDetails); IFhirResourceDaoCodeSystem.SubsumesResult result = dao.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theVersion, theRequestDetails);
return (Parameters) result.toParameters(theRequestDetails.getFhirContext()); return (Parameters) result.toParameters(theRequestDetails.getFhirContext());
} finally { } finally {
endRequest(theServletRequest); endRequest(theServletRequest);

View File

@ -55,6 +55,7 @@ public class BaseJpaResourceProviderCodeSystemR4 extends JpaResourceProviderR4<C
@OperationParam(name="code", min=0, max=1) CodeType theCode, @OperationParam(name="code", min=0, max=1) CodeType theCode,
@OperationParam(name="system", min=0, max=1) UriType theSystem, @OperationParam(name="system", min=0, max=1) UriType theSystem,
@OperationParam(name="coding", min=0, max=1) Coding theCoding, @OperationParam(name="coding", min=0, max=1) Coding theCoding,
@OperationParam(name="version", min=0, max=1) StringType theVersion,
@OperationParam(name = "property", min = 0, max = OperationParam.MAX_UNLIMITED) List<CodeType> theProperties, @OperationParam(name = "property", min = 0, max = OperationParam.MAX_UNLIMITED) List<CodeType> theProperties,
RequestDetails theRequestDetails RequestDetails theRequestDetails
) { ) {
@ -62,7 +63,7 @@ public class BaseJpaResourceProviderCodeSystemR4 extends JpaResourceProviderR4<C
startRequest(theServletRequest); startRequest(theServletRequest);
try { try {
IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> dao = (IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept>) getDao(); IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> dao = (IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept>) getDao();
IValidationSupport.LookupCodeResult result = dao.lookupCode(theCode, theSystem, theCoding, theRequestDetails); IValidationSupport.LookupCodeResult result = dao.lookupCode(theCode, theSystem, theCoding, theVersion, theRequestDetails);
result.throwNotFoundIfAppropriate(); result.throwNotFoundIfAppropriate();
return (Parameters) result.toParameters(theRequestDetails.getFhirContext(), theProperties); return (Parameters) result.toParameters(theRequestDetails.getFhirContext(), theProperties);
} finally { } finally {
@ -84,13 +85,14 @@ public class BaseJpaResourceProviderCodeSystemR4 extends JpaResourceProviderR4<C
@OperationParam(name="system", min=0, max=1) UriType theSystem, @OperationParam(name="system", min=0, max=1) UriType theSystem,
@OperationParam(name="codingA", min=0, max=1) Coding theCodingA, @OperationParam(name="codingA", min=0, max=1) Coding theCodingA,
@OperationParam(name="codingB", min=0, max=1) Coding theCodingB, @OperationParam(name="codingB", min=0, max=1) Coding theCodingB,
@OperationParam(name="version", min=0, max=1) StringType theVersion,
RequestDetails theRequestDetails RequestDetails theRequestDetails
) { ) {
startRequest(theServletRequest); startRequest(theServletRequest);
try { try {
IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> dao = (IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept>) getDao(); IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> dao = (IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept>) getDao();
IFhirResourceDaoCodeSystem.SubsumesResult result = dao.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theRequestDetails); IFhirResourceDaoCodeSystem.SubsumesResult result = dao.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theVersion, theRequestDetails);
return (Parameters) result.toParameters(theRequestDetails.getFhirContext()); return (Parameters) result.toParameters(theRequestDetails.getFhirContext());
} finally { } finally {
endRequest(theServletRequest); endRequest(theServletRequest);

View File

@ -55,6 +55,7 @@ public class BaseJpaResourceProviderCodeSystemR5 extends JpaResourceProviderR5<C
@OperationParam(name="code", min=0, max=1) CodeType theCode, @OperationParam(name="code", min=0, max=1) CodeType theCode,
@OperationParam(name="system", min=0, max=1) UriType theSystem, @OperationParam(name="system", min=0, max=1) UriType theSystem,
@OperationParam(name="coding", min=0, max=1) Coding theCoding, @OperationParam(name="coding", min=0, max=1) Coding theCoding,
@OperationParam(name="version", min=0, max=1) org.hl7.fhir.r4.model.StringType theVersion,
@OperationParam(name = "property", min = 0, max = OperationParam.MAX_UNLIMITED) List<CodeType> theProperties, @OperationParam(name = "property", min = 0, max = OperationParam.MAX_UNLIMITED) List<CodeType> theProperties,
RequestDetails theRequestDetails RequestDetails theRequestDetails
) { ) {
@ -62,7 +63,7 @@ public class BaseJpaResourceProviderCodeSystemR5 extends JpaResourceProviderR5<C
startRequest(theServletRequest); startRequest(theServletRequest);
try { try {
IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> dao = (IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept>) getDao(); IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> dao = (IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept>) getDao();
IValidationSupport.LookupCodeResult result = dao.lookupCode(theCode, theSystem, theCoding, theRequestDetails); IValidationSupport.LookupCodeResult result = dao.lookupCode(theCode, theSystem, theCoding, theVersion, theRequestDetails);
result.throwNotFoundIfAppropriate(); result.throwNotFoundIfAppropriate();
return (Parameters) result.toParameters(theRequestDetails.getFhirContext(), theProperties); return (Parameters) result.toParameters(theRequestDetails.getFhirContext(), theProperties);
} finally { } finally {
@ -84,13 +85,14 @@ public class BaseJpaResourceProviderCodeSystemR5 extends JpaResourceProviderR5<C
@OperationParam(name="system", min=0, max=1) UriType theSystem, @OperationParam(name="system", min=0, max=1) UriType theSystem,
@OperationParam(name="codingA", min=0, max=1) Coding theCodingA, @OperationParam(name="codingA", min=0, max=1) Coding theCodingA,
@OperationParam(name="codingB", min=0, max=1) Coding theCodingB, @OperationParam(name="codingB", min=0, max=1) Coding theCodingB,
@OperationParam(name="version", min=0, max=1) StringType theVersion,
RequestDetails theRequestDetails RequestDetails theRequestDetails
) { ) {
startRequest(theServletRequest); startRequest(theServletRequest);
try { try {
IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> dao = (IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept>) getDao(); IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> dao = (IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept>) getDao();
IFhirResourceDaoCodeSystem.SubsumesResult result = dao.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theRequestDetails); IFhirResourceDaoCodeSystem.SubsumesResult result = dao.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theVersion, theRequestDetails);
return (Parameters) result.toParameters(theRequestDetails.getFhirContext()); return (Parameters) result.toParameters(theRequestDetails.getFhirContext());
} finally { } finally {
endRequest(theServletRequest); endRequest(theServletRequest);

View File

@ -22,7 +22,6 @@ package ca.uhn.fhir.jpa.term;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
@ -126,7 +125,6 @@ import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r4.model.codesystems.ConceptSubsumptionOutcome; import org.hl7.fhir.r4.model.codesystems.ConceptSubsumptionOutcome;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.quartz.JobExecutionContext; import org.quartz.JobExecutionContext;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
@ -175,7 +173,6 @@ import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNoneBlank; import static org.apache.commons.lang3.StringUtils.isNoneBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
public abstract class BaseTermReadSvcImpl implements ITermReadSvc { public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
public static final int DEFAULT_FETCH_SIZE = 250; public static final int DEFAULT_FETCH_SIZE = 250;
@ -238,14 +235,12 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
private ITermCodeSystemStorageSvc myConceptStorageSvc; private ITermCodeSystemStorageSvc myConceptStorageSvc;
@Autowired @Autowired
private ApplicationContext myApplicationContext; private ApplicationContext myApplicationContext;
@Autowired
private DefaultProfileValidationSupport myDefaultProfileValidationSupport;
private volatile IValidationSupport myJpaValidationSupport; private volatile IValidationSupport myJpaValidationSupport;
private volatile IValidationSupport myValidationSupport; private volatile IValidationSupport myValidationSupport;
@Override @Override
public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) { public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) {
TermCodeSystemVersion cs = getCurrentCodeSystemVersion(theSystem); TermCodeSystemVersion cs = getCurrentCodeSystemVersion(theSystem,null);
return cs != null; return cs != null;
} }
@ -932,19 +927,19 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
case "parent": case "parent":
case "child": case "child":
isCodeSystemLoingOrThrowInvalidRequestException(theSystem, theFilter.getProperty()); isCodeSystemLoingOrThrowInvalidRequestException(theSystem, theFilter.getProperty());
handleFilterLoincParentChild(theQb, theBool, theFilter); handleFilterLoincParentChild(theBool, theFilter);
break; break;
case "ancestor": case "ancestor":
isCodeSystemLoingOrThrowInvalidRequestException(theSystem, theFilter.getProperty()); isCodeSystemLoingOrThrowInvalidRequestException(theSystem, theFilter.getProperty());
handleFilterLoincAncestor(theSystem, theQb, theBool, theFilter); handleFilterLoincAncestor(theSystem, theBool, theFilter);
break; break;
case "descendant": case "descendant":
isCodeSystemLoingOrThrowInvalidRequestException(theSystem, theFilter.getProperty()); isCodeSystemLoingOrThrowInvalidRequestException(theSystem, theFilter.getProperty());
handleFilterLoincDescendant(theSystem, theQb, theBool, theFilter); handleFilterLoincDescendant(theSystem, theBool, theFilter);
break; break;
case "copyright": case "copyright":
isCodeSystemLoingOrThrowInvalidRequestException(theSystem, theFilter.getProperty()); isCodeSystemLoingOrThrowInvalidRequestException(theSystem, theFilter.getProperty());
handleFilterLoincCopyright(theQb, theBool, theFilter); handleFilterLoincCopyright(theBool, theFilter);
break; break;
default: default:
handleFilterRegex(theBool, theFilter); handleFilterRegex(theBool, theFilter);
@ -952,11 +947,10 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
} }
} }
private boolean isCodeSystemLoingOrThrowInvalidRequestException(String theSystem, String theProperty) { private void isCodeSystemLoingOrThrowInvalidRequestException(String theSystem, String theProperty) {
if (!isCodeSystemLoinc(theSystem)) { if (!isCodeSystemLoinc(theSystem)) {
throw new InvalidRequestException("Invalid filter, property " + theProperty + " is LOINC-specific and cannot be used with system: " + theSystem); throw new InvalidRequestException("Invalid filter, property " + theProperty + " is LOINC-specific and cannot be used with system: " + theSystem);
} }
return true;
} }
private boolean isCodeSystemLoinc(String theSystem) { private boolean isCodeSystemLoinc(String theSystem) {
@ -1004,7 +998,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
} }
@SuppressWarnings("EnumSwitchStatementWhichMissesCases") @SuppressWarnings("EnumSwitchStatementWhichMissesCases")
private void handleFilterLoincParentChild(QueryBuilder theQb, BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) { private void handleFilterLoincParentChild(BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) {
switch (theFilter.getOp()) { switch (theFilter.getOp()) {
case EQUAL: case EQUAL:
addLoincFilterParentChildEqual(theBool, theFilter.getProperty(), theFilter.getValue()); addLoincFilterParentChildEqual(theBool, theFilter.getProperty(), theFilter.getValue());
@ -1037,29 +1031,29 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
} }
@SuppressWarnings("EnumSwitchStatementWhichMissesCases") @SuppressWarnings("EnumSwitchStatementWhichMissesCases")
private void handleFilterLoincAncestor(String theSystem, QueryBuilder theQb, BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) { private void handleFilterLoincAncestor(String theSystem, BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) {
switch (theFilter.getOp()) { switch (theFilter.getOp()) {
case EQUAL: case EQUAL:
addLoincFilterAncestorEqual(theSystem, theQb, theBool, theFilter); addLoincFilterAncestorEqual(theSystem, theBool, theFilter);
break; break;
case IN: case IN:
addLoincFilterAncestorIn(theSystem, theQb, theBool, theFilter); addLoincFilterAncestorIn(theSystem, theBool, theFilter);
break; break;
default: default:
throw new InvalidRequestException("Don't know how to handle op=" + theFilter.getOp() + " on property " + theFilter.getProperty()); throw new InvalidRequestException("Don't know how to handle op=" + theFilter.getOp() + " on property " + theFilter.getProperty());
} }
} }
private void addLoincFilterAncestorEqual(String theSystem, QueryBuilder theQb, BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) { private void addLoincFilterAncestorEqual(String theSystem, BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) {
addLoincFilterAncestorEqual(theSystem, theQb, theBool, theFilter.getProperty(), theFilter.getValue()); addLoincFilterAncestorEqual(theSystem, theBool, theFilter.getProperty(), theFilter.getValue());
} }
private void addLoincFilterAncestorEqual(String theSystem, QueryBuilder theQb, BooleanJunction<?> theBool, String theProperty, String theValue) { private void addLoincFilterAncestorEqual(String theSystem, BooleanJunction<?> theBool, String theProperty, String theValue) {
List<Term> terms = getAncestorTerms(theSystem, theProperty, theValue); List<Term> terms = getAncestorTerms(theSystem, theProperty, theValue);
theBool.must(new TermsQuery(terms)); theBool.must(new TermsQuery(terms));
} }
private void addLoincFilterAncestorIn(String theSystem, QueryBuilder theQb, BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) { private void addLoincFilterAncestorIn(String theSystem, BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) {
String[] values = theFilter.getValue().split(","); String[] values = theFilter.getValue().split(",");
List<Term> terms = new ArrayList<>(); List<Term> terms = new ArrayList<>();
for (String value : values) { for (String value : values) {
@ -1081,13 +1075,13 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
} }
@SuppressWarnings("EnumSwitchStatementWhichMissesCases") @SuppressWarnings("EnumSwitchStatementWhichMissesCases")
private void handleFilterLoincDescendant(String theSystem, QueryBuilder theQb, BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) { private void handleFilterLoincDescendant(String theSystem, BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) {
switch (theFilter.getOp()) { switch (theFilter.getOp()) {
case EQUAL: case EQUAL:
addLoincFilterDescendantEqual(theSystem, theBool, theFilter); addLoincFilterDescendantEqual(theSystem, theBool, theFilter);
break; break;
case IN: case IN:
addLoincFilterDescendantIn(theSystem, theQb, theBool, theFilter); addLoincFilterDescendantIn(theSystem, theBool, theFilter);
break; break;
default: default:
throw new InvalidRequestException("Don't know how to handle op=" + theFilter.getOp() + " on property " + theFilter.getProperty()); throw new InvalidRequestException("Don't know how to handle op=" + theFilter.getOp() + " on property " + theFilter.getProperty());
@ -1103,7 +1097,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
theBool.must(new TermsQuery(terms)); theBool.must(new TermsQuery(terms));
} }
private void addLoincFilterDescendantIn(String theSystem, QueryBuilder theQb, BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) { private void addLoincFilterDescendantIn(String theSystem, BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) {
String[] values = theFilter.getValue().split(","); String[] values = theFilter.getValue().split(",");
List<Term> terms = new ArrayList<>(); List<Term> terms = new ArrayList<>();
for (String value : values) { for (String value : values) {
@ -1127,7 +1121,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
return retVal; return retVal;
} }
private void handleFilterLoincCopyright(QueryBuilder theQb, BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) { private void handleFilterLoincCopyright(BooleanJunction<?> theBool, ValueSet.ConceptSetFilterComponent theFilter) {
if (theFilter.getOp() == ValueSet.FilterOperator.EQUAL) { if (theFilter.getOp() == ValueSet.FilterOperator.EQUAL) {
String copyrightFilterValue = defaultString(theFilter.getValue()).toLowerCase(); String copyrightFilterValue = defaultString(theFilter.getValue()).toLowerCase();
@ -1364,6 +1358,11 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
@Override @Override
public Optional<TermConcept> findCode(String theCodeSystem, String theCode) { public Optional<TermConcept> findCode(String theCodeSystem, String theCode) {
return findCode(theCodeSystem, theCode, null);
}
@Override
public Optional<TermConcept> findCode(String theCodeSystem, String theCode, String theVersion) {
/* /*
* Loading concepts without a transaction causes issues later on some * Loading concepts without a transaction causes issues later on some
* platforms (e.g. PSQL) so this transactiontemplate is here to make * platforms (e.g. PSQL) so this transactiontemplate is here to make
@ -1372,21 +1371,29 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_MANDATORY); txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_MANDATORY);
return txTemplate.execute(t -> { return txTemplate.execute(t -> {
TermCodeSystemVersion csv = getCurrentCodeSystemVersion(theCodeSystem); TermCodeSystemVersion csv = getCurrentCodeSystemVersion(theCodeSystem, theVersion);
if (csv == null) { if (csv == null) {
return null; return Optional.empty();
} }
return myConceptDao.findByCodeSystemAndCode(csv, theCode); return myConceptDao.findByCodeSystemAndCode(csv, theCode);
}); });
} }
@Nullable @Nullable
private TermCodeSystemVersion getCurrentCodeSystemVersion(String theUri) { private TermCodeSystemVersion getCurrentCodeSystemVersion(String theUri, String theVersion) {
TermCodeSystemVersion retVal = myCodeSystemCurrentVersionCache.get(theUri, uri -> myTxTemplate.execute(tx -> { StringBuilder key = new StringBuilder(theUri);
if (theVersion != null) {
key.append("_").append(theVersion);
}
TermCodeSystemVersion retVal = myCodeSystemCurrentVersionCache.get(key.toString(), t -> myTxTemplate.execute(tx -> {
TermCodeSystemVersion csv = null; TermCodeSystemVersion csv = null;
TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(uri); TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(theUri);
if (cs != null && cs.getCurrentVersion() != null) { if (cs != null) {
csv = cs.getCurrentVersion(); if (theVersion != null) {
csv = myCodeSystemVersionDao.findByCodeSystemPidAndVersion(cs.getPid(), theVersion);
} else if (cs.getCurrentVersion() != null) {
csv = cs.getCurrentVersion();
}
} }
if (csv != null) { if (csv != null) {
return csv; return csv;
@ -1820,20 +1827,30 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
ourLog.info("Done storing TermValueSet[{}] for {}", termValueSet.getId(), theValueSet.getIdElement().toVersionless().getValueAsString()); ourLog.info("Done storing TermValueSet[{}] for {}", termValueSet.getId(), theValueSet.getIdElement().toVersionless().getValueAsString());
} }
@Override
public IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB,
IPrimitiveType<String> theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB) {
return subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, null, null, null);
}
@Override @Override
@Transactional @Transactional
public IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB) { public IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB, IPrimitiveType<String> theSystemVersion, String theCodingAVersion, String theCodingBVersion) {
VersionIndependentConcept conceptA = toConcept(theCodeA, theSystem, theCodingA); VersionIndependentConceptWithSystemVersion conceptA = toConcept(theCodeA, theSystem, theCodingA, theSystemVersion, theCodingAVersion);
VersionIndependentConcept conceptB = toConcept(theCodeB, theSystem, theCodingB); VersionIndependentConceptWithSystemVersion conceptB = toConcept(theCodeB, theSystem, theCodingB, theSystemVersion, theCodingBVersion);
if (!StringUtils.equals(conceptA.getSystem(), conceptB.getSystem())) { if (!StringUtils.equals(conceptA.getSystem(), conceptB.getSystem())) {
throw new InvalidRequestException("Unable to test subsumption across different code systems"); throw new InvalidRequestException("Unable to test subsumption across different code systems");
} }
TermConcept codeA = findCode(conceptA.getSystem(), conceptA.getCode()) if (!StringUtils.equals(conceptA.getCodeSystemVersion(), conceptB.getCodeSystemVersion())) {
throw new InvalidRequestException("Unable to test subsumption across different code system versions");
}
TermConcept codeA = findCode(conceptA.getSystem(), conceptA.getCode(), conceptA.getCodeSystemVersion())
.orElseThrow(() -> new InvalidRequestException("Unknown code: " + conceptA)); .orElseThrow(() -> new InvalidRequestException("Unknown code: " + conceptA));
TermConcept codeB = findCode(conceptB.getSystem(), conceptB.getCode()) TermConcept codeB = findCode(conceptB.getSystem(), conceptB.getCode(), conceptB.getCodeSystemVersion())
.orElseThrow(() -> new InvalidRequestException("Unknown code: " + conceptB)); .orElseThrow(() -> new InvalidRequestException("Unknown code: " + conceptB));
FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager); FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager);
@ -1852,10 +1869,10 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
protected abstract ValueSet toCanonicalValueSet(IBaseResource theValueSet); protected abstract ValueSet toCanonicalValueSet(IBaseResource theValueSet);
protected IValidationSupport.LookupCodeResult lookupCode(String theSystem, String theCode) { protected IValidationSupport.LookupCodeResult lookupCode(String theSystem, String theCode, String theVersion) {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
return txTemplate.execute(t -> { return txTemplate.execute(t -> {
Optional<TermConcept> codeOpt = findCode(theSystem, theCode); Optional<TermConcept> codeOpt = findCode(theSystem, theCode, theVersion);
if (codeOpt.isPresent()) { if (codeOpt.isPresent()) {
TermConcept code = codeOpt.get(); TermConcept code = codeOpt.get();
@ -2116,6 +2133,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
while (scrollableResultsIterator.hasNext()) { while (scrollableResultsIterator.hasNext()) {
TermConceptMapGroupElement nextElement = scrollableResultsIterator.next(); TermConceptMapGroupElement nextElement = scrollableResultsIterator.next();
// TODO: The invocation of the size() below does not seem to be necessary but for some reason, removing it causes tests in TerminologySvcImplR4Test to fail.
nextElement.getConceptMapGroupElementTargets().size(); nextElement.getConceptMapGroupElementTargets().size();
myEntityManager.detach(nextElement); myEntityManager.detach(nextElement);
@ -2180,7 +2198,6 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
return null; return null;
} }
@CoverageIgnore @CoverageIgnore
@Override @Override
public IValidationSupport.CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { public IValidationSupport.CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
@ -2223,7 +2240,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
} }
} }
CodeValidationResult retVal = null; CodeValidationResult retVal;
if (valueSet != null) { if (valueSet != null) {
retVal = new InMemoryTerminologyServerValidationSupport(myContext).validateCodeInValueSet(theValidationSupportContext, theValidationOptions, theCodeSystem, theCode, theDisplay, valueSet); retVal = new InMemoryTerminologyServerValidationSupport(myContext).validateCodeInValueSet(theValidationSupportContext, theValidationOptions, theCodeSystem, theCode, theDisplay, valueSet);
} else { } else {
@ -2456,14 +2473,31 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
} }
@NotNull @NotNull
private static VersionIndependentConcept toConcept(IPrimitiveType<String> theCodeType, IPrimitiveType<String> theSystemType, IBaseCoding theCodingType) { private VersionIndependentConceptWithSystemVersion toConcept(IPrimitiveType<String> theCodeType, IPrimitiveType<String> theSystemType, IBaseCoding theCodingType, IPrimitiveType<String> theSystemVersionType, String theCodingVersionType) {
String code = theCodeType != null ? theCodeType.getValueAsString() : null; String code = theCodeType != null ? theCodeType.getValueAsString() : null;
String system = theSystemType != null ? theSystemType.getValueAsString() : null; String system = theSystemType != null ? theSystemType.getValueAsString() : null;
String systemVersion = theSystemVersionType != null ? theSystemVersionType.getValueAsString() : null;
if (theCodingType != null) { if (theCodingType != null) {
code = theCodingType.getCode(); code = theCodingType.getCode();
system = theCodingType.getSystem(); system = theCodingType.getSystem();
systemVersion = theCodingVersionType;
} }
return new VersionIndependentConcept(system, code); return new VersionIndependentConceptWithSystemVersion(system, code, systemVersion);
}
private static class VersionIndependentConceptWithSystemVersion extends VersionIndependentConcept {
String myCodeSystemVersion;
public VersionIndependentConceptWithSystemVersion(String theSystem, String theCode, String theSystemVersion) {
super(theSystem, theCode);
myCodeSystemVersion = theSystemVersion;
}
public String getCodeSystemVersion() {
return myCodeSystemVersion;
}
} }
/** /**

View File

@ -231,6 +231,26 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
}); });
} }
@Override
@Transactional(propagation = Propagation.NEVER)
public void deleteCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion) {
// Delete TermCodeSystemVersion
ourLog.info(" * Deleting code system version {}", theCodeSystemVersion.getCodeSystemVersionId());
deleteCodeSystemVersion(theCodeSystemVersion.getPid());
// Check if the version deleted is the current version. If so, delete TermCodeSystem as well.
TermCodeSystem termCodeSystem = theCodeSystemVersion.getCodeSystem();
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
txTemplate.executeWithoutResult(t -> {
if (myCodeSystemVersionDao.findByCodeSystemPid(termCodeSystem.getPid()).size() == 0) {
ourLog.info(" * Deleting code system {}", termCodeSystem.getPid());
deleteCodeSystem(termCodeSystem);
}
});
}
/** /**
* Returns the number of saved concepts * Returns the number of saved concepts
*/ */
@ -284,16 +304,19 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
ResourcePersistentId codeSystemResourcePid = getCodeSystemResourcePid(theCodeSystem.getIdElement()); ResourcePersistentId codeSystemResourcePid = getCodeSystemResourcePid(theCodeSystem.getIdElement());
/* /*
* If this is a not-present codesystem, we don't want to store a new version if one * If this is a not-present codesystem and codesystem version already exists, we don't want to
* already exists, since that will wipe out the existing concepts. We do create or update * overwrite the existing version since that will wipe out the existing concepts. We do create
* the TermCodeSystem table though, since that allows the DB to reject changes * or update the TermCodeSystem table though, since that allows the DB to reject changes that would
* that would result in duplicate CodeSysten.url values. * result in duplicate CodeSystem.url values.
*/ */
if (theCodeSystem.getContent() == CodeSystem.CodeSystemContentMode.NOTPRESENT) { if (theCodeSystem.getContent() == CodeSystem.CodeSystemContentMode.NOTPRESENT) {
TermCodeSystem codeSystem = myCodeSystemDao.findByCodeSystemUri(theCodeSystem.getUrl()); TermCodeSystem termCodeSystem = myCodeSystemDao.findByCodeSystemUri(theCodeSystem.getUrl());
if (codeSystem != null) { if (termCodeSystem != null) {
getOrCreateTermCodeSystem(codeSystemResourcePid, theCodeSystem.getUrl(), theCodeSystem.getUrl(), theResourceEntity); TermCodeSystemVersion codeSystemVersion = getExistingTermCodeSystemVersion(termCodeSystem.getPid(), theCodeSystem.getVersion());
return; if (codeSystemVersion != null) {
TermCodeSystem myCodeSystemEntity = getOrCreateDistinctTermCodeSystem(codeSystemResourcePid, theCodeSystem.getUrl(), theCodeSystem.getUrl(), theCodeSystem.getVersion(), theResourceEntity);
return;
}
} }
} }
@ -338,29 +361,33 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
ValidateUtil.isTrueOrThrowInvalidRequest(theCodeSystemVersion.getResource() != null, "No resource supplied"); ValidateUtil.isTrueOrThrowInvalidRequest(theCodeSystemVersion.getResource() != null, "No resource supplied");
ValidateUtil.isNotBlankOrThrowInvalidRequest(theSystemUri, "No system URI supplied"); ValidateUtil.isNotBlankOrThrowInvalidRequest(theSystemUri, "No system URI supplied");
// Grab the existing versions so we can delete them later // Grab the existing version so we can delete it
List<TermCodeSystemVersion> existing = myCodeSystemVersionDao.findByCodeSystemResourcePid(theCodeSystemResourcePid.getIdAsLong()); TermCodeSystem existingCodeSystem = myCodeSystemDao.findByCodeSystemUri(theSystemUri);
TermCodeSystemVersion existing = null;
/* if (existingCodeSystem != null) {
* For now we always delete old versions. At some point it would be nice to allow configuration to keep old versions. existing = getExistingTermCodeSystemVersion(existingCodeSystem.getPid(), theSystemVersionId);
*/
for (TermCodeSystemVersion next : existing) {
ourLog.info("Deleting old code system version {}", next.getPid());
Long codeSystemVersionPid = next.getPid();
deleteCodeSystemVersion(codeSystemVersionPid);
} }
ourLog.debug("Flushing..."); /*
myConceptDao.flush(); * Get CodeSystem and validate CodeSystemVersion
ourLog.debug("Done flushing"); */
TermCodeSystem codeSystem = getOrCreateDistinctTermCodeSystem(theCodeSystemResourcePid, theSystemUri, theSystemName, theSystemVersionId, theCodeSystemResourceTable);
/*
* Delete version being replaced.
*/
if(existing != null) {
ourLog.info("Deleting old code system version {}", existing.getPid());
Long codeSystemVersionPid = existing.getPid();
deleteCodeSystemVersion(codeSystemVersionPid);
}
/* /*
* Do the upload * Do the upload
*/ */
TermCodeSystem codeSystem = getOrCreateTermCodeSystem(theCodeSystemResourcePid, theSystemUri, theSystemName, theCodeSystemResourceTable);
theCodeSystemVersion.setCodeSystem(codeSystem); theCodeSystemVersion.setCodeSystem(codeSystem);
theCodeSystemVersion.setCodeSystemDisplayName(theSystemName); theCodeSystemVersion.setCodeSystemDisplayName(theSystemName);
@ -408,6 +435,17 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
} }
} }
private TermCodeSystemVersion getExistingTermCodeSystemVersion(Long theCodeSystemVersionPid, String theCodeSystemVersion) {
TermCodeSystemVersion existing;
if (theCodeSystemVersion == null) {
existing = myCodeSystemVersionDao.findByCodeSystemPidVersionIsNull(theCodeSystemVersionPid);
} else {
existing = myCodeSystemVersionDao.findByCodeSystemPidAndVersion(theCodeSystemVersionPid, theCodeSystemVersion);
}
return existing;
}
private void deleteCodeSystemVersion(final Long theCodeSystemVersionPid) { private void deleteCodeSystemVersion(final Long theCodeSystemVersionPid) {
ourLog.info(" * Deleting code system version {}", theCodeSystemVersionPid); ourLog.info(" * Deleting code system version {}", theCodeSystemVersionPid);
@ -455,10 +493,12 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
ourLog.info(" * Removing code system version {} as current version of code system {}", theCodeSystemVersionPid, codeSystem.getPid()); ourLog.info(" * Removing code system version {} as current version of code system {}", theCodeSystemVersionPid, codeSystem.getPid());
codeSystem.setCurrentVersion(null); codeSystem.setCurrentVersion(null);
myCodeSystemDao.save(codeSystem); myCodeSystemDao.save(codeSystem);
myCodeSystemDao.flush();
} }
ourLog.info(" * Deleting code system version"); ourLog.info(" * Deleting code system version");
myCodeSystemVersionDao.deleteById(theCodeSystemVersionPid); myCodeSystemVersionDao.delete(theCodeSystemVersionPid);
myCodeSystemVersionDao.flush();
}); });
} }
@ -651,28 +691,48 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
} }
@Nonnull @Nonnull
private TermCodeSystem getOrCreateTermCodeSystem(ResourcePersistentId theCodeSystemResourcePid, String theSystemUri, String theSystemName, ResourceTable theCodeSystemResourceTable) { private TermCodeSystem getOrCreateDistinctTermCodeSystem(ResourcePersistentId theCodeSystemResourcePid, String theSystemUri, String theSystemName, String theSystemVersionId, ResourceTable theCodeSystemResourceTable) {
TermCodeSystem codeSystem = myCodeSystemDao.findByCodeSystemUri(theSystemUri); TermCodeSystem codeSystem = myCodeSystemDao.findByCodeSystemUri(theSystemUri);
if (codeSystem == null) { if (codeSystem == null) {
codeSystem = myCodeSystemDao.findByResourcePid(theCodeSystemResourcePid.getIdAsLong()); codeSystem = myCodeSystemDao.findByResourcePid(theCodeSystemResourcePid.getIdAsLong());
if (codeSystem == null) { if (codeSystem == null) {
codeSystem = new TermCodeSystem(); codeSystem = new TermCodeSystem();
} }
codeSystem.setResource(theCodeSystemResourceTable);
} else {
if (!ObjectUtil.equals(codeSystem.getResource().getId(), theCodeSystemResourceTable.getId())) {
String msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "cannotCreateDuplicateCodeSystemUrl", theSystemUri,
codeSystem.getResource().getIdDt().toUnqualifiedVersionless().getValue());
throw new UnprocessableEntityException(msg);
}
} }
codeSystem.setResource(theCodeSystemResourceTable);
codeSystem.setCodeSystemUri(theSystemUri); codeSystem.setCodeSystemUri(theSystemUri);
codeSystem.setName(theSystemName); codeSystem.setName(theSystemName);
codeSystem = myCodeSystemDao.save(codeSystem); codeSystem = myCodeSystemDao.save(codeSystem);
checkForCodeSystemVersionDuplicate(codeSystem,theSystemUri, theSystemVersionId, theCodeSystemResourceTable);
return codeSystem; return codeSystem;
} }
private void checkForCodeSystemVersionDuplicate(TermCodeSystem theCodeSystem, String theSystemUri, String theSystemVersionId, ResourceTable theCodeSystemResourceTable) {
// Check if CodeSystemVersion entity already exists.
TermCodeSystemVersion codeSystemVersionEntity;
String msg = null;
if (theSystemVersionId == null) {
codeSystemVersionEntity = myCodeSystemVersionDao.findByCodeSystemPidVersionIsNull(theCodeSystem.getPid());
if (codeSystemVersionEntity != null) {
msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "cannotCreateDuplicateCodeSystemUrl", theSystemUri,
codeSystemVersionEntity.getResource().getIdDt().toUnqualifiedVersionless().getValue());
}
} else {
codeSystemVersionEntity = myCodeSystemVersionDao.findByCodeSystemPidAndVersion(theCodeSystem.getPid(), theSystemVersionId);
if (codeSystemVersionEntity != null) {
msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "cannotCreateDuplicateCodeSystemUrlAndVersion", theSystemUri,
theSystemVersionId, codeSystemVersionEntity.getResource().getIdDt().toUnqualifiedVersionless().getValue());
}
}
// Throw exception if the CodeSystemVersion is being duplicated.
if (codeSystemVersionEntity != null) {
if (!ObjectUtil.equals(codeSystemVersionEntity.getResource().getId(), theCodeSystemResourceTable.getId())) {
throw new UnprocessableEntityException(msg);
}
}
}
private void populateCodeSystemVersionProperties(TermCodeSystemVersion theCodeSystemVersion, CodeSystem theCodeSystemResource, ResourceTable theResourceTable) { private void populateCodeSystemVersionProperties(TermCodeSystemVersion theCodeSystemVersion, CodeSystem theCodeSystemResource, ResourceTable theResourceTable) {
theCodeSystemVersion.setResource(theResourceTable); theCodeSystemVersion.setResource(theResourceTable);
theCodeSystemVersion.setCodeSystemDisplayName(theCodeSystemResource.getName()); theCodeSystemVersion.setCodeSystemDisplayName(theCodeSystemResource.getName());

View File

@ -26,6 +26,7 @@ import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemVersionDao;
import ca.uhn.fhir.jpa.dao.data.ITermConceptDao; import ca.uhn.fhir.jpa.dao.data.ITermConceptDao;
import ca.uhn.fhir.jpa.dao.data.ITermConceptParentChildLinkDao; import ca.uhn.fhir.jpa.dao.data.ITermConceptParentChildLinkDao;
import ca.uhn.fhir.jpa.entity.TermCodeSystem; 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.TermConcept;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink;
import ca.uhn.fhir.jpa.model.sched.HapiJob; import ca.uhn.fhir.jpa.model.sched.HapiJob;
@ -68,6 +69,7 @@ public class TermDeferredStorageSvcImpl implements ITermDeferredStorageSvc {
protected PlatformTransactionManager myTransactionMgr; protected PlatformTransactionManager myTransactionMgr;
private boolean myProcessDeferred = true; private boolean myProcessDeferred = true;
private List<TermCodeSystem> myDefferedCodeSystemsDeletions = Collections.synchronizedList(new ArrayList<>()); private List<TermCodeSystem> myDefferedCodeSystemsDeletions = Collections.synchronizedList(new ArrayList<>());
private List<TermCodeSystemVersion> myDefferedCodeSystemVersionsDeletions = Collections.synchronizedList(new ArrayList<>());
private List<TermConcept> myDeferredConcepts = Collections.synchronizedList(new ArrayList<>()); private List<TermConcept> myDeferredConcepts = Collections.synchronizedList(new ArrayList<>());
private List<ValueSet> myDeferredValueSets = Collections.synchronizedList(new ArrayList<>()); private List<ValueSet> myDeferredValueSets = Collections.synchronizedList(new ArrayList<>());
private List<ConceptMap> myDeferredConceptMaps = Collections.synchronizedList(new ArrayList<>()); private List<ConceptMap> myDeferredConceptMaps = Collections.synchronizedList(new ArrayList<>());
@ -115,6 +117,12 @@ public class TermDeferredStorageSvcImpl implements ITermDeferredStorageSvc {
myDefferedCodeSystemsDeletions.add(theCodeSystem); myDefferedCodeSystemsDeletions.add(theCodeSystem);
} }
@Override
@Transactional
public void deleteCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion) {
myDefferedCodeSystemVersionsDeletions.add(theCodeSystemVersion);
}
@Override @Override
public void saveAllDeferred() { public void saveAllDeferred() {
while (!isStorageQueueEmpty()) { while (!isStorageQueueEmpty()) {
@ -266,6 +274,12 @@ public class TermDeferredStorageSvcImpl implements ITermDeferredStorageSvc {
} }
private void processDeferredCodeSystemDeletions() { private void processDeferredCodeSystemDeletions() {
for (TermCodeSystemVersion next : myDefferedCodeSystemVersionsDeletions) {
myCodeSystemStorageSvc.deleteCodeSystemVersion(next);
}
myDefferedCodeSystemVersionsDeletions.clear();
for (TermCodeSystem next : myDefferedCodeSystemsDeletions) { for (TermCodeSystem next : myDefferedCodeSystemsDeletions) {
myCodeSystemStorageSvc.deleteCodeSystem(next); myCodeSystemStorageSvc.deleteCodeSystem(next);
} }
@ -300,7 +314,7 @@ public class TermDeferredStorageSvcImpl implements ITermDeferredStorageSvc {
} }
private boolean isDeferredCodeSystemDeletions() { private boolean isDeferredCodeSystemDeletions() {
return !myDefferedCodeSystemsDeletions.isEmpty(); return !myDefferedCodeSystemsDeletions.isEmpty() || !myDefferedCodeSystemVersionsDeletions.isEmpty();
} }
private boolean isDeferredConcepts() { private boolean isDeferredConcepts() {

View File

@ -378,6 +378,10 @@ public class TermLoaderSvcImpl implements ITermLoaderSvc {
try { try {
String loincCsString = IOUtils.toString(BaseTermReadSvcImpl.class.getResourceAsStream("/ca/uhn/fhir/jpa/term/loinc/loinc.xml"), Charsets.UTF_8); String loincCsString = IOUtils.toString(BaseTermReadSvcImpl.class.getResourceAsStream("/ca/uhn/fhir/jpa/term/loinc/loinc.xml"), Charsets.UTF_8);
loincCs = FhirContext.forR4().newXmlParser().parseResource(CodeSystem.class, loincCsString); loincCs = FhirContext.forR4().newXmlParser().parseResource(CodeSystem.class, loincCsString);
String codeSystemVersionId = theUploadProperties.getProperty(LOINC_CODESYSTEM_VERSION.getCode());
if (codeSystemVersionId != null) {
loincCs.setVersion(codeSystemVersionId);
}
} catch (IOException e) { } catch (IOException e) {
throw new InternalErrorException("Failed to load loinc.xml", e); throw new InternalErrorException("Failed to load loinc.xml", e);
} }

View File

@ -149,7 +149,12 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation
@Override @Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) { public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) {
return super.lookupCode(theSystem, theCode); return super.lookupCode(theSystem, theCode, null);
}
@Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theVersion) {
return super.lookupCode(theSystem, theCode, theVersion);
} }
@Override @Override

View File

@ -89,9 +89,14 @@ public class TermReadSvcR4 extends BaseTermReadSvcImpl implements ITermReadSvcR4
return (CodeSystem) theCodeSystem; return (CodeSystem) theCodeSystem;
} }
@Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theVersion) {
return super.lookupCode(theSystem, theCode, theVersion);
}
@Override @Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) { public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) {
return super.lookupCode(theSystem, theCode); return super.lookupCode(theSystem, theCode, null);
} }
@Override @Override

View File

@ -41,6 +41,8 @@ public interface ITermCodeSystemStorageSvc {
void deleteCodeSystem(TermCodeSystem theCodeSystem); void deleteCodeSystem(TermCodeSystem theCodeSystem);
void deleteCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion);
void storeNewCodeSystemVersion(ResourcePersistentId theCodeSystemResourcePid, String theSystemUri, String theSystemName, String theSystemVersionId, TermCodeSystemVersion theCodeSystemVersion, ResourceTable theCodeSystemResourceTable); void storeNewCodeSystemVersion(ResourcePersistentId theCodeSystemResourcePid, String theSystemUri, String theSystemName, String theSystemVersionId, TermCodeSystemVersion theCodeSystemVersion, ResourceTable theCodeSystemResourceTable);
/** /**

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.term.api;
*/ */
import ca.uhn.fhir.jpa.entity.TermCodeSystem; 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.TermConcept;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink;
import org.hl7.fhir.r4.model.ConceptMap; import org.hl7.fhir.r4.model.ConceptMap;
@ -54,6 +55,8 @@ public interface ITermDeferredStorageSvc {
void deleteCodeSystem(TermCodeSystem theCodeSystem); void deleteCodeSystem(TermCodeSystem theCodeSystem);
void deleteCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion);
/** /**
* This is mostly here for unit tests - Saves any and all deferred concepts and links * This is mostly here for unit tests - Saves any and all deferred concepts and links
*/ */

View File

@ -73,6 +73,8 @@ public interface ITermReadSvc extends IValidationSupport {
List<VersionIndependentConcept> expandValueSet(ValueSetExpansionOptions theExpansionOptions, String theValueSet); List<VersionIndependentConcept> expandValueSet(ValueSetExpansionOptions theExpansionOptions, String theValueSet);
Optional<TermConcept> findCode(String theCodeSystem, String theCode, String theVersion);
Optional<TermConcept> findCode(String theCodeSystem, String theCode); Optional<TermConcept> findCode(String theCodeSystem, String theCode);
Set<TermConcept> findCodesAbove(Long theCodeSystemResourcePid, Long theCodeSystemResourceVersionPid, String theCode); Set<TermConcept> findCodesAbove(Long theCodeSystemResourcePid, Long theCodeSystemResourceVersionPid, String theCode);
@ -103,6 +105,8 @@ public interface ITermReadSvc extends IValidationSupport {
IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB); IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB);
IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB, IPrimitiveType<String> theSystemVersion, String theCodingAVersion, String theCodingBVersion);
void preExpandDeferredValueSetsToTerminologyTables(); void preExpandDeferredValueSetsToTerminologyTables();
/** /**

View File

@ -96,6 +96,9 @@ public enum LoincUploadPropertiesEnum {
/* /*
* OPTIONAL * OPTIONAL
*/ */
// This is the version identifier for the answer list file
LOINC_CODESYSTEM_VERSION("loinc.codesystem.version"),
// This is the version identifier for the answer list file // This is the version identifier for the answer list file
LOINC_ANSWERLIST_VERSION("loinc.answerlist.version"), LOINC_ANSWERLIST_VERSION("loinc.answerlist.version"),

View File

@ -61,6 +61,10 @@ loinc.universal.lab.order.valueset.file=AccessoryFiles/LoincUniversalLabOrdersVa
### OPTIONAL ### ### OPTIONAL ###
################ ################
# This is the version identifier for the LOINC code system
## Key may be omitted if only a single version of LOINC is being kept.
loinc.codesystem.version=2.68
# This is the version identifier for the answer list file # This is the version identifier for the answer list file
## Key may be omitted ## Key may be omitted
loinc.answerlist.version=Beta.1 loinc.answerlist.version=Beta.1

View File

@ -521,7 +521,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test {
myTerminologyDeferredStorageSvc.saveDeferred(); myTerminologyDeferredStorageSvc.saveDeferred();
myTerminologyDeferredStorageSvc.saveDeferred(); myTerminologyDeferredStorageSvc.saveDeferred();
IValidationSupport.LookupCodeResult lookupResults = myCodeSystemDao.lookupCode(new StringType("childAA"), new StringType(URL_MY_CODE_SYSTEM),null, mySrd); IValidationSupport.LookupCodeResult lookupResults = myCodeSystemDao.lookupCode(new StringType("childAA"), new StringType(URL_MY_CODE_SYSTEM), null,null, mySrd);
assertEquals(true, lookupResults.isFound()); assertEquals(true, lookupResults.isFound());
ValueSet vs = new ValueSet(); ValueSet vs = new ValueSet();
@ -711,7 +711,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test {
StringType code = new StringType("ParentA"); StringType code = new StringType("ParentA");
StringType system = new StringType("http://snomed.info/sct"); StringType system = new StringType("http://snomed.info/sct");
IValidationSupport.LookupCodeResult outcome = myCodeSystemDao.lookupCode(code, system, null, mySrd); IValidationSupport.LookupCodeResult outcome = myCodeSystemDao.lookupCode(code, system, null,null, mySrd);
assertEquals(true, outcome.isFound()); assertEquals(true, outcome.isFound());
} }

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.dao.r4; package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.term.TermReindexingSvcImpl; import ca.uhn.fhir.jpa.term.TermReindexingSvcImpl;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
@ -8,11 +9,14 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class FhirResourceDaoR4CodeSystemTest extends BaseJpaR4Test { public class FhirResourceDaoR4CodeSystemTest extends BaseJpaR4Test {
@ -35,15 +39,7 @@ public class FhirResourceDaoR4CodeSystemTest extends BaseJpaR4Test {
@Test @Test
public void testDeleteLargeCompleteCodeSystem() { public void testDeleteLargeCompleteCodeSystem() {
CodeSystem cs = new CodeSystem(); IIdType id = createLargeCodeSystem(null);
cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
cs.setUrl("http://foo");
for (int i = 0; i < 222; i++) {
cs.addConcept().setCode("CODE" + i);
}
IIdType id = myCodeSystemDao.create(cs).getId().toUnqualifiedVersionless();
myTerminologyDeferredStorageSvc.saveDeferred();
myTerminologyDeferredStorageSvc.saveDeferred();
runInTransaction(() -> { runInTransaction(() -> {
assertEquals(1, myTermCodeSystemDao.count()); assertEquals(1, myTermCodeSystemDao.count());
@ -72,6 +68,122 @@ public class FhirResourceDaoR4CodeSystemTest extends BaseJpaR4Test {
} }
@Test
public void testDeleteCodeSystemVersion() {
// Create code system with two versions.
IIdType id_first = createLargeCodeSystem("1");
runInTransaction(() -> {
assertEquals(1, myTermCodeSystemDao.count());
assertNotNull(myTermCodeSystemDao.findByCodeSystemUri("http://foo"));
assertEquals(1, myTermCodeSystemVersionDao.count());
List<ResourceTable> resourceList = myResourceTableDao.findAll();
assertEquals(222, myTermConceptDao.count());
assertEquals(1, resourceList.size());
assertNull(resourceList.get(0).getDeleted());
});
IIdType id_second = createLargeCodeSystem("2");
runInTransaction(() -> {
assertEquals(1, myTermCodeSystemDao.count());
assertNotNull(myTermCodeSystemDao.findByCodeSystemUri("http://foo"));
assertEquals(2, myTermCodeSystemVersionDao.count());
assertEquals(444, myTermConceptDao.count());
List<ResourceTable> resourceList = myResourceTableDao.findAll();
assertEquals(2, resourceList.size());
long active = resourceList
.stream()
.filter(t -> t.getDeleted() == null).count();
assertEquals(2, active);
});
// Attempt to delete first version
myCodeSystemDao.delete(id_first, mySrd);
// Only the resource will be deleted initially
runInTransaction(() -> {
assertEquals(1, myTermCodeSystemDao.count());
assertNotNull(myTermCodeSystemDao.findByCodeSystemUri("http://foo"));
assertEquals(2, myTermCodeSystemVersionDao.count());
assertEquals(444, myTermConceptDao.count());
List<ResourceTable> resourceList = myResourceTableDao.findAll();
assertEquals(2, resourceList.size());
long active = resourceList
.stream()
.filter(t -> t.getDeleted() == null).count();
assertEquals(1, active);
});
// Now the background scheduler will do its thing
myTerminologyDeferredStorageSvc.saveDeferred();
// Entities for first resource should be gone now.
runInTransaction(() -> {
assertEquals(1, myTermCodeSystemDao.count());
assertNotNull(myTermCodeSystemDao.findByCodeSystemUri("http://foo"));
assertEquals(1, myTermCodeSystemVersionDao.count());
assertEquals(222, myTermConceptDao.count());
List<ResourceTable> resourceList = myResourceTableDao.findAll();
assertEquals(2, resourceList.size());
long active = resourceList
.stream()
.filter(t -> t.getDeleted() == null).count();
assertEquals(1, active);
});
// Attempt to delete second version
myCodeSystemDao.delete(id_second, mySrd);
// Only the resource will be deleted initially
runInTransaction(() -> {
assertEquals(1, myTermCodeSystemDao.count());
assertNotNull(myTermCodeSystemDao.findByCodeSystemUri("http://foo"));
assertEquals(1, myTermCodeSystemVersionDao.count());
assertEquals(222, myTermConceptDao.count());
List<ResourceTable> resourceList = myResourceTableDao.findAll();
assertEquals(2, resourceList.size());
long active = resourceList
.stream()
.filter(t -> t.getDeleted() == null).count();
assertEquals(0, active);
});
// Now the background scheduler will do its thing
myTerminologyDeferredStorageSvc.saveDeferred();
// The remaining versions and Code System entities should be gone now.
runInTransaction(() -> {
assertEquals(0, myTermCodeSystemDao.count());
assertNull(myTermCodeSystemDao.findByCodeSystemUri("http://foo"));
assertEquals(0, myTermCodeSystemVersionDao.count());
List<ResourceTable> resourceList = myResourceTableDao.findAll();
assertEquals(2, resourceList.size());
long active = resourceList
.stream()
.filter(t -> t.getDeleted() == null).count();
assertEquals(0, active);
});
}
private IIdType createLargeCodeSystem(String theVersion) {
CodeSystem cs = new CodeSystem();
cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
cs.setUrl("http://foo");
if (theVersion != null) {
cs.setVersion(theVersion);
}
for (int i = 0; i < 222; i++) {
cs.addConcept().setCode("CODE" + i);
}
IIdType id = myCodeSystemDao.create(cs).getId().toUnqualifiedVersionless();
myTerminologyDeferredStorageSvc.saveDeferred();
myTerminologyDeferredStorageSvc.saveDeferred();
return id;
}
@AfterAll @AfterAll
public static void afterClassClearContext() { public static void afterClassClearContext() {
TermReindexingSvcImpl.setForceSaveDeferredAlwaysForUnitTest(false); TermReindexingSvcImpl.setForceSaveDeferredAlwaysForUnitTest(false);

View File

@ -828,7 +828,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test {
StringType code = new StringType("ParentA"); StringType code = new StringType("ParentA");
StringType system = new StringType("http://snomed.info/sct"); StringType system = new StringType("http://snomed.info/sct");
IValidationSupport.LookupCodeResult outcome = myCodeSystemDao.lookupCode(code, system, null, mySrd); IValidationSupport.LookupCodeResult outcome = myCodeSystemDao.lookupCode(code, system, null,null, mySrd);
assertEquals(true, outcome.isFound()); assertEquals(true, outcome.isFound());
} }

View File

@ -325,6 +325,7 @@ public class ResourceProviderR4CodeSystemTest extends BaseResourceProviderR4Test
.withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("FOO")) .withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("FOO"))
.andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentB")) .andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentB"))
.execute(); .execute();
fail();
} catch (InvalidRequestException e) { } catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Unknown code: [http://parentchild|FOO]", e.getMessage()); assertEquals("HTTP 400 Bad Request: Unknown code: [http://parentchild|FOO]", e.getMessage());
} }
@ -340,6 +341,7 @@ public class ResourceProviderR4CodeSystemTest extends BaseResourceProviderR4Test
.withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentB")) .withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentB"))
.andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("FOO")) .andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("FOO"))
.execute(); .execute();
fail();
} catch (InvalidRequestException e) { } catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Unknown code: [http://parentchild|FOO]", e.getMessage()); assertEquals("HTTP 400 Bad Request: Unknown code: [http://parentchild|FOO]", e.getMessage());
} }
@ -355,6 +357,7 @@ public class ResourceProviderR4CodeSystemTest extends BaseResourceProviderR4Test
.withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD + "A").setCode("ChildAA")) .withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD + "A").setCode("ChildAA"))
.andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD + "B").setCode("ParentA")) .andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD + "B").setCode("ParentA"))
.execute(); .execute();
fail();
} catch (InvalidRequestException e) { } catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Unable to test subsumption across different code systems", e.getMessage()); assertEquals("HTTP 400 Bad Request: Unable to test subsumption across different code systems", e.getMessage());
} }

View File

@ -0,0 +1,769 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.UriType;
import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r4.model.codesystems.ConceptSubsumptionOutcome;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
public class ResourceProviderR4CodeSystemVersionedTest extends BaseResourceProviderR4Test {
private static final String SYSTEM_PARENTCHILD = "http://parentchild";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderR4CodeSystemVersionedTest.class);
@BeforeEach
@Transactional
public void before02() throws IOException {
CodeSystem cs = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs-v1.xml");
myCodeSystemDao.create(cs, mySrd);
cs = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs-v2.xml");
myCodeSystemDao.create(cs, mySrd);
ValueSet upload = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml");
myValueSetDao.create(upload, mySrd).getId().toUnqualifiedVersionless();
CodeSystem parentChildCs = new CodeSystem();
parentChildCs.setUrl(SYSTEM_PARENTCHILD);
parentChildCs.setVersion("1");
parentChildCs.setStatus(Enumerations.PublicationStatus.ACTIVE);
parentChildCs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
parentChildCs.setHierarchyMeaning(CodeSystem.CodeSystemHierarchyMeaning.ISA);
CodeSystem.ConceptDefinitionComponent parentA = parentChildCs.addConcept().setCode("ParentA").setDisplay("Parent A");
parentA.addConcept().setCode("ChildAA").setDisplay("Child AA");
parentA.addConcept().setCode("ParentC").setDisplay("Parent C");
parentChildCs.addConcept().setCode("ParentB").setDisplay("Parent B");
myCodeSystemDao.create(parentChildCs);
parentChildCs = new CodeSystem();
parentChildCs.setVersion("2");
parentChildCs.setUrl(SYSTEM_PARENTCHILD);
parentChildCs.setStatus(Enumerations.PublicationStatus.ACTIVE);
parentChildCs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
parentChildCs.setHierarchyMeaning(CodeSystem.CodeSystemHierarchyMeaning.ISA);
parentA = parentChildCs.addConcept().setCode("ParentA").setDisplay("Parent A v2");
parentA.addConcept().setCode("ChildAA").setDisplay("Child AA v2");
parentA.addConcept().setCode("ParentB").setDisplay("Parent B v2");
parentChildCs.addConcept().setCode("ParentC").setDisplay("Parent C v2");
myCodeSystemDao.create(parentChildCs);
}
@Test
public void testLookupOnExternalCodeMultiVersion() {
ResourceProviderR4ValueSetTest.createExternalCs(myCodeSystemDao, myResourceTableDao, myTermCodeSystemStorageSvc, mySrd, "1");
ResourceProviderR4ValueSetTest.createExternalCs(myCodeSystemDao, myResourceTableDao, myTermCodeSystemStorageSvc, mySrd, "2");
// First test with no version specified (should return from last version created)
Parameters respParam = myClient
.operation()
.onType(CodeSystem.class)
.named("lookup")
.withParameter(Parameters.class, "code", new CodeType("ParentA"))
.andParameter("system", new UriType(FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM))
.execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals("name", respParam.getParameter().get(0).getName());
assertEquals("SYSTEM NAME", ((StringType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("version", respParam.getParameter().get(1).getName());
assertEquals("2", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(2).getName());
assertEquals("Parent A2", ((StringType) respParam.getParameter().get(2).getValue()).getValue());
assertEquals("abstract", respParam.getParameter().get(3).getName());
assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue());
// With HTTP GET
respParam = myClient
.operation()
.onType(CodeSystem.class)
.named("lookup")
.withParameter(Parameters.class, "code", new CodeType("ParentA"))
.andParameter("system", new UriType(FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM))
.useHttpGet()
.execute();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals("name", respParam.getParameter().get(0).getName());
assertEquals(("SYSTEM NAME"), ((StringType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("version", respParam.getParameter().get(1).getName());
assertEquals(("2"), ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(2).getName());
assertEquals("Parent A2", ((StringType) respParam.getParameter().get(2).getValue()).getValue());
assertEquals("abstract", respParam.getParameter().get(3).getName());
assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue());
// Test with version 1 specified.
respParam = myClient
.operation()
.onType(CodeSystem.class)
.named("lookup")
.withParameter(Parameters.class, "code", new CodeType("ParentA"))
.andParameter("system", new UriType(FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM))
.andParameter("version", new StringType("1"))
.execute();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals("name", respParam.getParameter().get(0).getName());
assertEquals("SYSTEM NAME", ((StringType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("version", respParam.getParameter().get(1).getName());
assertEquals("1", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(2).getName());
assertEquals("Parent A1", ((StringType) respParam.getParameter().get(2).getValue()).getValue());
assertEquals("abstract", respParam.getParameter().get(3).getName());
assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue());
// With HTTP GET
respParam = myClient
.operation()
.onType(CodeSystem.class)
.named("lookup")
.withParameter(Parameters.class, "code", new CodeType("ParentA"))
.andParameter("system", new UriType(FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM))
.andParameter("version", new StringType("1"))
.useHttpGet()
.execute();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals("name", respParam.getParameter().get(0).getName());
assertEquals(("SYSTEM NAME"), ((StringType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("version", respParam.getParameter().get(1).getName());
assertEquals(("1"), ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(2).getName());
assertEquals("Parent A1", ((StringType) respParam.getParameter().get(2).getValue()).getValue());
assertEquals("abstract", respParam.getParameter().get(3).getName());
assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue());
// Test with version 2 specified.
respParam = myClient
.operation()
.onType(CodeSystem.class)
.named("lookup")
.withParameter(Parameters.class, "code", new CodeType("ParentA"))
.andParameter("system", new UriType(FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM))
.andParameter("version", new StringType("2"))
.execute();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals("name", respParam.getParameter().get(0).getName());
assertEquals("SYSTEM NAME", ((StringType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("version", respParam.getParameter().get(1).getName());
assertEquals("2", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(2).getName());
assertEquals("Parent A2", ((StringType) respParam.getParameter().get(2).getValue()).getValue());
assertEquals("abstract", respParam.getParameter().get(3).getName());
assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue());
// With HTTP GET
respParam = myClient
.operation()
.onType(CodeSystem.class)
.named("lookup")
.withParameter(Parameters.class, "code", new CodeType("ParentA"))
.andParameter("system", new UriType(FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM))
.andParameter("version", new StringType("2"))
.useHttpGet()
.execute();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals("name", respParam.getParameter().get(0).getName());
assertEquals(("SYSTEM NAME"), ((StringType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("version", respParam.getParameter().get(1).getName());
assertEquals(("2"), ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(2).getName());
assertEquals("Parent A2", ((StringType) respParam.getParameter().get(2).getValue()).getValue());
assertEquals("abstract", respParam.getParameter().get(3).getName());
assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue());
}
@Test
public void testLookupOperationByCodeAndSystemBuiltInCode() {
// First test with no version specified (should return the one and only version defined).
Parameters respParam = myClient
.operation()
.onType(CodeSystem.class)
.named("lookup")
.withParameter(Parameters.class, "code", new CodeType("ACSN"))
.andParameter("system", new UriType("http://terminology.hl7.org/CodeSystem/v2-0203"))
.execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals("name", respParam.getParameter().get(0).getName());
assertEquals("v2.0203", ((StringType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("version", respParam.getParameter().get(1).getName());
assertEquals("2.9", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(2).getName());
assertEquals("Accession ID", ((StringType) respParam.getParameter().get(2).getValue()).getValue());
assertEquals("abstract", respParam.getParameter().get(3).getName());
assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue());
// Repeat with version specified.
respParam = myClient
.operation()
.onType(CodeSystem.class)
.named("lookup")
.withParameter(Parameters.class, "code", new CodeType("ACSN"))
.andParameter("system", new UriType("http://terminology.hl7.org/CodeSystem/v2-0203"))
.andParameter("version", new StringType("2.9"))
.execute();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals("name", respParam.getParameter().get(0).getName());
assertEquals("v2.0203", ((StringType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("version", respParam.getParameter().get(1).getName());
assertEquals("2.9", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(2).getName());
assertEquals("Accession ID", ((StringType) respParam.getParameter().get(2).getValue()).getValue());
assertEquals("abstract", respParam.getParameter().get(3).getName());
assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue());
}
@Test
public void testLookupOperationByCodeAndSystemBuiltInNonexistentVersion() {
try {
myClient
.operation()
.onType(CodeSystem.class)
.named("lookup")
.withParameter(Parameters.class, "code", new CodeType("ACSN"))
.andParameter("system", new UriType("http://hl7.org/fhir/v2/0203"))
.andParameter("version", new StringType("2.8"))
.execute();
fail();
} catch (ResourceNotFoundException e) {
ourLog.info("Lookup failed as expected");
}
}
@Test
public void testLookupOperationByCodeAndSystemUserDefinedCode() {
// First test with no version specified (should return from last version created)
Parameters respParam = myClient
.operation()
.onType(CodeSystem.class)
.named("lookup")
.withParameter(Parameters.class, "code", new CodeType("8450-9"))
.andParameter("system", new UriType("http://acme.org"))
.execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals("name", respParam.getParameter().get(0).getName());
assertEquals(("ACME Codes"), ((StringType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("version", respParam.getParameter().get(1).getName());
assertEquals("2", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(2).getName());
assertEquals(("Systolic blood pressure--expiration v2"), ((StringType) respParam.getParameter().get(2).getValue()).getValue());
assertEquals("abstract", respParam.getParameter().get(3).getName());
assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue());
// Test with version 1 specified.
respParam = myClient
.operation()
.onType(CodeSystem.class)
.named("lookup")
.withParameter(Parameters.class, "code", new CodeType("8450-9"))
.andParameter("system", new UriType("http://acme.org"))
.andParameter("version", new StringType("1"))
.execute();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals("name", respParam.getParameter().get(0).getName());
assertEquals(("ACME Codes"), ((StringType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("version", respParam.getParameter().get(1).getName());
assertEquals("1", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(2).getName());
assertEquals(("Systolic blood pressure--expiration"), ((StringType) respParam.getParameter().get(2).getValue()).getValue());
assertEquals("abstract", respParam.getParameter().get(3).getName());
assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue());
// Test with version 2 specified
respParam = myClient
.operation()
.onType(CodeSystem.class)
.named("lookup")
.withParameter(Parameters.class, "code", new CodeType("8450-9"))
.andParameter("system", new UriType("http://acme.org"))
.andParameter("version", new StringType("2"))
.execute();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals("name", respParam.getParameter().get(0).getName());
assertEquals(("ACME Codes"), ((StringType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("version", respParam.getParameter().get(1).getName());
assertEquals("2", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(2).getName());
assertEquals(("Systolic blood pressure--expiration v2"), ((StringType) respParam.getParameter().get(2).getValue()).getValue());
assertEquals("abstract", respParam.getParameter().get(3).getName());
assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue());
}
@Test
public void testLookupOperationByCodeAndSystemUserDefinedNonExistentVersion() {
try {
myClient
.operation()
.onType(CodeSystem.class)
.named("lookup")
.withParameter(Parameters.class, "code", new CodeType("8450-9"))
.andParameter("system", new UriType("http://acme.org"))
.andParameter("version", new StringType("3"))
.execute();
fail();
} catch (ResourceNotFoundException e) {
ourLog.info("Lookup failed as expected");
}
}
@Test
public void testLookupOperationByCoding() {
// First test with no version specified (should return from last version created)
Parameters respParam = myClient
.operation()
.onType(CodeSystem.class)
.named("lookup")
.withParameter(Parameters.class, "coding", new Coding().setSystem("http://acme.org").setCode("8450-9"))
.execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals("name", respParam.getParameter().get(0).getName());
assertEquals(("ACME Codes"), ((StringType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("version", respParam.getParameter().get(1).getName());
assertEquals("2", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(2).getName());
assertEquals(("Systolic blood pressure--expiration v2"), ((StringType) respParam.getParameter().get(2).getValue()).getValue());
assertEquals("abstract", respParam.getParameter().get(3).getName());
assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue());
// Test with version set to 1
respParam = myClient
.operation()
.onType(CodeSystem.class)
.named("lookup")
.withParameter(Parameters.class, "coding", new Coding().setSystem("http://acme.org").setCode("8450-9").setVersion("1"))
.execute();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals("name", respParam.getParameter().get(0).getName());
assertEquals(("ACME Codes"), ((StringType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("version", respParam.getParameter().get(1).getName());
assertEquals("1", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(2).getName());
assertEquals(("Systolic blood pressure--expiration"), ((StringType) respParam.getParameter().get(2).getValue()).getValue());
assertEquals("abstract", respParam.getParameter().get(3).getName());
assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue());
// Test with version set to 2
respParam = myClient
.operation()
.onType(CodeSystem.class)
.named("lookup")
.withParameter(Parameters.class, "coding", new Coding().setSystem("http://acme.org").setCode("8450-9").setVersion("2"))
.execute();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals("name", respParam.getParameter().get(0).getName());
assertEquals(("ACME Codes"), ((StringType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("version", respParam.getParameter().get(1).getName());
assertEquals("2", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(2).getName());
assertEquals(("Systolic blood pressure--expiration v2"), ((StringType) respParam.getParameter().get(2).getValue()).getValue());
assertEquals("abstract", respParam.getParameter().get(3).getName());
assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue());
}
@Test
public void testSubsumesOnCodes_Subsumes() {
// First test with no version specified (should return result for last version created).
Parameters respParam = myClient
.operation()
.onType(CodeSystem.class)
.named(JpaConstants.OPERATION_SUBSUMES)
.withParameter(Parameters.class, "codeA", new CodeType("ParentB"))
.andParameter("codeB", new CodeType("ParentA"))
.andParameter("system", new UriType(SYSTEM_PARENTCHILD))
.execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(1, respParam.getParameter().size());
assertEquals("outcome", respParam.getParameter().get(0).getName());
assertEquals(ConceptSubsumptionOutcome.SUBSUMES.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue());
// Test with version set to 1.
respParam = myClient
.operation()
.onType(CodeSystem.class)
.named(JpaConstants.OPERATION_SUBSUMES)
.withParameter(Parameters.class, "codeA", new CodeType("ParentC"))
.andParameter("codeB", new CodeType("ParentA"))
.andParameter("system", new UriType(SYSTEM_PARENTCHILD))
.andParameter("version", new StringType("1"))
.execute();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(1, respParam.getParameter().size());
assertEquals("outcome", respParam.getParameter().get(0).getName());
assertEquals(ConceptSubsumptionOutcome.SUBSUMES.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue());
// Test with version set to 2.
respParam = myClient
.operation()
.onType(CodeSystem.class)
.named(JpaConstants.OPERATION_SUBSUMES)
.withParameter(Parameters.class, "codeA", new CodeType("ParentB"))
.andParameter("codeB", new CodeType("ParentA"))
.andParameter("system", new UriType(SYSTEM_PARENTCHILD))
.andParameter("version", new StringType("2"))
.execute();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(1, respParam.getParameter().size());
assertEquals("outcome", respParam.getParameter().get(0).getName());
assertEquals(ConceptSubsumptionOutcome.SUBSUMES.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue());
}
@Test
public void testSubsumesOnCodes_Subsumedby() {
// First test with no version specified (should return result for last version created).
Parameters respParam = myClient
.operation()
.onType(CodeSystem.class)
.named(JpaConstants.OPERATION_SUBSUMES)
.withParameter(Parameters.class, "codeA", new CodeType("ParentA"))
.andParameter("codeB", new CodeType("ParentB"))
.andParameter("system", new UriType(SYSTEM_PARENTCHILD))
.execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(1, respParam.getParameter().size());
assertEquals("outcome", respParam.getParameter().get(0).getName());
assertEquals(ConceptSubsumptionOutcome.SUBSUMEDBY.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue());
// Test with version set to 1.
respParam = myClient
.operation()
.onType(CodeSystem.class)
.named(JpaConstants.OPERATION_SUBSUMES)
.withParameter(Parameters.class, "codeA", new CodeType("ParentA"))
.andParameter("codeB", new CodeType("ParentC"))
.andParameter("system", new UriType(SYSTEM_PARENTCHILD))
.andParameter("version", new StringType("1"))
.execute();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(1, respParam.getParameter().size());
assertEquals("outcome", respParam.getParameter().get(0).getName());
assertEquals(ConceptSubsumptionOutcome.SUBSUMEDBY.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue());
// Test with version set to 2.
respParam = myClient
.operation()
.onType(CodeSystem.class)
.named(JpaConstants.OPERATION_SUBSUMES)
.withParameter(Parameters.class, "codeA", new CodeType("ParentA"))
.andParameter("codeB", new CodeType("ParentB"))
.andParameter("system", new UriType(SYSTEM_PARENTCHILD))
.andParameter("version", new StringType("2"))
.execute();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(1, respParam.getParameter().size());
assertEquals("outcome", respParam.getParameter().get(0).getName());
assertEquals(ConceptSubsumptionOutcome.SUBSUMEDBY.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue());
}
@Test
public void testSubsumesOnCodes_Disjoint() {
// First test with no version specified (should return result for last version created).
Parameters respParam = myClient
.operation()
.onType(CodeSystem.class)
.named(JpaConstants.OPERATION_SUBSUMES)
.withParameter(Parameters.class, "codeA", new CodeType("ParentA"))
.andParameter("codeB", new CodeType("ParentC"))
.andParameter("system", new UriType(SYSTEM_PARENTCHILD))
.execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(1, respParam.getParameter().size());
assertEquals("outcome", respParam.getParameter().get(0).getName());
assertEquals(ConceptSubsumptionOutcome.NOTSUBSUMED.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue());
// Test with version set to 1
respParam = myClient
.operation()
.onType(CodeSystem.class)
.named(JpaConstants.OPERATION_SUBSUMES)
.withParameter(Parameters.class, "codeA", new CodeType("ParentA"))
.andParameter("codeB", new CodeType("ParentB"))
.andParameter("system", new UriType(SYSTEM_PARENTCHILD))
.andParameter("version", new StringType("1"))
.execute();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(1, respParam.getParameter().size());
assertEquals("outcome", respParam.getParameter().get(0).getName());
assertEquals(ConceptSubsumptionOutcome.NOTSUBSUMED.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue());
// Test with version set to 2
respParam = myClient
.operation()
.onType(CodeSystem.class)
.named(JpaConstants.OPERATION_SUBSUMES)
.withParameter(Parameters.class, "codeA", new CodeType("ParentA"))
.andParameter("codeB", new CodeType("ParentC"))
.andParameter("system", new UriType(SYSTEM_PARENTCHILD))
.andParameter("version", new StringType("2"))
.execute();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(1, respParam.getParameter().size());
assertEquals("outcome", respParam.getParameter().get(0).getName());
assertEquals(ConceptSubsumptionOutcome.NOTSUBSUMED.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue());
}
@Test
public void testSubsumesOnCodings_MismatchedCsVersions() {
try {
myClient
.operation()
.onType(CodeSystem.class)
.named(JpaConstants.OPERATION_SUBSUMES)
.withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ChildAA").setVersion("1"))
.andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentA").setVersion("2"))
.execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Unable to test subsumption across different code system versions", e.getMessage());
}
}
@Test
public void testSubsumesOnCodings_Subsumes() {
// First test with no version specified (should return result for last version created).
Parameters respParam = myClient
.operation()
.onType(CodeSystem.class)
.named(JpaConstants.OPERATION_SUBSUMES)
.withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentB"))
.andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentA"))
.execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(1, respParam.getParameter().size());
assertEquals("outcome", respParam.getParameter().get(0).getName());
assertEquals(ConceptSubsumptionOutcome.SUBSUMES.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue());
// Test with version set to 1.
respParam = myClient
.operation()
.onType(CodeSystem.class)
.named(JpaConstants.OPERATION_SUBSUMES)
.withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentC").setVersion("1"))
.andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentA").setVersion("1"))
.execute();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(1, respParam.getParameter().size());
assertEquals("outcome", respParam.getParameter().get(0).getName());
assertEquals(ConceptSubsumptionOutcome.SUBSUMES.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue());
// Test with version set to 2.
respParam = myClient
.operation()
.onType(CodeSystem.class)
.named(JpaConstants.OPERATION_SUBSUMES)
.withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentB").setVersion("2"))
.andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentA").setVersion("2"))
.execute();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(1, respParam.getParameter().size());
assertEquals("outcome", respParam.getParameter().get(0).getName());
assertEquals(ConceptSubsumptionOutcome.SUBSUMES.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue());
}
@Test
public void testSubsumesOnCodings_Subsumedby() {
// First test with no version specified (should return result for last version created).
Parameters respParam = myClient
.operation()
.onType(CodeSystem.class)
.named(JpaConstants.OPERATION_SUBSUMES)
.withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentA"))
.andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentB"))
.execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(1, respParam.getParameter().size());
assertEquals("outcome", respParam.getParameter().get(0).getName());
assertEquals(ConceptSubsumptionOutcome.SUBSUMEDBY.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue());
// Test with version set to 1.
respParam = myClient
.operation()
.onType(CodeSystem.class)
.named(JpaConstants.OPERATION_SUBSUMES)
.withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentA").setVersion("1"))
.andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentC").setVersion("1"))
.execute();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(1, respParam.getParameter().size());
assertEquals("outcome", respParam.getParameter().get(0).getName());
assertEquals(ConceptSubsumptionOutcome.SUBSUMEDBY.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue());
// Test with version set to 2.
respParam = myClient
.operation()
.onType(CodeSystem.class)
.named(JpaConstants.OPERATION_SUBSUMES)
.withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentA").setVersion("2"))
.andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentB").setVersion("2"))
.execute();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(1, respParam.getParameter().size());
assertEquals("outcome", respParam.getParameter().get(0).getName());
assertEquals(ConceptSubsumptionOutcome.SUBSUMEDBY.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue());
}
@Test
public void testSubsumesOnCodings_Disjoint() {
// First test with no version specified (should return result for last version created).
Parameters respParam = myClient
.operation()
.onType(CodeSystem.class)
.named(JpaConstants.OPERATION_SUBSUMES)
.withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentA"))
.andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentC"))
.execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(1, respParam.getParameter().size());
assertEquals("outcome", respParam.getParameter().get(0).getName());
assertEquals(ConceptSubsumptionOutcome.NOTSUBSUMED.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue());
// Test with version set to 1
respParam = myClient
.operation()
.onType(CodeSystem.class)
.named(JpaConstants.OPERATION_SUBSUMES)
.withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentA").setVersion("1"))
.andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentB").setVersion("1"))
.execute();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(1, respParam.getParameter().size());
assertEquals("outcome", respParam.getParameter().get(0).getName());
assertEquals(ConceptSubsumptionOutcome.NOTSUBSUMED.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue());
// Test with version set to 2
respParam = myClient
.operation()
.onType(CodeSystem.class)
.named(JpaConstants.OPERATION_SUBSUMES)
.withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentA").setVersion("2"))
.andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentC").setVersion("2"))
.execute();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(1, respParam.getParameter().size());
assertEquals("outcome", respParam.getParameter().get(0).getName());
assertEquals(ConceptSubsumptionOutcome.NOTSUBSUMED.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue());
}
}

View File

@ -16,6 +16,7 @@ import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.ParametersUtil;
import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.UrlUtil;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
@ -1089,4 +1090,38 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
return codeSystem; return codeSystem;
} }
public static CodeSystem createExternalCs(IFhirResourceDao<CodeSystem> theCodeSystemDao, IResourceTableDao theResourceTableDao, ITermCodeSystemStorageSvc theTermCodeSystemStorageSvc, ServletRequestDetails theRequestDetails, String theCodeSystemVersion) {
CodeSystem codeSystem = new CodeSystem();
codeSystem.setUrl(URL_MY_CODE_SYSTEM);
codeSystem.setContent(CodeSystemContentMode.NOTPRESENT);
codeSystem.setVersion(theCodeSystemVersion);
IIdType id = theCodeSystemDao.create(codeSystem, theRequestDetails).getId().toUnqualified();
ResourceTable table = theResourceTableDao.findById(id.getIdPartAsLong()).orElseThrow(IllegalStateException::new);
TermCodeSystemVersion cs = new TermCodeSystemVersion();
cs.setResource(table);
TermConcept parentA = new TermConcept(cs, "ParentA").setDisplay("Parent A" + theCodeSystemVersion);
cs.getConcepts().add(parentA);
TermConcept childAA = new TermConcept(cs, "childAA").setDisplay("Child AA" + theCodeSystemVersion);
parentA.addChild(childAA, RelationshipTypeEnum.ISA);
TermConcept childAAA = new TermConcept(cs, "childAAA").setDisplay("Child AAA" + theCodeSystemVersion);
childAA.addChild(childAAA, RelationshipTypeEnum.ISA);
TermConcept childAAB = new TermConcept(cs, "childAAB").setDisplay("Child AAB" + theCodeSystemVersion);
childAA.addChild(childAAB, RelationshipTypeEnum.ISA);
TermConcept childAB = new TermConcept(cs, "childAB").setDisplay("Child AB" + theCodeSystemVersion);
parentA.addChild(childAB, RelationshipTypeEnum.ISA);
TermConcept parentB = new TermConcept(cs, "ParentB").setDisplay("Parent B" + theCodeSystemVersion);
cs.getConcepts().add(parentB);
theTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM, "SYSTEM NAME", theCodeSystemVersion, cs, table);
return codeSystem;
}
} }

View File

@ -1,11 +1,17 @@
package ca.uhn.fhir.jpa.term; package ca.uhn.fhir.jpa.term;
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
import ca.uhn.fhir.jpa.entity.TermCodeSystem;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeType; import org.hl7.fhir.r4.model.CodeType;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
public class TermCodeSystemStorageSvcTest extends BaseJpaR4Test { public class TermCodeSystemStorageSvcTest extends BaseJpaR4Test {
@ -13,39 +19,115 @@ public class TermCodeSystemStorageSvcTest extends BaseJpaR4Test {
public static final String URL_MY_CODE_SYSTEM = "http://example.com/my_code_system"; public static final String URL_MY_CODE_SYSTEM = "http://example.com/my_code_system";
@Test @Test
public void testStoreNewCodeSystemVersionForExistingCodeSystem() { public void testStoreNewCodeSystemVersionForExistingCodeSystemNoVersionId() {
CodeSystem upload = createCodeSystemWithMoreThan100Concepts(); CodeSystem firstUpload = createCodeSystemWithMoreThan100Concepts();
CodeSystem duplicateUpload = createCodeSystemWithMoreThan100Concepts();
// Create CodeSystem resource testCreatingAndUpdatingCodeSystemEntity(firstUpload, duplicateUpload, 125, "Can not create multiple CodeSystem resources with CodeSystem.url \"http://example.com/my_code_system\", already have one with resource ID: CodeSystem/");
ResourceTable codeSystemResourceEntity = (ResourceTable) myCodeSystemDao.create(upload, mySrd).getEntity();
// Update the CodeSystem resource runInTransaction(() -> {
runInTransaction(() -> myTermCodeSystemStorageSvc.storeNewCodeSystemVersionIfNeeded(upload, codeSystemResourceEntity)); assertEquals(1, myTermCodeSystemDao.count());
assertEquals(1, myTermCodeSystemVersionDao.count());
TermCodeSystem myTermCodeSystem = myTermCodeSystemDao.findByCodeSystemUri(URL_MY_CODE_SYSTEM);
/* TermCodeSystemVersion myTermCodeSystemVersion = myTermCodeSystemVersionDao.findByCodeSystemPidVersionIsNull( myTermCodeSystem.getPid());
Because there are more than 100 concepts in the code system, the first 100 will be persisted immediately and assertEquals(myTermCodeSystem.getCurrentVersion().getPid(), myTermCodeSystemVersion.getPid());
the remaining 25 concepts will be queued up for "deferred save". assertEquals(myTermCodeSystem.getResource().getId(), myTermCodeSystemVersion.getResource().getId());
});
}
@Test
public void testStoreNewCodeSystemVersionForExistingCodeSystemVersionId() {
CodeSystem firstUpload = createCodeSystemWithMoreThan100Concepts();
firstUpload.setVersion("1");
CodeSystem duplicateUpload = createCodeSystemWithMoreThan100Concepts();
duplicateUpload.setVersion("1");
testCreatingAndUpdatingCodeSystemEntity(firstUpload, duplicateUpload, 125,"Can not create multiple CodeSystem resources with CodeSystem.url \"http://example.com/my_code_system\" and CodeSystem.version \"1\", already have one with resource ID: CodeSystem/");
runInTransaction(() -> {
assertEquals(1, myTermCodeSystemDao.count());
assertEquals(1, myTermCodeSystemVersionDao.count());
TermCodeSystem myTermCodeSystem = myTermCodeSystemDao.findByCodeSystemUri(URL_MY_CODE_SYSTEM);
TermCodeSystemVersion myTermCodeSystemVersion = myTermCodeSystemVersionDao.findByCodeSystemPidAndVersion( myTermCodeSystem.getPid(), "1");
assertEquals(myTermCodeSystem.getCurrentVersion().getPid(), myTermCodeSystemVersion.getPid());
assertEquals(myTermCodeSystem.getResource().getId(), myTermCodeSystemVersion.getResource().getId());
});
// Now add a second version
firstUpload = createCodeSystemWithMoreThan100Concepts();
firstUpload.setVersion("2");
duplicateUpload = createCodeSystemWithMoreThan100Concepts();
duplicateUpload.setVersion("2");
testCreatingAndUpdatingCodeSystemEntity(firstUpload, duplicateUpload, 251,"Can not create multiple CodeSystem resources with CodeSystem.url \"http://example.com/my_code_system\" and CodeSystem.version \"2\", already have one with resource ID: CodeSystem/");
runInTransaction(() -> {
assertEquals(1, myTermCodeSystemDao.count());
assertEquals(2, myTermCodeSystemVersionDao.count());
TermCodeSystem myTermCodeSystem = myTermCodeSystemDao.findByCodeSystemUri(URL_MY_CODE_SYSTEM);
TermCodeSystemVersion mySecondTermCodeSystemVersion = myTermCodeSystemVersionDao.findByCodeSystemPidAndVersion(myTermCodeSystem.getPid(), "2");
assertEquals(myTermCodeSystem.getCurrentVersion().getPid(), mySecondTermCodeSystemVersion.getPid());
assertEquals(myTermCodeSystem.getResource().getId(), mySecondTermCodeSystemVersion.getResource().getId());
});
As the CodeSystem was persisted twice, the extra 25 term concepts will be queued twice, each with a different
CodeSystem version PID. Only one set of the term concepts should be persisted (i.e. 125 term concepts in total).
*/
myTerminologyDeferredStorageSvc.setProcessDeferred(true);
myTerminologyDeferredStorageSvc.saveDeferred();
assertEquals(125, myTermConceptDao.count());
} }
private CodeSystem createCodeSystemWithMoreThan100Concepts() { private CodeSystem createCodeSystemWithMoreThan100Concepts() {
CodeSystem codeSystem = new CodeSystem(); CodeSystem codeSystem = new CodeSystem();
codeSystem.setUrl(URL_MY_CODE_SYSTEM); codeSystem.setUrl(URL_MY_CODE_SYSTEM);
codeSystem.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT);
for (int i = 0; i < 125; i++) { for (int i = 0; i < 125; i++) {
codeSystem.addConcept(new CodeSystem.ConceptDefinitionComponent(new CodeType("codeA " + i))); codeSystem.addConcept(new CodeSystem.ConceptDefinitionComponent(new CodeType("codeA " + i)));
} }
codeSystem.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
return codeSystem; return codeSystem;
} }
private void testCreatingAndUpdatingCodeSystemEntity(CodeSystem theUpload, CodeSystem theDuplicate, int expectedCnt, String theDuplicateErrorBaseMsg) {
// Create CodeSystem resource
ResourceTable codeSystemResourceEntity = (ResourceTable) myCodeSystemDao.create(theUpload, mySrd).getEntity();
// Create the CodeSystem and CodeSystemVersion entities
validateCodeSystemUpdates(expectedCnt);
// Update the CodeSystem
theUpload.addConcept(new CodeSystem.ConceptDefinitionComponent(new CodeType("codeB")));
// Update the CodeSystem and CodeSystemVersion entities
runInTransaction(() -> myTermCodeSystemStorageSvc.storeNewCodeSystemVersionIfNeeded(theUpload, codeSystemResourceEntity));
validateCodeSystemUpdates(expectedCnt+1);
// Try duplicating the CodeSystem
Long originalResId = codeSystemResourceEntity.getId();
try {
myCodeSystemDao.create(theDuplicate, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertEquals(theDuplicateErrorBaseMsg + originalResId, e.getMessage());
}
// Try updating code system when content mode is NOT PRESENT
theUpload.setConcept(new ArrayList<>());
theUpload.setContent((CodeSystem.CodeSystemContentMode.NOTPRESENT));
runInTransaction(() -> myTermCodeSystemStorageSvc.storeNewCodeSystemVersionIfNeeded(theUpload, codeSystemResourceEntity));
validateCodeSystemUpdates(expectedCnt+1);
}
private void validateCodeSystemUpdates(int theExpectedConceptCount) {
myTerminologyDeferredStorageSvc.setProcessDeferred(true);
myTerminologyDeferredStorageSvc.saveDeferred();
myTerminologyDeferredStorageSvc.setProcessDeferred(false);
assertEquals(theExpectedConceptCount, myTermConceptDao.count());
}
} }

View File

@ -343,6 +343,37 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest {
assertEquals("13006-2", vs.getCompose().getInclude().get(0).getConcept().get(1).getCode()); assertEquals("13006-2", vs.getCompose().getInclude().get(0).getConcept().get(1).getCode());
} }
@Test
public void testLoadLoincMultipleVersions() throws IOException {
// Load LOINC marked as version 2.67
addLoincMandatoryFilesWithPropertiesFileToZip(myFiles, "v267_loincupload.properties");
mySvc.loadLoinc(myFiles.getFiles(), mySrd);
verify(myTermCodeSystemStorageSvc, times(1)).storeNewCodeSystemVersion(mySystemCaptor.capture(), myCsvCaptor.capture(), any(RequestDetails.class), myValueSetsCaptor.capture(), myConceptMapCaptor.capture());
CodeSystem loincCS = mySystemCaptor.getValue();
assertEquals("2.67", loincCS.getVersion());
// Update LOINC marked as version 2.67
myFiles = new ZipCollectionBuilder();
addLoincMandatoryFilesWithPropertiesFileToZip(myFiles, "v267_loincupload.properties");
mySvc.loadLoinc(myFiles.getFiles(), mySrd);
verify(myTermCodeSystemStorageSvc, times(2)).storeNewCodeSystemVersion(mySystemCaptor.capture(), myCsvCaptor.capture(), any(RequestDetails.class), myValueSetsCaptor.capture(), myConceptMapCaptor.capture());
loincCS = mySystemCaptor.getValue();
assertEquals("2.67", loincCS.getVersion());
// Load LOINC marked as version 2.68
myFiles = new ZipCollectionBuilder();
addLoincMandatoryFilesWithPropertiesFileToZip(myFiles, "v268_loincupload.properties");
mySvc.loadLoinc(myFiles.getFiles(), mySrd);
verify(myTermCodeSystemStorageSvc, times(3)).storeNewCodeSystemVersion(mySystemCaptor.capture(), myCsvCaptor.capture(), any(RequestDetails.class), myValueSetsCaptor.capture(), myConceptMapCaptor.capture());
loincCS = mySystemCaptor.getValue();
assertEquals("2.68", loincCS.getVersion());
}
@Test @Test
@Disabled @Disabled
public void testLoadLoincMandatoryFilesOnly() throws IOException { public void testLoadLoincMandatoryFilesOnly() throws IOException {
@ -383,7 +414,11 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest {
} }
public static void addLoincMandatoryFilesToZip(ZipCollectionBuilder theFiles) throws IOException { public static void addLoincMandatoryFilesToZip(ZipCollectionBuilder theFiles) throws IOException {
theFiles.addFileZip("/loinc/", LOINC_UPLOAD_PROPERTIES_FILE.getCode()); addLoincMandatoryFilesWithPropertiesFileToZip(theFiles, LOINC_UPLOAD_PROPERTIES_FILE.getCode());
}
public static void addLoincMandatoryFilesWithPropertiesFileToZip(ZipCollectionBuilder theFiles, String thePropertiesFile) throws IOException {
theFiles.addFileZip("/loinc/", thePropertiesFile);
theFiles.addFileZip("/loinc/", LOINC_GROUP_FILE_DEFAULT.getCode()); theFiles.addFileZip("/loinc/", LOINC_GROUP_FILE_DEFAULT.getCode());
theFiles.addFileZip("/loinc/", LOINC_GROUP_TERMS_FILE_DEFAULT.getCode()); theFiles.addFileZip("/loinc/", LOINC_GROUP_TERMS_FILE_DEFAULT.getCode());
theFiles.addFileZip("/loinc/", LOINC_PARENT_GROUP_FILE_DEFAULT.getCode()); theFiles.addFileZip("/loinc/", LOINC_PARENT_GROUP_FILE_DEFAULT.getCode());

View File

@ -184,7 +184,7 @@ public class TerminologySvcDeltaR4Test extends BaseJpaR4Test {
assertEquals(2, outcome.getUpdatedConceptCount()); assertEquals(2, outcome.getUpdatedConceptCount());
runInTransaction(() -> { runInTransaction(() -> {
TermConcept concept = myTermSvc.findCode("http://foo/cs", "ChildAA").orElseThrow(() -> new IllegalStateException()); TermConcept concept = myTermSvc.findCode("http://foo/cs", "ChildAA", null).orElseThrow(() -> new IllegalStateException());
assertEquals(2, concept.getParents().size()); assertEquals(2, concept.getParents().size());
assertThat(concept.getParentPidsAsString(), matchesPattern("^[0-9]+ [0-9]+$")); assertThat(concept.getParentPidsAsString(), matchesPattern("^[0-9]+ [0-9]+$"));
}); });

View File

@ -4,6 +4,9 @@ import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.jpa.api.model.TranslationRequest; import ca.uhn.fhir.jpa.api.model.TranslationRequest;
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.TermConceptMap; import ca.uhn.fhir.jpa.entity.TermConceptMap;
import ca.uhn.fhir.jpa.entity.TermConceptMapGroup; import ca.uhn.fhir.jpa.entity.TermConceptMapGroup;
import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement; import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement;
@ -21,7 +24,6 @@ import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence;
import org.hl7.fhir.r4.model.UriType; import org.hl7.fhir.r4.model.UriType;
import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r4.model.codesystems.HttpVerb; import org.hl7.fhir.r4.model.codesystems.HttpVerb;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -32,9 +34,14 @@ import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -1802,10 +1809,10 @@ public class TerminologySvcImplR4Test extends BaseTermR4Test {
createCodeSystem(); createCodeSystem();
IValidationSupport.CodeValidationResult validation = myTermSvc.validateCode(new ValidationSupportContext(myValidationSupport), new ConceptValidationOptions(), CS_URL, "ParentWithNoChildrenA", null, null); IValidationSupport.CodeValidationResult validation = myTermSvc.validateCode(new ValidationSupportContext(myValidationSupport), new ConceptValidationOptions(), CS_URL, "ParentWithNoChildrenA", null, null);
assertEquals(true, validation.isOk()); assertTrue(validation.isOk());
validation = myTermSvc.validateCode(new ValidationSupportContext(myValidationSupport), new ConceptValidationOptions(), CS_URL, "ZZZZZZZ", null, null); validation = myTermSvc.validateCode(new ValidationSupportContext(myValidationSupport), new ConceptValidationOptions(), CS_URL, "ZZZZZZZ", null, null);
assertEquals(false, validation.isOk()); assertFalse(validation.isOk());
} }
@Test @Test
@ -1904,4 +1911,109 @@ public class TerminologySvcImplR4Test extends BaseTermR4Test {
assertTrue(result.isOk()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@Test
public void testCreateCodeSystemTwoVersions() {
CodeSystem codeSystem = new CodeSystem();
codeSystem.setUrl(CS_URL);
codeSystem.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
codeSystem
.addConcept().setCode("A").setDisplay("Code A");
codeSystem
.addConcept().setCode("B").setDisplay("Code A");
codeSystem.setVersion("1");
IIdType id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified();
Set<TermConcept> codes = myTermSvc.findCodesBelow(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "A");
assertThat(toCodes(codes), containsInAnyOrder("A"));
codes = myTermSvc.findCodesBelow(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "B");
assertThat(toCodes(codes), containsInAnyOrder("B"));
runInTransaction(() -> {
List<TermCodeSystemVersion> termCodeSystemVersions = myTermCodeSystemVersionDao.findAll();
assertEquals(termCodeSystemVersions.size(), 1);
TermCodeSystemVersion termCodeSystemVersion_1 = termCodeSystemVersions.get(0);
assertEquals(termCodeSystemVersion_1.getConcepts().size(), 2);
Set<TermConcept> termConcepts = new HashSet<>(termCodeSystemVersion_1.getConcepts());
assertThat(toCodes(termConcepts), containsInAnyOrder("A", "B"));
TermCodeSystem termCodeSystem = myTermCodeSystemDao.findByResourcePid(id.getIdPartAsLong());
assertEquals("1", termCodeSystem.getCurrentVersion().getCodeSystemVersionId());
});
codeSystem.setVersion("2");
codeSystem
.addConcept().setCode("C").setDisplay("Code C");
IIdType id_v2 = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified();
codes = myTermSvc.findCodesBelow(id_v2.getIdPartAsLong(), id_v2.getVersionIdPartAsLong(), "C");
assertThat(toCodes(codes), containsInAnyOrder("C"));
runInTransaction(() -> {
List<TermCodeSystemVersion> termCodeSystemVersions_updated = myTermCodeSystemVersionDao.findAll();
assertEquals(termCodeSystemVersions_updated.size(), 2);
TermCodeSystemVersion termCodeSystemVersion_2 = termCodeSystemVersions_updated.get(1);
assertEquals(termCodeSystemVersion_2.getConcepts().size(), 3);
Set<TermConcept> termConcepts_updated = new HashSet<>(termCodeSystemVersion_2.getConcepts());
assertThat(toCodes(termConcepts_updated), containsInAnyOrder("A", "B", "C"));
TermCodeSystem termCodeSystem = myTermCodeSystemDao.findByResourcePid(id_v2.getIdPartAsLong());
assertEquals("2", termCodeSystem.getCurrentVersion().getCodeSystemVersionId());
});
}
@Test
public void testUpdateCodeSystemContentModeNotPresent() {
CodeSystem codeSystem = new CodeSystem();
codeSystem.setUrl(CS_URL);
codeSystem.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
codeSystem
.addConcept().setCode("A").setDisplay("Code A");
codeSystem
.addConcept().setCode("B").setDisplay("Code A");
codeSystem.setVersion("1");
IIdType id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified();
Set<TermConcept> codes = myTermSvc.findCodesBelow(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "A");
assertThat(toCodes(codes), containsInAnyOrder("A"));
codes = myTermSvc.findCodesBelow(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "B");
assertThat(toCodes(codes), containsInAnyOrder("B"));
runInTransaction(() -> {
List<TermCodeSystemVersion> termCodeSystemVersions = myTermCodeSystemVersionDao.findAll();
assertEquals(termCodeSystemVersions.size(), 1);
TermCodeSystemVersion termCodeSystemVersion_1 = termCodeSystemVersions.get(0);
assertEquals(termCodeSystemVersion_1.getConcepts().size(), 2);
Set<TermConcept> termConcepts = new HashSet<>(termCodeSystemVersion_1.getConcepts());
assertThat(toCodes(termConcepts), containsInAnyOrder("A", "B"));
TermCodeSystem termCodeSystem = myTermCodeSystemDao.findByResourcePid(id.getIdPartAsLong());
assertEquals("1", termCodeSystem.getCurrentVersion().getCodeSystemVersionId());
});
// Remove concepts and changed ContentMode to NOTPRESENT
codeSystem.setConcept(new ArrayList<>());
codeSystem.setContent((CodeSystem.CodeSystemContentMode.NOTPRESENT));
myCodeSystemDao.update(codeSystem, mySrd).getId().toUnqualified();
runInTransaction(() -> {
List<TermCodeSystemVersion> termCodeSystemVersions_updated = myTermCodeSystemVersionDao.findAll();
assertEquals(termCodeSystemVersions_updated.size(), 1);
TermCodeSystemVersion termCodeSystemVersion_2 = termCodeSystemVersions_updated.get(0);
assertEquals(termCodeSystemVersion_2.getConcepts().size(), 2);
Set<TermConcept> termConcepts_updated = new HashSet<>(termCodeSystemVersion_2.getConcepts());
assertThat(toCodes(termConcepts_updated), containsInAnyOrder("A", "B"));
TermCodeSystem termCodeSystem = myTermCodeSystemDao.findByResourcePid(id.getIdPartAsLong());
assertEquals("1", termCodeSystem.getCurrentVersion().getCodeSystemVersionId());
});
}
} }

View File

@ -0,0 +1,101 @@
<CodeSystem xmlns="http://hl7.org/fhir">
<url value="http://acme.org" />
<version value="1"/>
<name value="ACME Codes" />
<concept>
<code value="8450-9" />
<display value="Systolic blood pressure--expiration" />
</concept>
<concept>
<code value="11378-7" />
<display value="Systolic blood pressure at First encounter" />
</concept>
<concept>
<code value="8493-9" />
<display value="Systolic blood pressure 10 hour minimum" />
</concept>
<concept>
<code value="8494-7" />
<display value="Systolic blood pressure 12 hour minimum" />
</concept>
<concept>
<code value="8495-4" />
<display value="Systolic blood pressure 24 hour minimum" />
</concept>
<concept>
<code value="8451-7" />
<display value="Systolic blood pressure--inspiration" />
</concept>
<concept>
<code value="8452-5" />
<display value="Systolic blood pressure.inspiration - expiration" />
</concept>
<concept>
<code value="8459-0" />
<display value="Systolic blood pressure--sitting" />
</concept>
<concept>
<code value="8460-8" />
<display value="Systolic blood pressure--standing" />
</concept>
<concept>
<code value="8461-6" />
<display value="Systolic blood pressure--supine" />
</concept>
<concept>
<code value="8479-8" />
<display value="Systolic blood pressure by palpation" />
</concept>
<concept>
<code value="8480-6" />
<display value="Systolic blood pressure" />
</concept>
<concept>
<code value="8481-4" />
<display value="Systolic blood pressure 1 hour maximum" />
</concept>
<concept>
<code value="8482-2" />
<display value="Systolic blood pressure 8 hour maximum" />
</concept>
<concept>
<code value="8483-0" />
<display value="Systolic blood pressure 10 hour maximum" />
</concept>
<concept>
<code value="8484-8" />
<display value="Systolic blood pressure 12 hour maximum" />
</concept>
<concept>
<code value="8485-5" />
<display value="Systolic blood pressure 24 hour maximum" />
</concept>
<concept>
<code value="8486-3" />
<display value="Systolic blood pressure 1 hour mean" />
</concept>
<concept>
<code value="8487-1" />
<display value="Systolic blood pressure 8 hour mean" />
</concept>
<concept>
<code value="8488-9" />
<display value="Systolic blood pressure 10 hour mean" />
</concept>
<concept>
<code value="8489-7" />
<display value="Systolic blood pressure 12 hour mean" />
</concept>
<concept>
<code value="8490-5" />
<display value="Systolic blood pressure 24 hour mean" />
</concept>
<concept>
<code value="8491-3" />
<display value="Systolic blood pressure 1 hour minimum" />
</concept>
<concept>
<code value="8492-1" />
<display value="Systolic blood pressure 8 hour minimum" />
</concept>
</CodeSystem>

View File

@ -0,0 +1,101 @@
<CodeSystem xmlns="http://hl7.org/fhir">
<url value="http://acme.org" />
<version value="2"/>
<name value="ACME Codes" />
<concept>
<code value="8450-9" />
<display value="Systolic blood pressure--expiration v2" />
</concept>
<concept>
<code value="11378-7" />
<display value="Systolic blood pressure at First encounter v2" />
</concept>
<concept>
<code value="8493-9" />
<display value="Systolic blood pressure 10 hour minimum v2" />
</concept>
<concept>
<code value="8494-7" />
<display value="Systolic blood pressure 12 hour minimum v2" />
</concept>
<concept>
<code value="8495-4" />
<display value="Systolic blood pressure 24 hour minimum v2" />
</concept>
<concept>
<code value="8451-7" />
<display value="Systolic blood pressure--inspiration v2" />
</concept>
<concept>
<code value="8452-5" />
<display value="Systolic blood pressure.inspiration - expiration v2" />
</concept>
<concept>
<code value="8459-0" />
<display value="Systolic blood pressure--sitting v2" />
</concept>
<concept>
<code value="8460-8" />
<display value="Systolic blood pressure--standing v2" />
</concept>
<concept>
<code value="8461-6" />
<display value="Systolic blood pressure--supine v2" />
</concept>
<concept>
<code value="8479-8" />
<display value="Systolic blood pressure by palpation v2" />
</concept>
<concept>
<code value="8480-6" />
<display value="Systolic blood pressure v2" />
</concept>
<concept>
<code value="8481-4" />
<display value="Systolic blood pressure 1 hour maximum v2" />
</concept>
<concept>
<code value="8482-2" />
<display value="Systolic blood pressure 8 hour maximum v2" />
</concept>
<concept>
<code value="8483-0" />
<display value="Systolic blood pressure 10 hour maximum v2" />
</concept>
<concept>
<code value="8484-8" />
<display value="Systolic blood pressure 12 hour maximum v2" />
</concept>
<concept>
<code value="8485-5" />
<display value="Systolic blood pressure 24 hour maximum v2" />
</concept>
<concept>
<code value="8486-3" />
<display value="Systolic blood pressure 1 hour mean v2" />
</concept>
<concept>
<code value="8487-1" />
<display value="Systolic blood pressure 8 hour mean v2" />
</concept>
<concept>
<code value="8488-9" />
<display value="Systolic blood pressure 10 hour mean v2" />
</concept>
<concept>
<code value="8489-7" />
<display value="Systolic blood pressure 12 hour mean v2" />
</concept>
<concept>
<code value="8490-5" />
<display value="Systolic blood pressure 24 hour mean v2" />
</concept>
<concept>
<code value="8491-3" />
<display value="Systolic blood pressure 1 hour minimum v2" />
</concept>
<concept>
<code value="8492-1" />
<display value="Systolic blood pressure 8 hour minimum v2" />
</concept>
</CodeSystem>

View File

@ -0,0 +1,87 @@
#################
### MANDATORY ###
#################
# Answer lists (ValueSets of potential answers/values for LOINC "questions")
## File must be present
loinc.answerlist.file=AccessoryFiles/AnswerFile/AnswerList.csv
# Answer list links (connects LOINC observation codes to answer list codes)
## File must be present
loinc.answerlist.link.file=AccessoryFiles/AnswerFile/LoincAnswerListLink.csv
# Document ontology
## File must be present
loinc.document.ontology.file=AccessoryFiles/DocumentOntology/DocumentOntology.csv
# LOINC codes
## File must be present
loinc.file=LoincTable/Loinc.csv
# LOINC hierarchy
## File must be present
loinc.hierarchy.file=AccessoryFiles/MultiAxialHierarchy/MultiAxialHierarchy.csv
# IEEE medical device codes
## File must be present
loinc.ieee.medical.device.code.mapping.table.file=AccessoryFiles/LoincIeeeMedicalDeviceCodeMappingTable/LoincIeeeMedicalDeviceCodeMappingTable.csv
# Imaging document codes
## File must be present
loinc.imaging.document.codes.file=AccessoryFiles/ImagingDocuments/ImagingDocumentCodes.csv
# Part
## File must be present
loinc.part.file=AccessoryFiles/PartFile/Part.csv
# Part link
## File must be present
loinc.part.link.primary.file=AccessoryFiles/PartFile/LoincPartLink_Primary.csv
loinc.part.link.supplementary.file=AccessoryFiles/PartFile/LoincPartLink_Supplementary.csv
# Part related code mapping
## File must be present
loinc.part.related.code.mapping.file=AccessoryFiles/PartFile/PartRelatedCodeMapping.csv
# RSNA playbook
## File must be present
loinc.rsna.playbook.file=AccessoryFiles/LoincRsnaRadiologyPlaybook/LoincRsnaRadiologyPlaybook.csv
# Top 2000 codes - SI
## File must be present
loinc.top2000.common.lab.results.si.file=AccessoryFiles/Top2000Results/SI/Top2000CommonLabResultsSi.csv
# Top 2000 codes - US
## File must be present
loinc.top2000.common.lab.results.us.file=AccessoryFiles/Top2000Results/US/Top2000CommonLabResultsUs.csv
# Universal lab order ValueSet
## File must be present
loinc.universal.lab.order.valueset.file=AccessoryFiles/LoincUniversalLabOrdersValueSet/LoincUniversalLabOrdersValueSet.csv
################
### OPTIONAL ###
################
# This is the version identifier for the LOINC code system
## Key may be omitted if only a single version of LOINC is being kept.
loinc.codesystem.version=2.67
# This is the version identifier for the answer list file
## Key may be omitted
loinc.answerlist.version=Beta.1
# This is the version identifier for uploaded ConceptMap resources
## Key may be omitted
loinc.conceptmap.version=Beta.1
# Group
## Default value if key not provided: AccessoryFiles/GroupFile/Group.csv
## File may be omitted
loinc.group.file=AccessoryFiles/GroupFile/Group.csv
# Group terms
## Default value if key not provided: AccessoryFiles/GroupFile/GroupLoincTerms.csv
## File may be omitted
loinc.group.terms.file=AccessoryFiles/GroupFile/GroupLoincTerms.csv
# Parent group
## Default value if key not provided: AccessoryFiles/GroupFile/ParentGroup.csv
## File may be omitted
loinc.parent.group.file=AccessoryFiles/GroupFile/ParentGroup.csv

View File

@ -0,0 +1,87 @@
#################
### MANDATORY ###
#################
# This is the version identifier for the LOINC code system
## Key may be omitted if only a single version of LOINC is being kept.
loinc.codesystem.version=2.68
# Answer lists (ValueSets of potential answers/values for LOINC "questions")
## File must be present
loinc.answerlist.file=AccessoryFiles/AnswerFile/AnswerList.csv
# Answer list links (connects LOINC observation codes to answer list codes)
## File must be present
loinc.answerlist.link.file=AccessoryFiles/AnswerFile/LoincAnswerListLink.csv
# Document ontology
## File must be present
loinc.document.ontology.file=AccessoryFiles/DocumentOntology/DocumentOntology.csv
# LOINC codes
## File must be present
loinc.file=LoincTable/Loinc.csv
# LOINC hierarchy
## File must be present
loinc.hierarchy.file=AccessoryFiles/MultiAxialHierarchy/MultiAxialHierarchy.csv
# IEEE medical device codes
## File must be present
loinc.ieee.medical.device.code.mapping.table.file=AccessoryFiles/LoincIeeeMedicalDeviceCodeMappingTable/LoincIeeeMedicalDeviceCodeMappingTable.csv
# Imaging document codes
## File must be present
loinc.imaging.document.codes.file=AccessoryFiles/ImagingDocuments/ImagingDocumentCodes.csv
# Part
## File must be present
loinc.part.file=AccessoryFiles/PartFile/Part.csv
# Part link
## File must be present
loinc.part.link.primary.file=AccessoryFiles/PartFile/LoincPartLink_Primary.csv
loinc.part.link.supplementary.file=AccessoryFiles/PartFile/LoincPartLink_Supplementary.csv
# Part related code mapping
## File must be present
loinc.part.related.code.mapping.file=AccessoryFiles/PartFile/PartRelatedCodeMapping.csv
# RSNA playbook
## File must be present
loinc.rsna.playbook.file=AccessoryFiles/LoincRsnaRadiologyPlaybook/LoincRsnaRadiologyPlaybook.csv
# Top 2000 codes - SI
## File must be present
loinc.top2000.common.lab.results.si.file=AccessoryFiles/Top2000Results/SI/Top2000CommonLabResultsSi.csv
# Top 2000 codes - US
## File must be present
loinc.top2000.common.lab.results.us.file=AccessoryFiles/Top2000Results/US/Top2000CommonLabResultsUs.csv
# Universal lab order ValueSet
## File must be present
loinc.universal.lab.order.valueset.file=AccessoryFiles/LoincUniversalLabOrdersValueSet/LoincUniversalLabOrdersValueSet.csv
################
### OPTIONAL ###
################
# This is the version identifier for the answer list file
## Key may be omitted
loinc.answerlist.version=Beta.1
# This is the version identifier for uploaded ConceptMap resources
## Key may be omitted
loinc.conceptmap.version=Beta.1
# Group
## Default value if key not provided: AccessoryFiles/GroupFile/Group.csv
## File may be omitted
loinc.group.file=AccessoryFiles/GroupFile/Group.csv
# Group terms
## Default value if key not provided: AccessoryFiles/GroupFile/GroupLoincTerms.csv
## File may be omitted
loinc.group.terms.file=AccessoryFiles/GroupFile/GroupLoincTerms.csv
# Parent group
## Default value if key not provided: AccessoryFiles/GroupFile/ParentGroup.csv
## File may be omitted
loinc.parent.group.file=AccessoryFiles/GroupFile/ParentGroup.csv

View File

@ -58,6 +58,11 @@ public class BaseValidationSupportWrapper extends BaseValidationSupport {
return myWrap.validateCode(theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, theValueSetUrl); return myWrap.validateCode(theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, theValueSetUrl);
} }
@Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theVersion) {
return myWrap.lookupCode(theValidationSupportContext, theSystem, theCode, theVersion);
}
@Override @Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) { public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) {
return myWrap.lookupCode(theValidationSupportContext, theSystem, theCode); return myWrap.lookupCode(theValidationSupportContext, theSystem, theCode);

View File

@ -83,6 +83,12 @@ public class CachingValidationSupport extends BaseValidationSupportWrapper imple
return loadFromCache(myValidateCodeCache, key, t -> super.validateCode(theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, theValueSetUrl)); return loadFromCache(myValidateCodeCache, key, t -> super.validateCode(theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, theValueSetUrl));
} }
@Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theVersion) {
String key = "lookupCode " + theSystem + " " + theCode + " " + theVersion;
return loadFromCache(myLookupCodeCache, key, t -> super.lookupCode(theValidationSupportContext, theSystem, theCode, theVersion));
}
@Override @Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) { public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) {
String key = "lookupCode " + theSystem + " " + theCode; String key = "lookupCode " + theSystem + " " + theCode;

View File

@ -145,6 +145,12 @@ public class CommonCodeSystemsTerminologyService implements IValidationSupport {
} }
@Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theVersion) {
// For now Common Code Systems will not be versioned.
return lookupCode(theValidationSupportContext, theSystem, theCode);
}
@Override @Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) { public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) {

View File

@ -308,6 +308,12 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
.setMessage(message); .setMessage(message);
} }
@Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theVersion) {
// TODO: Add support for validating versioned codes as well.
return lookupCode(theValidationSupportContext, theSystem, theCode);
}
@Override @Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) { public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) {
return validateCode(theValidationSupportContext, new ConceptValidationOptions(), theSystem, theCode, null, null).asLookupCodeResult(theSystem, theCode); return validateCode(theValidationSupportContext, new ConceptValidationOptions(), theSystem, theCode, null, null).asLookupCodeResult(theSystem, theCode);
@ -470,6 +476,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
List<VersionIndependentConcept> nextCodeList = new ArrayList<>(); List<VersionIndependentConcept> nextCodeList = new ArrayList<>();
String system = nextInclude.getSystem(); String system = nextInclude.getSystem();
String systemVersion = nextInclude.getVersion();
if (isNotBlank(system)) { if (isNotBlank(system)) {
if (theWantSystem != null && !theWantSystem.equals(system)) { if (theWantSystem != null && !theWantSystem.equals(system)) {
@ -492,7 +499,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
if (theWantCode != null) { if (theWantCode != null) {
if (theValidationSupportContext.getRootValidationSupport().isCodeSystemSupported(theValidationSupportContext, system)) { if (theValidationSupportContext.getRootValidationSupport().isCodeSystemSupported(theValidationSupportContext, system)) {
LookupCodeResult lookup = theValidationSupportContext.getRootValidationSupport().lookupCode(theValidationSupportContext, system, theWantCode); LookupCodeResult lookup = theValidationSupportContext.getRootValidationSupport().lookupCode(theValidationSupportContext, system, theWantCode, systemVersion);
if (lookup != null && lookup.isFound()) { if (lookup != null && lookup.isFound()) {
CodeSystem.ConceptDefinitionComponent conceptDefinition = new CodeSystem.ConceptDefinitionComponent() CodeSystem.ConceptDefinitionComponent conceptDefinition = new CodeSystem.ConceptDefinitionComponent()
.addConcept() .addConcept()

View File

@ -257,6 +257,16 @@ public class ValidationSupportChain implements IValidationSupport {
return null; return null;
} }
@Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theVersion) {
for (IValidationSupport next : myChain) {
if (next.isCodeSystemSupported(theValidationSupportContext, theSystem)) {
return next.lookupCode(theValidationSupportContext, theSystem, theCode, theVersion);
}
}
return null;
}
@Override @Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) { public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) {
for (IValidationSupport next : myChain) { for (IValidationSupport next : myChain) {

View File

@ -254,7 +254,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest {
return retVal; return retVal;
} }
}); });
when(mockSupport.lookupCode(any(), any(), any())).thenAnswer(t -> { when(mockSupport.lookupCode(any(), any(), any(), any())).thenAnswer(t -> {
String system = t.getArgument(1, String.class); String system = t.getArgument(1, String.class);
String code = t.getArgument(2, String.class); String code = t.getArgument(2, String.class);
if (myValidConcepts.contains(system + "___" + code)) { if (myValidConcepts.contains(system + "___" + code)) {