From 5fe46be41cb8d0f455f3c5a32f3bc4a23fbaa2f2 Mon Sep 17 00:00:00 2001 From: ianmarshall Date: Thu, 24 Sep 2020 18:51:47 -0400 Subject: [PATCH] Validate code changes and new tests. --- .../dstu3/FhirResourceDaoCodeSystemDstu3.java | 6 +- .../dao/r4/FhirResourceDaoCodeSystemR4.java | 6 +- .../dao/r5/FhirResourceDaoCodeSystemR5.java | 6 +- .../BaseJpaResourceProviderValueSetDstu3.java | 16 +- .../BaseJpaResourceProviderCodeSystemR4.java | 9 - .../r4/BaseJpaResourceProviderValueSetR4.java | 16 +- .../BaseJpaResourceProviderCodeSystemR5.java | 9 - .../r5/BaseJpaResourceProviderValueSetR5.java | 18 +- .../fhir/jpa/term/BaseTermReadSvcImpl.java | 150 +- .../uhn/fhir/jpa/term/TermReadSvcDstu2.java | 14 +- .../uhn/fhir/jpa/term/TermReadSvcDstu3.java | 7 + .../ca/uhn/fhir/jpa/term/TermReadSvcR4.java | 6 + .../ca/uhn/fhir/jpa/term/TermReadSvcR5.java | 7 + ...rceProviderDstu3ValueSetVersionedTest.java | 471 +++-- ...sourceProviderR4ValueSetVersionedTest.java | 405 +++-- ...sourceProviderR5ValueSetVersionedTest.java | 1549 +++++++++++++++++ .../CommonCodeSystemsTerminologyService.java | 24 + ...oryTerminologyServerValidationSupport.java | 254 ++- 18 files changed, 2472 insertions(+), 501 deletions(-) create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetVersionedTest.java diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java index 3df67dd6bea..16f219a6f4c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java @@ -100,7 +100,11 @@ public class FhirResourceDaoCodeSystemDstu3 extends BaseHapiFhirResourceDao dao = (IFhirResourceDaoValueSet) getDao(); - IValidationSupport.CodeValidationResult result = dao.validateCode(url, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails); + UriType valueSetIdentifier; + if (theValueSetVersion != null) { + valueSetIdentifier = new UriType(theValueSetUrl.getValue() + "|" + theValueSetVersion); + } else { + valueSetIdentifier = theValueSetUrl; + } + UriType codeSystemIdentifier; + if (theSystemVersion != null) { + codeSystemIdentifier = new UriType(theSystem.getValue() + "|" + theSystemVersion); + } else { + codeSystemIdentifier = theSystem; + } + IValidationSupport.CodeValidationResult result = dao.validateCode(valueSetIdentifier, theId, theCode, codeSystemIdentifier, theDisplay, theCoding, theCodeableConcept, theRequestDetails); return (Parameters) BaseJpaResourceProviderValueSetDstu2.toValidateCodeResult(getContext(), result); } finally { endRequest(theServletRequest); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderCodeSystemR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderCodeSystemR4.java index a54a645ec59..a3e16a7fb3c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderCodeSystemR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderCodeSystemR4.java @@ -66,9 +66,6 @@ public class BaseJpaResourceProviderCodeSystemR4 extends JpaResourceProviderR4 dao = (IFhirResourceDaoValueSet) getDao(); - IValidationSupport.CodeValidationResult result = dao.validateCode(theValueSetUrl, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails); + UriType valueSetIdentifier; + if (theValueSetVersion != null) { + valueSetIdentifier = new UriType(theValueSetUrl.getValue() + "|" + theValueSetVersion); + } else { + valueSetIdentifier = theValueSetUrl; + } + UriType codeSystemIdentifier; + if (theSystemVersion != null) { + codeSystemIdentifier = new UriType(theSystem.getValue() + "|" + theSystemVersion); + } else { + codeSystemIdentifier = theSystem; + } + IValidationSupport.CodeValidationResult result = dao.validateCode(valueSetIdentifier, theId, theCode, codeSystemIdentifier, theDisplay, theCoding, theCodeableConcept, theRequestDetails); return (Parameters) BaseJpaResourceProviderValueSetDstu2.toValidateCodeResult(getContext(), result); } finally { endRequest(theServletRequest); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderCodeSystemR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderCodeSystemR5.java index 693ab7f1c69..91c0028d718 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderCodeSystemR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderCodeSystemR5.java @@ -66,9 +66,6 @@ public class BaseJpaResourceProviderCodeSystemR5 extends JpaResourceProviderR5 dao = (IFhirResourceDaoValueSet) getDao(); - IValidationSupport.CodeValidationResult result = dao.validateCode(theValueSetUrl, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails); + UriType valueSetIdentifier; + if (theValueSetVersion != null) { + valueSetIdentifier = new UriType(theValueSetUrl.getValue() + "|" + theValueSetVersion); + } else { + valueSetIdentifier = theValueSetUrl; + } + UriType codeSystemIdentifier; + if (theSystemVersion != null) { + codeSystemIdentifier = new UriType(theSystem.getValue() + "|" + theSystemVersion); + } else { + codeSystemIdentifier = theSystem; + } + IValidationSupport.CodeValidationResult result = dao.validateCode(valueSetIdentifier, theId, theCode, codeSystemIdentifier, theDisplay, theCoding, theCodeableConcept, theRequestDetails); return (Parameters) BaseJpaResourceProviderValueSetDstu2.toValidateCodeResult(getContext(), result); } finally { endRequest(theServletRequest); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java index d977c49cb1b..8d3912a5bbb 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java @@ -651,22 +651,22 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { */ private Boolean expandValueSetHandleIncludeOrExclude(@Nullable ValueSetExpansionOptions theExpansionOptions, IValueSetConceptAccumulator theValueSetCodeAccumulator, Set theAddedCodes, ValueSet.ConceptSetComponent theIncludeOrExclude, boolean theAdd, AtomicInteger theCodeCounter, int theQueryIndex, VersionIndependentConcept theWantConceptOrNull) { - String system = theIncludeOrExclude.getSystem(); - boolean hasSystem = isNotBlank(system); + String includeOrExcludeSystemUrl = theIncludeOrExclude.getSystem(); + boolean hasSystem = isNotBlank(includeOrExcludeSystemUrl); boolean hasValueSet = theIncludeOrExclude.getValueSet().size() > 0; if (hasSystem) { - if (theWantConceptOrNull != null && theWantConceptOrNull.getSystem() != null && !system.equals(theWantConceptOrNull.getSystem())) { + if (theWantConceptOrNull != null && theWantConceptOrNull.getSystem() != null && !includeOrExcludeSystemUrl.equals(theWantConceptOrNull.getSystem())) { return false; } - ourLog.debug("Starting {} expansion around CodeSystem: {}", (theAdd ? "inclusion" : "exclusion"), system); + ourLog.debug("Starting {} expansion around CodeSystem: {}", (theAdd ? "inclusion" : "exclusion"), includeOrExcludeSystemUrl); - TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(system); + TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(includeOrExcludeSystemUrl); if (cs != null) { - return expandValueSetHandleIncludeOrExcludeUsingDatabase(theValueSetCodeAccumulator, theAddedCodes, theIncludeOrExclude, theAdd, theCodeCounter, theQueryIndex, theWantConceptOrNull, system, cs); + return expandValueSetHandleIncludeOrExcludeUsingDatabase(theValueSetCodeAccumulator, theAddedCodes, theIncludeOrExclude, theAdd, theCodeCounter, theQueryIndex, theWantConceptOrNull, includeOrExcludeSystemUrl, cs); } else { @@ -679,7 +679,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } // No CodeSystem matching the URL found in the database. - CodeSystem codeSystemFromContext = fetchCanonicalCodeSystemFromCompleteContext(system); + CodeSystem codeSystemFromContext = fetchCanonicalCodeSystemFromCompleteContext(includeOrExcludeSystemUrl); if (codeSystemFromContext == null) { // This is a last ditch effort.. We don't have a CodeSystem resource for the desired CS, and we don't have @@ -704,7 +704,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { for (VersionIndependentConcept next : includedConcepts) { String nextSystem = next.getSystem(); if (nextSystem == null) { - nextSystem = system; + nextSystem = includeOrExcludeSystemUrl; } LookupCodeResult lookup = myValidationSupport.lookupCode(new ValidationSupportContext(provideValidationSupport()), nextSystem, next.getCode()); @@ -720,7 +720,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } } - String msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "expansionRefersToUnknownCs", system); + String msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "expansionRefersToUnknownCs", includeOrExcludeSystemUrl); if (provideExpansionOptions(theExpansionOptions).isFailOnMissingCodeSystem()) { throw new PreconditionFailedException(msg); } else { @@ -735,12 +735,12 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { for (ValueSet.ConceptReferenceComponent next : theIncludeOrExclude.getConcept()) { String nextCode = next.getCode(); if (theWantConceptOrNull == null || theWantConceptOrNull.getCode().equals(nextCode)) { - if (isNoneBlank(system, nextCode) && !theAddedCodes.contains(system + "|" + nextCode)) { + if (isNoneBlank(includeOrExcludeSystemUrl, nextCode) && !theAddedCodes.contains(includeOrExcludeSystemUrl + "|" + nextCode)) { CodeSystem.ConceptDefinitionComponent code = findCode(codeSystemFromContext.getConcept(), nextCode); if (code != null) { String display = code.getDisplay(); - addOrRemoveCode(theValueSetCodeAccumulator, theAddedCodes, theAdd, system, nextCode, display); + addOrRemoveCode(theValueSetCodeAccumulator, theAddedCodes, theAdd, includeOrExcludeSystemUrl, nextCode, display); } } @@ -748,7 +748,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } } else { List concept = codeSystemFromContext.getConcept(); - addConceptsToList(theValueSetCodeAccumulator, theAddedCodes, system, concept, theAdd, theWantConceptOrNull); + addConceptsToList(theValueSetCodeAccumulator, theAddedCodes, includeOrExcludeSystemUrl, concept, theAdd, theWantConceptOrNull); } return false; @@ -841,13 +841,13 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { /* * Filters */ - String canonicalSystem; + String codeSystemIdentifier; if (codeSystemVersion != null) { - canonicalSystem = theSystem + "|" + codeSystemVersion; + codeSystemIdentifier = theSystem + "|" + codeSystemVersion; } else { - canonicalSystem = theSystem; + codeSystemIdentifier = theSystem; } - handleFilters(bool, canonicalSystem, qb, theIncludeOrExclude); + handleFilters(bool, codeSystemIdentifier, qb, theIncludeOrExclude); Query luceneQuery = bool.createQuery(); @@ -956,15 +956,15 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } } - private void handleFilters(BooleanJunction theBool, String theCanonicalSystem, QueryBuilder theQb, ValueSet.ConceptSetComponent theIncludeOrExclude) { + private void handleFilters(BooleanJunction theBool, String theCodeSystemIdentifier, QueryBuilder theQb, ValueSet.ConceptSetComponent theIncludeOrExclude) { if (theIncludeOrExclude.getFilter().size() > 0) { for (ValueSet.ConceptSetFilterComponent nextFilter : theIncludeOrExclude.getFilter()) { - handleFilter(theCanonicalSystem, theQb, theBool, nextFilter); + handleFilter(theCodeSystemIdentifier, theQb, theBool, nextFilter); } } } - private void handleFilter(String theSystem, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter) { + private void handleFilter(String theCodeSystemIdentifier, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter) { if (isBlank(theFilter.getValue()) && theFilter.getOp() == null && isBlank(theFilter.getProperty())) { return; } @@ -980,23 +980,23 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { break; case "concept": case "code": - handleFilterConceptAndCode(theSystem, theQb, theBool, theFilter); + handleFilterConceptAndCode(theCodeSystemIdentifier, theQb, theBool, theFilter); break; case "parent": case "child": - isCodeSystemLoingOrThrowInvalidRequestException(theSystem, theFilter.getProperty()); + isCodeSystemLoingOrThrowInvalidRequestException(theCodeSystemIdentifier, theFilter.getProperty()); handleFilterLoincParentChild(theBool, theFilter); break; case "ancestor": - isCodeSystemLoingOrThrowInvalidRequestException(theSystem, theFilter.getProperty()); - handleFilterLoincAncestor(theSystem, theBool, theFilter); + isCodeSystemLoingOrThrowInvalidRequestException(theCodeSystemIdentifier, theFilter.getProperty()); + handleFilterLoincAncestor(theCodeSystemIdentifier, theBool, theFilter); break; case "descendant": - isCodeSystemLoingOrThrowInvalidRequestException(theSystem, theFilter.getProperty()); - handleFilterLoincDescendant(theSystem, theBool, theFilter); + isCodeSystemLoingOrThrowInvalidRequestException(theCodeSystemIdentifier, theFilter.getProperty()); + handleFilterLoincDescendant(theCodeSystemIdentifier, theBool, theFilter); break; case "copyright": - isCodeSystemLoingOrThrowInvalidRequestException(theSystem, theFilter.getProperty()); + isCodeSystemLoingOrThrowInvalidRequestException(theCodeSystemIdentifier, theFilter.getProperty()); handleFilterLoincCopyright(theBool, theFilter); break; default: @@ -1005,8 +1005,8 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } } - private void isCodeSystemLoingOrThrowInvalidRequestException(String theSystem, String theProperty) { - String systemUrl = getSystemFromCanonicalUrl(theSystem); + private void isCodeSystemLoingOrThrowInvalidRequestException(String theSystemIdentifier, String theProperty) { + String systemUrl = getUrlFromIdentifier(theSystemIdentifier); if (!isCodeSystemLoinc(systemUrl)) { throw new InvalidRequestException("Invalid filter, property " + theProperty + " is LOINC-specific and cannot be used with system: " + systemUrl); } @@ -1435,12 +1435,12 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } @Nullable - private TermCodeSystemVersion getCurrentCodeSystemVersion(String theUri) { - String myVersion = getVersionFromCanonicalUrl(theUri); - String key = theUri; + private TermCodeSystemVersion getCurrentCodeSystemVersion(String theCodeSystemIdentifier) { + String myVersion = getVersionFromIdentifier(theCodeSystemIdentifier); + String key = theCodeSystemIdentifier; TermCodeSystemVersion retVal = myCodeSystemCurrentVersionCache.get(key.toString(), t -> myTxTemplate.execute(tx -> { TermCodeSystemVersion csv = null; - TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(getSystemFromCanonicalUrl(theUri)); + TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(getUrlFromIdentifier(theCodeSystemIdentifier)); if (cs != null) { if (myVersion != null) { csv = myCodeSystemVersionDao.findByCodeSystemPidAndVersion(cs.getPid(), myVersion); @@ -1460,7 +1460,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { return retVal; } - private String getVersionFromCanonicalUrl(String theUri) { + private String getVersionFromIdentifier(String theUri) { String retVal = null; if (StringUtils.isNotEmpty((theUri))) { int versionSeparator = theUri.lastIndexOf('|'); @@ -1471,7 +1471,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { return retVal; } - private String getSystemFromCanonicalUrl(String theUri) { + private String getUrlFromIdentifier(String theUri) { String retVal = theUri; if (StringUtils.isNotEmpty((theUri))){ int versionSeparator = theUri.lastIndexOf('|'); @@ -1777,15 +1777,15 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } @Override - public CodeValidationResult validateCode(ConceptValidationOptions theOptions, IIdType theValueSetId, String theValueSetUrl, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) { + public CodeValidationResult validateCode(ConceptValidationOptions theOptions, IIdType theValueSetId, String theValueSetIdentifier, String theCodeSystemIdentifierToValidate, String theCodeToValidate, String theDisplayToValidate, IBaseDatatype theCodingToValidate, IBaseDatatype theCodeableConceptToValidate) { - CodeableConcept codeableConcept = toCanonicalCodeableConcept(theCodeableConcept); + CodeableConcept codeableConcept = toCanonicalCodeableConcept(theCodeableConceptToValidate); boolean haveCodeableConcept = codeableConcept != null && codeableConcept.getCoding().size() > 0; - Coding coding = toCanonicalCoding(theCoding); - boolean haveCoding = coding != null && coding.isEmpty() == false; + Coding canonicalCodingToValidate = toCanonicalCoding(theCodingToValidate); + boolean haveCoding = canonicalCodingToValidate != null && canonicalCodingToValidate.isEmpty() == false; - boolean haveCode = theCode != null && theCode.isEmpty() == false; + boolean haveCode = theCodeToValidate != null && theCodeToValidate.isEmpty() == false; if (!haveCodeableConcept && !haveCoding && !haveCode) { throw new InvalidRequestException("No code, coding, or codeableConcept provided to validate"); @@ -1794,38 +1794,54 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { throw new InvalidRequestException("$validate-code can only validate (system AND code) OR (coding) OR (codeableConcept)"); } - boolean haveIdentifierParam = isNotBlank(theValueSetUrl); - String valueSetUrl; + boolean haveIdentifierParam = isNotBlank(theValueSetIdentifier); + String valueSetIdentifier; if (theValueSetId != null) { IBaseResource valueSet = myDaoRegistry.getResourceDao("ValueSet").read(theValueSetId); - valueSetUrl = CommonCodeSystemsTerminologyService.getValueSetUrl(valueSet); + StringBuilder valueSetIdentifierBuilder = new StringBuilder(CommonCodeSystemsTerminologyService.getValueSetUrl(valueSet)); + String valueSetVersion = CommonCodeSystemsTerminologyService.getValueSetVersion(valueSet); + if (valueSetVersion != null) { + valueSetIdentifierBuilder.append("|").append(valueSetVersion); + } + valueSetIdentifier = valueSetIdentifierBuilder.toString(); + } else if (haveIdentifierParam) { - valueSetUrl = theValueSetUrl; + valueSetIdentifier = theValueSetIdentifier; } else { throw new InvalidRequestException("Either ValueSet ID or ValueSet identifier or system and code must be provided. Unable to validate."); } ValidationSupportContext validationContext = new ValidationSupportContext(provideValidationSupport()); - String code = theCode; - String system = theSystem; - String display = theDisplay; + String codeValueToValidate = theCodeToValidate; + String codeSystemIdentifierValueToValidate = theCodeSystemIdentifierToValidate; + String codeDisplayValueToValidate = theDisplayToValidate; if (haveCodeableConcept) { for (int i = 0; i < codeableConcept.getCoding().size(); i++) { Coding nextCoding = codeableConcept.getCoding().get(i); - CodeValidationResult nextValidation = validateCode(validationContext, theOptions, nextCoding.getSystem(), nextCoding.getCode(), nextCoding.getDisplay(), valueSetUrl); + String codeSystemIdentifier; + if (nextCoding.hasVersion()) { + codeSystemIdentifier = nextCoding.getSystem() + "|" + nextCoding.getVersion(); + } else { + codeSystemIdentifier = nextCoding.getSystem(); + } + CodeValidationResult nextValidation = validateCode(validationContext, theOptions, codeSystemIdentifier, nextCoding.getCode(), nextCoding.getDisplay(), valueSetIdentifier); if (nextValidation.isOk() || i == codeableConcept.getCoding().size() - 1) { return nextValidation; } } } else if (haveCoding) { - system = coding.getSystem(); - code = coding.getCode(); - display = coding.getDisplay(); + if (canonicalCodingToValidate.hasVersion()) { + codeSystemIdentifierValueToValidate = canonicalCodingToValidate.getSystem() + "|" + canonicalCodingToValidate.getVersion(); + } else { + codeSystemIdentifierValueToValidate = canonicalCodingToValidate.getSystem(); + } + codeValueToValidate = canonicalCodingToValidate.getCode(); + codeDisplayValueToValidate = canonicalCodingToValidate.getDisplay(); } - return validateCode(validationContext, theOptions, system, code, display, valueSetUrl); + return validateCode(validationContext, theOptions, codeSystemIdentifierValueToValidate, codeValueToValidate, codeDisplayValueToValidate, valueSetIdentifier); } private boolean isNotSafeToPreExpandValueSets() { @@ -1914,22 +1930,22 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { throw new InvalidRequestException("Unable to test subsumption across different code system versions"); } - String codeACanonicalUrl; + String codeASystemIdentifier; if (StringUtils.isNotEmpty(conceptA.getSystemVersion())) { - codeACanonicalUrl = conceptA.getSystem() + "|" + conceptA.getSystemVersion(); + codeASystemIdentifier = conceptA.getSystem() + "|" + conceptA.getSystemVersion(); } else { - codeACanonicalUrl = conceptA.getSystem(); + codeASystemIdentifier = conceptA.getSystem(); } - TermConcept codeA = findCode(codeACanonicalUrl, conceptA.getCode()) + TermConcept codeA = findCode(codeASystemIdentifier, conceptA.getCode()) .orElseThrow(() -> new InvalidRequestException("Unknown code: " + conceptA)); - String codeBCanonicalUrl; + String codeBSystemIdentifier; if (StringUtils.isNotEmpty(conceptB.getSystemVersion())) { - codeBCanonicalUrl = conceptB.getSystem() + "|" + conceptB.getSystemVersion(); + codeBSystemIdentifier = conceptB.getSystem() + "|" + conceptB.getSystemVersion(); } else { - codeBCanonicalUrl = conceptB.getSystem(); + codeBSystemIdentifier = conceptB.getSystem(); } - TermConcept codeB = findCode(codeBCanonicalUrl, conceptB.getCode()) + TermConcept codeB = findCode(codeBSystemIdentifier, conceptB.getCode()) .orElseThrow(() -> new InvalidRequestException("Unknown code: " + conceptB)); FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager); @@ -2405,6 +2421,9 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { @Nullable protected abstract Coding toCanonicalCoding(@Nullable IBaseDatatype theCoding); + @Nullable + protected abstract Coding toCanonicalCoding(@Nullable IBaseCoding theCoding); + @Nullable protected abstract CodeableConcept toCanonicalCodeableConcept(@Nullable IBaseDatatype theCodeableConcept); @@ -2496,14 +2515,15 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } @NotNull - private VersionIndependentConcept toConcept(IPrimitiveType theCodeType, IPrimitiveType theSystemType, IBaseCoding theCodingType) { + private VersionIndependentConcept toConcept(IPrimitiveType theCodeType, IPrimitiveType theCodeSystemIdentifierType, IBaseCoding theCodingType) { String code = theCodeType != null ? theCodeType.getValueAsString() : null; - String system = theSystemType != null ? getSystemFromCanonicalUrl(theSystemType.getValueAsString()): null; - String systemVersion = theSystemType != null ? getVersionFromCanonicalUrl(theSystemType.getValueAsString()): null; + String system = theCodeSystemIdentifierType != null ? getUrlFromIdentifier(theCodeSystemIdentifierType.getValueAsString()): null; + String systemVersion = theCodeSystemIdentifierType != null ? getVersionFromIdentifier(theCodeSystemIdentifierType.getValueAsString()): null; if (theCodingType != null) { - code = theCodingType.getCode(); - system = getSystemFromCanonicalUrl(theCodingType.getSystem()); - systemVersion = getVersionFromCanonicalUrl(theCodingType.getSystem()); + Coding canonicalizedCoding = toCanonicalCoding(theCodingType); + code = canonicalizedCoding.getCode(); + system = canonicalizedCoding.getSystem(); + systemVersion = canonicalizedCoding.getVersion(); } return new VersionIndependentConcept(system, code, null, systemVersion); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcDstu2.java index 7f15699e201..e0f0ef269b5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcDstu2.java @@ -27,6 +27,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt; import ca.uhn.fhir.model.dstu2.composite.CodingDt; import ca.uhn.fhir.util.VersionIndependentConcept; +import org.hl7.fhir.instance.model.api.IBaseCoding; import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.CodeSystem; @@ -148,6 +149,17 @@ public class TermReadSvcDstu2 extends BaseTermReadSvcImpl { return retVal; } + @Nullable + @Override + protected Coding toCanonicalCoding(@Nullable IBaseCoding theCoding) { + Coding retVal = null; + if (theCoding != null) { + CodingDt coding = (CodingDt) theCoding; + retVal = new Coding(coding.getSystem(), coding.getCode(), coding.getDisplay()); + } + return retVal; + } + @Nullable @Override protected CodeableConcept toCanonicalCodeableConcept(@Nullable IBaseDatatype theCodeableConcept) { @@ -157,7 +169,7 @@ public class TermReadSvcDstu2 extends BaseTermReadSvcImpl { CodeableConceptDt cc = (CodeableConceptDt) theCodeableConcept; outcome.setText(cc.getText()); for (CodingDt next : cc.getCoding()) { - outcome.addCoding(toCanonicalCoding(next)); + outcome.addCoding(toCanonicalCoding((IBaseDatatype)next)); } } return outcome; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcDstu3.java index 6b266ae52f3..4ab16d142d9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcDstu3.java @@ -20,6 +20,7 @@ import org.hl7.fhir.dstu3.model.CodeableConcept; import org.hl7.fhir.dstu3.model.Coding; import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.instance.model.api.IBaseCoding; import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.utilities.validation.ValidationOptions; @@ -112,6 +113,12 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation return VersionConvertor_30_40.convertCoding((org.hl7.fhir.dstu3.model.Coding) theCoding); } + @Override + @Nullable + protected org.hl7.fhir.r4.model.Coding toCanonicalCoding(IBaseCoding theCoding) { + return VersionConvertor_30_40.convertCoding((org.hl7.fhir.dstu3.model.Coding) theCoding); + } + @Override @Nullable protected org.hl7.fhir.r4.model.CodeableConcept toCanonicalCodeableConcept(IBaseDatatype theCoding) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcR4.java index 2075725dc2f..9cfe7c533b9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcR4.java @@ -8,6 +8,7 @@ import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4; import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException; +import org.hl7.fhir.instance.model.api.IBaseCoding; import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.CodeSystem; @@ -107,6 +108,11 @@ public class TermReadSvcR4 extends BaseTermReadSvcImpl implements ITermReadSvcR4 return (Coding) theCoding; } + @Override + protected Coding toCanonicalCoding(IBaseCoding theCoding) { + return (Coding) theCoding; + } + @Override protected CodeableConcept toCanonicalCodeableConcept(IBaseDatatype theCodeableConcept) { return (CodeableConcept) theCodeableConcept; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcR5.java index 2fbda8d9d88..8303aea044e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcR5.java @@ -12,6 +12,7 @@ import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException; import ca.uhn.fhir.util.ValidateUtil; import org.hl7.fhir.convertors.VersionConvertor_40_50; import org.hl7.fhir.convertors.conv40_50.CodeSystem40_50; +import org.hl7.fhir.instance.model.api.IBaseCoding; import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r5.model.CodeSystem; @@ -111,6 +112,12 @@ public class TermReadSvcR5 extends BaseTermReadSvcImpl implements IValidationSup return VersionConvertor_40_50.convertCoding((Coding) theCoding); } + @Override + @Nullable + protected org.hl7.fhir.r4.model.Coding toCanonicalCoding(IBaseCoding theCoding) { + return VersionConvertor_40_50.convertCoding((Coding) theCoding); + } + @Override @Nullable protected org.hl7.fhir.r4.model.CodeableConcept toCanonicalCodeableConcept(IBaseDatatype theCoding) { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetVersionedTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetVersionedTest.java index 0238206b51f..b92cf6566d1 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetVersionedTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetVersionedTest.java @@ -29,6 +29,10 @@ import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.dstu3.model.ValueSet.FilterOperator; import org.hl7.fhir.dstu3.model.codesystems.HttpVerb; +import org.hl7.fhir.dstu3.model.BooleanType; +import org.hl7.fhir.dstu3.model.CodeType; +import org.hl7.fhir.dstu3.model.CodeableConcept; +import org.hl7.fhir.dstu3.model.Coding; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.springframework.data.domain.PageRequest; @@ -49,6 +53,7 @@ import static org.hamcrest.Matchers.containsStringIgnoringCase; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.stringContainsInOrder; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -116,21 +121,16 @@ public class ResourceProviderDstu3ValueSetVersionedTest extends BaseResourceProv private void loadAndPersistValueSet() throws IOException { ValueSet valueSet = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml"); - persistTwoVersionsOfValueSet(valueSet, HttpVerb.POST); - } - - @SuppressWarnings("EnumSwitchStatementWhichMissesCases") - private void persistTwoVersionsOfValueSet(ValueSet theValueSet, HttpVerb theVerb) { - theValueSet.setVersion("1"); - theValueSet.setId("ValueSet/vs1"); - theValueSet.getCompose().getInclude().get(0).setVersion("1"); - myExtensionalVsId_v1 = persistSingleValueSet(theValueSet, theVerb); + valueSet.setVersion("1"); + valueSet.setId("ValueSet/vs1"); + valueSet.getCompose().getInclude().get(0).setVersion("1"); + myExtensionalVsId_v1 = persistSingleValueSet(valueSet, HttpVerb.POST); myExtensionalVsIdOnResourceTable_v1 = myValueSetDao.readEntity(myExtensionalVsId_v1, null).getId(); - theValueSet.setVersion("2"); - theValueSet.setId("ValueSet/vs2"); - theValueSet.getCompose().getInclude().get(0).setVersion("2"); - myExtensionalVsId_v2 = persistSingleValueSet(theValueSet, theVerb); + valueSet.setVersion("2"); + valueSet.setId("ValueSet/vs2"); + valueSet.getCompose().getInclude().get(0).setVersion("2"); + myExtensionalVsId_v2 = persistSingleValueSet(valueSet, HttpVerb.POST); myExtensionalVsIdOnResourceTable_v2 = myValueSetDao.readEntity(myExtensionalVsId_v2, null).getId(); } @@ -138,6 +138,8 @@ public class ResourceProviderDstu3ValueSetVersionedTest extends BaseResourceProv private IIdType persistSingleValueSet(ValueSet theValueSet, HttpVerb theVerb) { final IIdType[] vsId = new IIdType[1]; switch (theVerb) { + case GET: + break; case POST: new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override @@ -154,6 +156,8 @@ public class ResourceProviderDstu3ValueSetVersionedTest extends BaseResourceProv } }); break; + case DELETE: + case NULL: default: throw new IllegalArgumentException("HTTP verb is not supported: " + theVerb); } @@ -1053,7 +1057,7 @@ public class ResourceProviderDstu3ValueSetVersionedTest extends BaseResourceProv TermValueSetConcept concept = termValueSet.getConcepts().get(0); ourLog.info("Concept:\n" + concept.toString()); - assertEquals("http://acme.org", concept.getSystem()); + assertEquals("http://acme.org|1", concept.getSystem()); assertEquals("8450-9", concept.getCode()); assertEquals("Systolic blood pressure--expiration", concept.getDisplay()); assertEquals(2, concept.getDesignations().size()); @@ -1075,7 +1079,7 @@ public class ResourceProviderDstu3ValueSetVersionedTest extends BaseResourceProv concept = termValueSet.getConcepts().get(1); ourLog.info("Concept:\n" + concept.toString()); - assertEquals("http://acme.org", concept.getSystem()); + assertEquals("http://acme.org|1", concept.getSystem()); assertEquals("11378-7", concept.getCode()); assertEquals("Systolic blood pressure at First encounter", concept.getDisplay()); assertEquals(0, concept.getDesignations().size()); @@ -1085,7 +1089,7 @@ public class ResourceProviderDstu3ValueSetVersionedTest extends BaseResourceProv concept = termValueSet.getConcepts().get(22); ourLog.info("Concept:\n" + concept.toString()); - assertEquals("http://acme.org", concept.getSystem()); + assertEquals("http://acme.org|1", concept.getSystem()); assertEquals("8491-3", concept.getCode()); assertEquals("Systolic blood pressure 1 hour minimum", concept.getDisplay()); assertEquals(1, concept.getDesignations().size()); @@ -1100,7 +1104,7 @@ public class ResourceProviderDstu3ValueSetVersionedTest extends BaseResourceProv concept = termValueSet.getConcepts().get(23); ourLog.info("Concept:\n" + concept.toString()); - assertEquals("http://acme.org", concept.getSystem()); + assertEquals("http://acme.org|1", concept.getSystem()); assertEquals("8492-1", concept.getCode()); assertEquals("Systolic blood pressure 8 hour minimum", concept.getDisplay()); assertEquals(0, concept.getDesignations().size()); @@ -1126,7 +1130,7 @@ public class ResourceProviderDstu3ValueSetVersionedTest extends BaseResourceProv TermValueSetConcept concept = termValueSet.getConcepts().get(0); ourLog.info("Concept:\n" + concept.toString()); - assertEquals("http://acme.org", concept.getSystem()); + assertEquals("http://acme.org|2", concept.getSystem()); assertEquals("8450-9", concept.getCode()); assertEquals("Systolic blood pressure--expiration v2", concept.getDisplay()); assertEquals(2, concept.getDesignations().size()); @@ -1148,7 +1152,7 @@ public class ResourceProviderDstu3ValueSetVersionedTest extends BaseResourceProv concept = termValueSet.getConcepts().get(1); ourLog.info("Concept:\n" + concept.toString()); - assertEquals("http://acme.org", concept.getSystem()); + assertEquals("http://acme.org|2", concept.getSystem()); assertEquals("11378-7", concept.getCode()); assertEquals("Systolic blood pressure at First encounter v2", concept.getDisplay()); assertEquals(0, concept.getDesignations().size()); @@ -1158,7 +1162,7 @@ public class ResourceProviderDstu3ValueSetVersionedTest extends BaseResourceProv concept = termValueSet.getConcepts().get(22); ourLog.info("Concept:\n" + concept.toString()); - assertEquals("http://acme.org", concept.getSystem()); + assertEquals("http://acme.org|2", concept.getSystem()); assertEquals("8491-3", concept.getCode()); assertEquals("Systolic blood pressure 1 hour minimum v2", concept.getDisplay()); assertEquals(1, concept.getDesignations().size()); @@ -1173,161 +1177,16 @@ public class ResourceProviderDstu3ValueSetVersionedTest extends BaseResourceProv concept = termValueSet.getConcepts().get(23); ourLog.info("Concept:\n" + concept.toString()); - assertEquals("http://acme.org", concept.getSystem()); + assertEquals("http://acme.org|2", concept.getSystem()); assertEquals("8492-1", concept.getCode()); assertEquals("Systolic blood pressure 8 hour minimum v2", concept.getDisplay()); assertEquals(0, concept.getDesignations().size()); assertEquals(23, concept.getOrder()); }); } -/* - @Test - public void testValidateCodeOperationByCodeAndSystemInstance() throws Exception { - loadAndPersistCodeSystemAndValueSet(); - - Parameters respParam = ourClient - .operation() - .onInstance(myExtensionalVsId_v1) - .named("validate-code") - .withParameter(Parameters.class, "code", new CodeType("8495-4")) - .andParameter("system", new UriType("http://acme.org")) - .execute(); - - String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); - ourLog.info(resp); - - assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); - } @Test - public void testValidateCodeOperationByCodeAndSystemInstanceOnType() throws IOException { - createLocalCs(); - createLocalVsWithIncludeConcept(); - - String url = ourServerBase + - "/ValueSet/" + myLocalValueSetId_v1.getIdPart() + "/$validate-code?system=" + - UrlUtil.escapeUrlParam(URL_MY_CODE_SYSTEM) + - "&code=AA"; - - HttpGet request = new HttpGet(url); - request.addHeader("Accept", "application/fhir+json"); - try (CloseableHttpResponse response = ourHttpClient.execute(request)) { - String respString = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8); - ourLog.info(respString); - - Parameters respParam = myFhirCtx.newJsonParser().parseResource(Parameters.class, respString); - assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); - } - } - - @Test - public void testValidateCodeOperationByCodeAndSystemInstanceOnInstance() throws IOException { - createLocalCs(); - createLocalVsWithIncludeConcept(); - - String url = ourServerBase + - "/ValueSet/" + myLocalValueSetId_v1.getIdPart() + "/$validate-code?system=" + - UrlUtil.escapeUrlParam(URL_MY_CODE_SYSTEM) + - "&code=AA"; - - ourLog.info("* Requesting: {}", url); - - HttpGet request = new HttpGet(url); - request.addHeader("Accept", "application/fhir+json"); - try (CloseableHttpResponse response = ourHttpClient.execute(request)) { - String respString = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8); - ourLog.info(respString); - - Parameters respParam = myFhirCtx.newJsonParser().parseResource(Parameters.class, respString); - assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); - } - } - - @Test - public void testValidateCodeOperationByCodeAndSystemType() throws Exception { - loadAndPersistCodeSystemAndValueSet(); - - Parameters respParam = ourClient - .operation() - .onInstance(myExtensionalVsId_v1) - .named("validate-code") - .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(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); - } - - @Test - public void testValidateCodeOperationNoValueSetProvided() throws Exception { - loadAndPersistCodeSystemAndValueSet(); - - try { - ourClient - .operation() - .onType(ValueSet.class) - .named("validate-code") - .withParameter(Parameters.class, "code", new CodeType("8450-9")) - .andParameter("system", new UriType("http://acme.org")) - .execute(); - fail(); - } catch (InvalidRequestException e) { - assertEquals("HTTP 400 Bad Request: Either ValueSet ID or ValueSet identifier or system and code must be provided. Unable to validate.", e.getMessage()); - } - } - - @Test - public void testValidateCodeOperationOnInstanceWithIsAExpansion() throws IOException { - CodeSystem cs = new CodeSystem(); - cs.setUrl("http://mycs"); - cs.setContent(CodeSystemContentMode.COMPLETE); - cs.setHierarchyMeaning(CodeSystem.CodeSystemHierarchyMeaning.ISA); - cs.setStatus(Enumerations.PublicationStatus.ACTIVE); - ConceptDefinitionComponent parentA = cs.addConcept().setCode("ParentA").setDisplay("Parent A"); - parentA.addConcept().setCode("ChildAA").setDisplay("Child AA"); - myCodeSystemDao.create(cs); - - ValueSet vs = new ValueSet(); - vs.setUrl("http://myvs"); - vs.getCompose() - .addInclude() - .setSystem("http://mycs") - .addFilter() - .setOp(FilterOperator.ISA) - .setProperty("concept") - .setValue("ParentA"); - IIdType vsId = myValueSetDao.create(vs).getId().toUnqualifiedVersionless(); - - HttpGet expandGet = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$expand?_pretty=true"); - try (CloseableHttpResponse status = ourHttpClient.execute(expandGet)) { - String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8); - ourLog.info("Response: {}", response); - } - - HttpGet validateCodeGet = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$validate-code?system=http://mycs&code=ChildAA&_pretty=true"); - try (CloseableHttpResponse status = ourHttpClient.execute(validateCodeGet)) { - String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8); - ourLog.info("Response: {}", response); - Parameters output = myFhirCtx.newXmlParser().parseResource(Parameters.class, response); - assertEquals(true, output.getParameterBool("result")); - } - - HttpGet validateCodeGet2 = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$validate-code?system=http://mycs&code=FOO&_pretty=true"); - try (CloseableHttpResponse status = ourHttpClient.execute(validateCodeGet2)) { - String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8); - ourLog.info("Response: {}", response); - Parameters output = myFhirCtx.newXmlParser().parseResource(Parameters.class, response); - assertEquals(false, output.getParameterBool("result")); - } - - } -*/ - - @Test - public void testCreateDuplicatValueSetVersion() { + public void testCreateDuplicateValueSetVersion() { createExternalCsAndLocalVs(); try { persistLocalVs(createLocalVs(URL_MY_CODE_SYSTEM, "1")); @@ -1338,6 +1197,284 @@ public class ResourceProviderDstu3ValueSetVersionedTest extends BaseResourceProv } + @Test + public void testValidateCodeOperationByCodeAndSystem() throws Exception { + loadAndPersistCodeSystemAndValueSet(); + + // With correct system version specified. Should pass. + Parameters respParam = ourClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "code", new CodeType("8495-4")) + .andParameter("system", new UriType("http://acme.org")) + .andParameter("systemVersion", new StringType("1")) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("1")) + .execute(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + respParam = ourClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "code", new CodeType("8495-4")) + .andParameter("system", new UriType("http://acme.org")) + .andParameter("systemVersion", new StringType("2")) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + // With incorrect version specified. Should fail. + respParam = ourClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "code", new CodeType("8495-4")) + .andParameter("system", new UriType("http://acme.org")) + .andParameter("systemVersion", new StringType("1")) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + respParam = ourClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "code", new CodeType("8495-4")) + .andParameter("system", new UriType("http://acme.org")) + .andParameter("systemVersion", new StringType("2")) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("1")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + } + + @Test + public void testValidateCodeOperationOnInstanceByCodeAndSystem() throws Exception { + loadAndPersistCodeSystemAndValueSet(); + + // With correct system version specified. Should pass. + Parameters respParam = ourClient + .operation() + .onInstance(myExtensionalVsId_v1) + .named("validate-code") + .withParameter(Parameters.class, "code", new CodeType("8495-4")) + .andParameter("system", new UriType("http://acme.org")) + .andParameter("systemVersion", new StringType("1")) + .execute(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + respParam = ourClient + .operation() + .onInstance(myExtensionalVsId_v2) + .named("validate-code") + .withParameter(Parameters.class, "code", new CodeType("8495-4")) + .andParameter("system", new UriType("http://acme.org")) + .andParameter("systemVersion", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + // With incorrect version specified. Should fail. + respParam = ourClient + .operation() + .onInstance(myExtensionalVsId_v1) + .named("validate-code") + .withParameter(Parameters.class, "code", new CodeType("8495-4")) + .andParameter("system", new UriType("http://acme.org")) + .andParameter("systemVersion", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + respParam = ourClient + .operation() + .onInstance(myExtensionalVsId_v2) + .named("validate-code") + .withParameter(Parameters.class, "code", new CodeType("8495-4")) + .andParameter("system", new UriType("http://acme.org")) + .andParameter("systemVersion", new StringType("1")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + } + + @Test + public void testValidateCodeOperationByCoding() throws Exception { + loadAndPersistCodeSystemAndValueSet(); + + Coding codingToValidate_v1 = new Coding("http://acme.org", "8495-4", "Systolic blood pressure 24 hour minimum"); + codingToValidate_v1.setVersion("1"); + + Coding codingToValidate_v2 = new Coding("http://acme.org", "8495-4", "Systolic blood pressure 24 hour minimum v2"); + codingToValidate_v2.setVersion("2"); + + // With correct system version specified. Should pass. + Parameters respParam = ourClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "coding", codingToValidate_v1) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("1")) + .execute(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + respParam = ourClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "coding", codingToValidate_v2) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + // With incorrect version specified. Should fail. + respParam = ourClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "coding", codingToValidate_v1) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + respParam = ourClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "coding", codingToValidate_v2) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("1")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + } + + @Test + public void testValidateCodeOperationByCodeableConcept() throws Exception { + loadAndPersistCodeSystemAndValueSet(); + + Coding codingToValidate = new Coding("http://acme.org", "8495-4", "Systolic blood pressure 24 hour minimum"); + codingToValidate.setVersion("1"); + CodeableConcept codeableConceptToValidate_v1 = new CodeableConcept(codingToValidate); + + codingToValidate = new Coding("http://acme.org", "8495-4", "Systolic blood pressure 24 hour minimum v2"); + codingToValidate.setVersion("2"); + CodeableConcept codeableConceptToValidate_v2 = new CodeableConcept(codingToValidate); + + // With correct system version specified. Should pass. + Parameters respParam = ourClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "codeableConcept", codeableConceptToValidate_v1) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("1")) + .execute(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + respParam = ourClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "codeableConcept", codeableConceptToValidate_v2) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + // With incorrect version specified. Should fail. + respParam = ourClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "codeableConcept", codeableConceptToValidate_v1) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + respParam = ourClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "codeableConcept", codeableConceptToValidate_v2) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("1")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + } + @AfterEach public void afterResetPreExpansionDefault() { myDaoConfig.setPreExpandValueSets(new DaoConfig().isPreExpandValueSets()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetVersionedTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetVersionedTest.java index 88381769748..613f33351ac 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetVersionedTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetVersionedTest.java @@ -13,12 +13,11 @@ import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.UrlUtil; -import org.apache.commons.io.Charsets; +import com.google.common.base.Charsets; import org.apache.commons.io.IOUtils; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; @@ -29,6 +28,8 @@ import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem.CodeSystemContentMode; import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; import org.hl7.fhir.r4.model.CodeType; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Parameters; @@ -59,6 +60,7 @@ import static org.hamcrest.Matchers.containsStringIgnoringCase; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.stringContainsInOrder; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -126,21 +128,16 @@ public class ResourceProviderR4ValueSetVersionedTest extends BaseResourceProvide private void loadAndPersistValueSet() throws IOException { ValueSet valueSet = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml"); - persistTwoVersionsOfValueSet(valueSet, HttpVerb.POST); - } - - @SuppressWarnings("EnumSwitchStatementWhichMissesCases") - private void persistTwoVersionsOfValueSet(ValueSet theValueSet, HttpVerb theVerb) { - theValueSet.setVersion("1"); - theValueSet.setId("ValueSet/vs1"); - theValueSet.getCompose().getInclude().get(0).setVersion("1"); - myExtensionalVsId_v1 = persistSingleValueSet(theValueSet, theVerb); + valueSet.setVersion("1"); + valueSet.setId("ValueSet/vs1"); + valueSet.getCompose().getInclude().get(0).setVersion("1"); + myExtensionalVsId_v1 = persistSingleValueSet(valueSet, HttpVerb.POST); myExtensionalVsIdOnResourceTable_v1 = myValueSetDao.readEntity(myExtensionalVsId_v1, null).getId(); - theValueSet.setVersion("2"); - theValueSet.setId("ValueSet/vs2"); - theValueSet.getCompose().getInclude().get(0).setVersion("2"); - myExtensionalVsId_v2 = persistSingleValueSet(theValueSet, theVerb); + valueSet.setVersion("2"); + valueSet.setId("ValueSet/vs2"); + valueSet.getCompose().getInclude().get(0).setVersion("2"); + myExtensionalVsId_v2 = persistSingleValueSet(valueSet, HttpVerb.POST); myExtensionalVsIdOnResourceTable_v2 = myValueSetDao.readEntity(myExtensionalVsId_v2, null).getId(); } @@ -148,6 +145,9 @@ public class ResourceProviderR4ValueSetVersionedTest extends BaseResourceProvide private IIdType persistSingleValueSet(ValueSet theValueSet, HttpVerb theVerb) { final IIdType[] vsId = new IIdType[1]; switch (theVerb) { + case GET: + case HEAD: + break; case POST: new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { @Override @@ -164,6 +164,9 @@ public class ResourceProviderR4ValueSetVersionedTest extends BaseResourceProvide } }); break; + case DELETE: + case PATCH: + case NULL: default: throw new IllegalArgumentException("HTTP verb is not supported: " + theVerb); } @@ -188,12 +191,6 @@ public class ResourceProviderR4ValueSetVersionedTest extends BaseResourceProvide } -// private void createExternalCsAndLocalVsWithUnknownCode() { -// String codeSystemUrl = createExternalCs(); - -// createLocalVsWithUnknownCode(codeSystemUrl); -// } - private void createLocalCs() { CodeSystem codeSystem = new CodeSystem(); codeSystem.setUrl(URL_MY_CODE_SYSTEM); @@ -250,27 +247,6 @@ public class ResourceProviderR4ValueSetVersionedTest extends BaseResourceProvide return myValueSetDao.create(theValueSet, mySrd).getId().toUnqualifiedVersionless(); } - private void createLocalVsWithUnknownCode(String codeSystemUrl) { - myLocalVs_v1 = new ValueSet(); - myLocalVs_v1.setUrl(URL_MY_VALUE_SET); - myLocalVs_v1.setVersion("1"); - ConceptSetComponent include = myLocalVs_v1.getCompose().addInclude(); - include.setSystem(codeSystemUrl); - include.setSystem("1"); - include.addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue("childFOOOOOOO"); - myLocalValueSetId_v1 = myValueSetDao.create(myLocalVs_v1, mySrd).getId().toUnqualifiedVersionless(); - - myLocalVs_v2 = new ValueSet(); - myLocalVs_v2.setUrl(URL_MY_VALUE_SET); - myLocalVs_v2.setVersion("2"); - include = myLocalVs_v2.getCompose().addInclude(); - include.setSystem(codeSystemUrl); - include.setSystem("2"); - include.addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue("childFOOOOOOO"); - myLocalValueSetId_v2 = myValueSetDao.create(myLocalVs_v2, mySrd).getId().toUnqualifiedVersionless(); - - } - @Test public void testExpandById() throws Exception { loadAndPersistCodeSystemAndValueSet(); @@ -1201,151 +1177,284 @@ public class ResourceProviderR4ValueSetVersionedTest extends BaseResourceProvide assertEquals(23, concept.getOrder()); }); } -/* + @Test - public void testValidateCodeOperationByCodeAndSystemInstance() throws Exception { + public void testValidateCodeOperationByCodeAndSystem() throws Exception { loadAndPersistCodeSystemAndValueSet(); + // With correct system version specified. Should pass. + Parameters respParam = myClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "code", new CodeType("8495-4")) + .andParameter("system", new UriType("http://acme.org")) + .andParameter("systemVersion", new StringType("1")) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("1")) + .execute(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "code", new CodeType("8495-4")) + .andParameter("system", new UriType("http://acme.org")) + .andParameter("systemVersion", new StringType("2")) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + // With incorrect version specified. Should fail. + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "code", new CodeType("8495-4")) + .andParameter("system", new UriType("http://acme.org")) + .andParameter("systemVersion", new StringType("1")) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "code", new CodeType("8495-4")) + .andParameter("system", new UriType("http://acme.org")) + .andParameter("systemVersion", new StringType("2")) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("1")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + } + + @Test + public void testValidateCodeOperationOnInstanceByCodeAndSystem() throws Exception { + loadAndPersistCodeSystemAndValueSet(); + + // With correct system version specified. Should pass. Parameters respParam = myClient .operation() .onInstance(myExtensionalVsId_v1) .named("validate-code") .withParameter(Parameters.class, "code", new CodeType("8495-4")) .andParameter("system", new UriType("http://acme.org")) + .andParameter("systemVersion", new StringType("1")) .execute(); String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); ourLog.info(resp); - assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); - } + assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); - @Test - public void testValidateCodeOperationByCodeAndSystemInstanceOnType() throws IOException { - createLocalCs(); - createLocalVsWithIncludeConcept(); + respParam = myClient + .operation() + .onInstance(myExtensionalVsId_v2) + .named("validate-code") + .withParameter(Parameters.class, "code", new CodeType("8495-4")) + .andParameter("system", new UriType("http://acme.org")) + .andParameter("systemVersion", new StringType("2")) + .execute(); - String url = ourServerBase + - "/ValueSet/" + myLocalValueSetId_v1.getIdPart() + "/$validate-code?system=" + - UrlUtil.escapeUrlParam(URL_MY_CODE_SYSTEM) + - "&code=AA"; + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); - HttpGet request = new HttpGet(url); - request.addHeader("Accept", "application/fhir+json"); - try (CloseableHttpResponse response = ourHttpClient.execute(request)) { - String respString = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8); - ourLog.info(respString); + assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); - Parameters respParam = myFhirCtx.newJsonParser().parseResource(Parameters.class, respString); - assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); - } - } - - @Test - public void testValidateCodeOperationByCodeAndSystemInstanceOnInstance() throws IOException { - createLocalCs(); - createLocalVsWithIncludeConcept(); - - String url = ourServerBase + - "/ValueSet/" + myLocalValueSetId_v1.getIdPart() + "/$validate-code?system=" + - UrlUtil.escapeUrlParam(URL_MY_CODE_SYSTEM) + - "&code=AA"; - - ourLog.info("* Requesting: {}", url); - - HttpGet request = new HttpGet(url); - request.addHeader("Accept", "application/fhir+json"); - try (CloseableHttpResponse response = ourHttpClient.execute(request)) { - String respString = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8); - ourLog.info(respString); - - Parameters respParam = myFhirCtx.newJsonParser().parseResource(Parameters.class, respString); - assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); - } - } - - @Test - public void testValidateCodeOperationByCodeAndSystemType() throws Exception { - loadAndPersistCodeSystemAndValueSet(); - - Parameters respParam = myClient + // With incorrect version specified. Should fail. + respParam = myClient .operation() .onInstance(myExtensionalVsId_v1) .named("validate-code") - .withParameter(Parameters.class, "code", new CodeType("8450-9")) + .withParameter(Parameters.class, "code", new CodeType("8495-4")) .andParameter("system", new UriType("http://acme.org")) + .andParameter("systemVersion", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + respParam = myClient + .operation() + .onInstance(myExtensionalVsId_v2) + .named("validate-code") + .withParameter(Parameters.class, "code", new CodeType("8495-4")) + .andParameter("system", new UriType("http://acme.org")) + .andParameter("systemVersion", new StringType("1")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + } + + @Test + public void testValidateCodeOperationByCoding() throws Exception { + loadAndPersistCodeSystemAndValueSet(); + + Coding codingToValidate_v1 = new Coding("http://acme.org", "8495-4", "Systolic blood pressure 24 hour minimum"); + codingToValidate_v1.setVersion("1"); + + Coding codingToValidate_v2 = new Coding("http://acme.org", "8495-4", "Systolic blood pressure 24 hour minimum v2"); + codingToValidate_v2.setVersion("2"); + + // With correct system version specified. Should pass. + Parameters respParam = myClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "coding", codingToValidate_v1) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("1")) .execute(); String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); ourLog.info(resp); - assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "coding", codingToValidate_v2) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + // With incorrect version specified. Should fail. + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "coding", codingToValidate_v1) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "coding", codingToValidate_v2) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("1")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + } @Test - public void testValidateCodeOperationNoValueSetProvided() throws Exception { + public void testValidateCodeOperationByCodeableConcept() throws Exception { loadAndPersistCodeSystemAndValueSet(); - try { - myClient - .operation() - .onType(ValueSet.class) - .named("validate-code") - .withParameter(Parameters.class, "code", new CodeType("8450-9")) - .andParameter("system", new UriType("http://acme.org")) - .execute(); - fail(); - } catch (InvalidRequestException e) { - assertEquals("HTTP 400 Bad Request: Either ValueSet ID or ValueSet identifier or system and code must be provided. Unable to validate.", e.getMessage()); - } - } + Coding codingToValidate = new Coding("http://acme.org", "8495-4", "Systolic blood pressure 24 hour minimum"); + codingToValidate.setVersion("1"); + CodeableConcept codeableConceptToValidate_v1 = new CodeableConcept(codingToValidate); - @Test - public void testValidateCodeOperationOnInstanceWithIsAExpansion() throws IOException { - CodeSystem cs = new CodeSystem(); - cs.setUrl("http://mycs"); - cs.setContent(CodeSystemContentMode.COMPLETE); - cs.setHierarchyMeaning(CodeSystem.CodeSystemHierarchyMeaning.ISA); - cs.setStatus(Enumerations.PublicationStatus.ACTIVE); - ConceptDefinitionComponent parentA = cs.addConcept().setCode("ParentA").setDisplay("Parent A"); - parentA.addConcept().setCode("ChildAA").setDisplay("Child AA"); - myCodeSystemDao.create(cs); + codingToValidate = new Coding("http://acme.org", "8495-4", "Systolic blood pressure 24 hour minimum v2"); + codingToValidate.setVersion("2"); + CodeableConcept codeableConceptToValidate_v2 = new CodeableConcept(codingToValidate); - ValueSet vs = new ValueSet(); - vs.setUrl("http://myvs"); - vs.getCompose() - .addInclude() - .setSystem("http://mycs") - .addFilter() - .setOp(FilterOperator.ISA) - .setProperty("concept") - .setValue("ParentA"); - IIdType vsId = myValueSetDao.create(vs).getId().toUnqualifiedVersionless(); + // With correct system version specified. Should pass. + Parameters respParam = myClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "codeableConcept", codeableConceptToValidate_v1) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("1")) + .execute(); - HttpGet expandGet = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$expand?_pretty=true"); - try (CloseableHttpResponse status = ourHttpClient.execute(expandGet)) { - String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8); - ourLog.info("Response: {}", response); - } + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); - HttpGet validateCodeGet = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$validate-code?system=http://mycs&code=ChildAA&_pretty=true"); - try (CloseableHttpResponse status = ourHttpClient.execute(validateCodeGet)) { - String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8); - ourLog.info("Response: {}", response); - Parameters output = myFhirCtx.newXmlParser().parseResource(Parameters.class, response); - assertEquals(true, output.getParameterBool("result")); - } + assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); - HttpGet validateCodeGet2 = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$validate-code?system=http://mycs&code=FOO&_pretty=true"); - try (CloseableHttpResponse status = ourHttpClient.execute(validateCodeGet2)) { - String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8); - ourLog.info("Response: {}", response); - Parameters output = myFhirCtx.newXmlParser().parseResource(Parameters.class, response); - assertEquals(false, output.getParameterBool("result")); - } + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "codeableConcept", codeableConceptToValidate_v2) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + // With incorrect version specified. Should fail. + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "codeableConcept", codeableConceptToValidate_v1) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "codeableConcept", codeableConceptToValidate_v2) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("1")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); } -*/ private boolean clearDeferredStorageQueue() { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetVersionedTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetVersionedTest.java new file mode 100644 index 00000000000..59eead8a901 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetVersionedTest.java @@ -0,0 +1,1549 @@ +package ca.uhn.fhir.jpa.provider.r5; + +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; +import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; +import ca.uhn.fhir.jpa.entity.TermConcept; +import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; +import ca.uhn.fhir.jpa.entity.TermValueSet; +import ca.uhn.fhir.jpa.entity.TermValueSetConcept; +import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation; +import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; +import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r5.model.BooleanType; +import org.hl7.fhir.r5.model.Bundle; +import org.hl7.fhir.r5.model.CodeSystem; +import org.hl7.fhir.r5.model.CodeSystem.CodeSystemContentMode; +import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; +import org.hl7.fhir.r5.model.CodeType; +import org.hl7.fhir.r5.model.CodeableConcept; +import org.hl7.fhir.r5.model.Coding; +import org.hl7.fhir.r5.model.IdType; +import org.hl7.fhir.r5.model.Parameters; +import org.hl7.fhir.r5.model.StringType; +import org.hl7.fhir.r5.model.UriType; +import org.hl7.fhir.r5.model.ValueSet; +import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; +import org.hl7.fhir.r5.model.Enumerations.FilterOperator; +import org.hl7.fhir.r4.model.codesystems.HttpVerb; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.util.Optional; + +import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM; +import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_VALUE_SET; +import static org.awaitility.Awaitility.await; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.containsStringIgnoringCase; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.stringContainsInOrder; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +public class ResourceProviderR5ValueSetVersionedTest extends BaseResourceProviderR5Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderR5ValueSetVersionedTest.class); + private IIdType myExtensionalCsId_v1; + private IIdType myExtensionalCsId_v2; + private IIdType myExtensionalVsId_v1; + private IIdType myExtensionalVsId_v2; + private IIdType myLocalValueSetId_v1; + private IIdType myLocalValueSetId_v2; + private Long myExtensionalVsIdOnResourceTable_v1; + private Long myExtensionalVsIdOnResourceTable_v2; + private ValueSet myLocalVs_v1; + private ValueSet myLocalVs_v2; + + private void loadAndPersistCodeSystemAndValueSet() throws IOException { + loadAndPersistCodeSystem(); + loadAndPersistValueSet(); + } + + private void loadAndPersistCodeSystemAndValueSetWithDesignations() throws IOException { + loadAndPersistCodeSystemWithDesignations(); + loadAndPersistValueSet(); + } + + private void loadAndPersistCodeSystem() throws IOException { + CodeSystem codeSystem = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs.xml"); + persistCodeSystem(codeSystem); + } + + private void loadAndPersistCodeSystemWithDesignations() throws IOException { + CodeSystem codeSystem = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs-with-designations.xml"); + persistCodeSystem(codeSystem); + } + + private void persistCodeSystem(CodeSystem theCodeSystem) { + theCodeSystem.setId("CodeSystem/cs1"); + theCodeSystem.setVersion("1"); + new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { + myExtensionalCsId_v1 = myCodeSystemDao.create(theCodeSystem, mySrd).getId().toUnqualifiedVersionless(); + } + }); + myCodeSystemDao.readEntity(myExtensionalCsId_v1, null).getId(); + + theCodeSystem.setId("CodeSystem/cs2"); + theCodeSystem.setVersion("2"); + for(ConceptDefinitionComponent conceptDefinitionComponent : theCodeSystem.getConcept()) { + conceptDefinitionComponent.setDisplay(conceptDefinitionComponent.getDisplay() + " v2"); + } + new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { + myExtensionalCsId_v2 = myCodeSystemDao.create(theCodeSystem, mySrd).getId().toUnqualifiedVersionless(); + } + }); + myCodeSystemDao.readEntity(myExtensionalCsId_v2, null).getId(); + + } + + private void loadAndPersistValueSet() throws IOException { + ValueSet valueSet = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml"); + valueSet.setVersion("1"); + valueSet.setId("ValueSet/vs1"); + valueSet.getCompose().getInclude().get(0).setVersion("1"); + myExtensionalVsId_v1 = persistSingleValueSet(valueSet, HttpVerb.POST); + myExtensionalVsIdOnResourceTable_v1 = myValueSetDao.readEntity(myExtensionalVsId_v1, null).getId(); + + valueSet.setVersion("2"); + valueSet.setId("ValueSet/vs2"); + valueSet.getCompose().getInclude().get(0).setVersion("2"); + myExtensionalVsId_v2 = persistSingleValueSet(valueSet, HttpVerb.POST); + myExtensionalVsIdOnResourceTable_v2 = myValueSetDao.readEntity(myExtensionalVsId_v2, null).getId(); + + } + + private IIdType persistSingleValueSet(ValueSet theValueSet, HttpVerb theVerb) { + final IIdType[] vsId = new IIdType[1]; + switch (theVerb) { + case GET: + case HEAD: + break; + case POST: + new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { + vsId[0] = myValueSetDao.create(theValueSet, mySrd).getId().toUnqualifiedVersionless(); + } + }); + break; + case PUT: + new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { + vsId[0] = myValueSetDao.update(theValueSet, mySrd).getId().toUnqualifiedVersionless(); + } + }); + break; + case DELETE: + case PATCH: + case NULL: + default: + throw new IllegalArgumentException("HTTP verb is not supported: " + theVerb); + } + return vsId[0]; + } + + private String createExternalCs(String theCodeSystemVersion) { + IFhirResourceDao codeSystemDao = myCodeSystemDao; + IResourceTableDao resourceTableDao = myResourceTableDao; + + return createExternalCs(codeSystemDao, resourceTableDao, myTermCodeSystemStorageSvc, mySrd, theCodeSystemVersion).getUrl(); + } + + private void createExternalCsAndLocalVs() { + String codeSystemUrl = createExternalCs("1"); + myLocalVs_v1 = createLocalVs(codeSystemUrl, "1"); + myLocalValueSetId_v1 = persistLocalVs(myLocalVs_v1); + + codeSystemUrl = createExternalCs("2"); + myLocalVs_v2 = createLocalVs(codeSystemUrl, "2"); + myLocalValueSetId_v2 = persistLocalVs(myLocalVs_v2); + + } + + private void createLocalCs() { + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setUrl(URL_MY_CODE_SYSTEM); + codeSystem.setContent(CodeSystemContentMode.COMPLETE); + codeSystem + .addConcept().setCode("A").setDisplay("Code A") + .addConcept(new ConceptDefinitionComponent().setCode("AA").setDisplay("Code AA") + .addConcept(new ConceptDefinitionComponent().setCode("AAA").setDisplay("Code AAA")) + ) + .addConcept(new ConceptDefinitionComponent().setCode("AB").setDisplay("Code AB")); + codeSystem + .addConcept().setCode("B").setDisplay("Code B") + .addConcept(new ConceptDefinitionComponent().setCode("BA").setDisplay("Code BA")) + .addConcept(new ConceptDefinitionComponent().setCode("BB").setDisplay("Code BB")); + myCodeSystemDao.create(codeSystem, mySrd); + } + + private void createLocalVsWithIncludeConcept() { + myLocalVs_v1 = new ValueSet(); + myLocalVs_v1.setUrl(URL_MY_VALUE_SET); + myLocalVs_v1.setVersion("1"); + ConceptSetComponent include = myLocalVs_v1.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM); + include.setVersion("1"); + include.addConcept().setCode("A").setDisplay("A v1"); + include.addConcept().setCode("AA").setDisplay("AA v1"); + myLocalValueSetId_v1 = myValueSetDao.create(myLocalVs_v1, mySrd).getId().toUnqualifiedVersionless(); + + myLocalVs_v2 = new ValueSet(); + myLocalVs_v2.setUrl(URL_MY_VALUE_SET); + myLocalVs_v2.setVersion("2"); + include = myLocalVs_v2.getCompose().addInclude(); + include.setSystem(URL_MY_CODE_SYSTEM); + include.setVersion("2"); + include.addConcept().setCode("A").setDisplay("A v2"); + include.addConcept().setCode("AA").setDisplay("AA v2"); + myLocalValueSetId_v2 = myValueSetDao.create(myLocalVs_v2, mySrd).getId().toUnqualifiedVersionless(); + + } + + private ValueSet createLocalVs(String theCodeSystemUrl, String theValueSetVersion) { + ValueSet myLocalVs = new ValueSet(); + myLocalVs.setUrl(URL_MY_VALUE_SET); + myLocalVs.setVersion(theValueSetVersion); + ConceptSetComponent include = myLocalVs.getCompose().addInclude(); + include.setSystem(theCodeSystemUrl); + include.setVersion(theValueSetVersion); + include.addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue("ParentA"); + return myLocalVs; + + } + + private IIdType persistLocalVs(ValueSet theValueSet) { + return myValueSetDao.create(theValueSet, mySrd).getId().toUnqualifiedVersionless(); + } + + @Test + public void testExpandById() throws Exception { + loadAndPersistCodeSystemAndValueSet(); + + // Test with v1 of ValueSet + Parameters respParam = myClient + .operation() + .onInstance(myExtensionalVsId_v1) + .named("expand") + .withNoParameters(Parameters.class) + .execute(); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + + // Test with v2 of ValueSet + respParam = myClient + .operation() + .onInstance(myExtensionalVsId_v2) + .named("expand") + .withNoParameters(Parameters.class) + .execute(); + expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + + } + + @Test + public void testExpandByIdWithPreExpansion() throws Exception { + myDaoConfig.setPreExpandValueSets(true); + + loadAndPersistCodeSystemAndValueSet(); + await().until(() -> clearDeferredStorageQueue()); + myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); + Slice page = myTermValueSetDao.findByExpansionStatus(PageRequest.of(0, 10), TermValueSetPreExpansionStatusEnum.EXPANDED); + assertEquals(2, page.getContent().size()); + + // Verify v1 ValueSet + Parameters respParam = myClient + .operation() + .onInstance(myExtensionalVsId_v1) + .named("expand") + .withNoParameters(Parameters.class) + .execute(); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + + // Verify v2 ValueSet + respParam = myClient + .operation() + .onInstance(myExtensionalVsId_v2) + .named("expand") + .withNoParameters(Parameters.class) + .execute(); + expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + assertThat(resp, containsString("")); + + } + + @Test + public void testExpandByIdWithFilter() throws Exception { + loadAndPersistCodeSystemAndValueSet(); + + // Verify ValueSet v1 + Parameters respParam = myClient + .operation() + .onInstance(myExtensionalVsId_v1) + .named("expand") + .withParameter(Parameters.class, "filter", new StringType("first")) + .execute(); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + assertThat(resp, containsString("")); + assertThat(resp, not(containsString(""))); + + // Verify ValueSet v2 + respParam = myClient + .operation() + .onInstance(myExtensionalVsId_v2) + .named("expand") + .withParameter(Parameters.class, "filter", new StringType("first")) + .execute(); + expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + assertThat(resp, containsString("")); + assertThat(resp, not(containsString(""))); + + } + + @Test + public void testExpandByIdWithFilterWithPreExpansion() throws Exception { + myDaoConfig.setPreExpandValueSets(true); + + loadAndPersistCodeSystemAndValueSet(); + await().until(() -> clearDeferredStorageQueue()); + myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); + + Slice page = myTermValueSetDao.findByExpansionStatus(PageRequest.of(0, 10), TermValueSetPreExpansionStatusEnum.EXPANDED); + assertEquals(2, page.getContent().size()); + + // Validate ValueSet v1 + Parameters respParam = myClient + .operation() + .onInstance(myExtensionalVsId_v1) + .named("expand") + .withParameter(Parameters.class, "filter", new StringType("first")) + .execute(); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + assertThat(resp, containsString("")); + assertThat(resp, not(containsString(""))); + + // Validate ValueSet v2 + respParam = myClient + .operation() + .onInstance(myExtensionalVsId_v2) + .named("expand") + .withParameter(Parameters.class, "filter", new StringType("first")) + .execute(); + expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + assertThat(resp, containsString("")); + assertThat(resp, not(containsString(""))); + + } + + @Test + public void testExpandByUrlAndVersion() throws Exception { + loadAndPersistCodeSystemAndValueSet(); + + // Check expansion of multi-versioned ValueSet with version 1 + Parameters respParam = myClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("1")) + .execute(); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + assertThat(resp, stringContainsInOrder( + "", + "")); + + // Check expansion of multi-versioned ValueSet with version set to null + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .execute(); + expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + // Should return v2 as this was the last version loaded. + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + assertThat(resp, stringContainsInOrder( + "", + "")); + + // Check expansion of version 2 + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("2")) + .execute(); + expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + assertThat(resp, stringContainsInOrder( + "", + "")); + + } + + @Test + public void testExpandByUrlWithBogusVersion() throws Exception { + loadAndPersistCodeSystemAndValueSet(); + + try { + myClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("3")) + .execute(); + } catch (ResourceNotFoundException e) { + assertEquals(404, e.getStatusCode()); + assertEquals("HTTP 404 Not Found: Unknown ValueSet: http%3A%2F%2Fwww.healthintersections.com.au%2Ffhir%2FValueSet%2Fextensional-case-2%7C3", e.getMessage()); + } + } + + @Test + public void testExpandByUrlWithPreExpansion() throws Exception { + myDaoConfig.setPreExpandValueSets(true); + + loadAndPersistCodeSystemAndValueSet(); + await().until(() -> clearDeferredStorageQueue()); + myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); + + Slice page = myTermValueSetDao.findByExpansionStatus(PageRequest.of(0, 10), TermValueSetPreExpansionStatusEnum.EXPANDED); + assertEquals(2, page.getContent().size()); + + // Check expansion of multi-versioned ValueSet with version 1 + Parameters respParam = myClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("1")) + .execute(); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + assertThat(resp, stringContainsInOrder( + "", + "")); + + // Check expansion of multi-versioned ValueSet with version set to null + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .execute(); + expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + // Should return v2 as this was the last version loaded. + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + assertThat(resp, stringContainsInOrder( + "", + "")); + + // Check expansion of version 2 + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("2")) + .execute(); + expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + assertThat(resp, stringContainsInOrder( + "", + "")); + + } + + @Test + public void testExpandByUrlWithPreExpansionAndBogusVersion() throws Exception { + myDaoConfig.setPreExpandValueSets(true); + + loadAndPersistCodeSystemAndValueSet(); + myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); + + try { + myClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("3")) + .execute(); + } catch (ResourceNotFoundException e) { + assertEquals(404, e.getStatusCode()); + assertEquals("HTTP 404 Not Found: Unknown ValueSet: http%3A%2F%2Fwww.healthintersections.com.au%2Ffhir%2FValueSet%2Fextensional-case-2%7C3", e.getMessage()); + } + } + + @Test + public void testExpandByValueSet() throws IOException { + loadAndPersistCodeSystem(); + + // Test with no version specified + ValueSet toExpand = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml"); + + Parameters respParam = myClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "valueSet", toExpand) + .execute(); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + // Should return v2 as this was the last updated. + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + assertThat(resp, stringContainsInOrder( + "", + "")); + + // Test with version 1 specified. + toExpand.setVersion("1"); + toExpand.setId("ValueSet/vs1"); + toExpand.getCompose().getInclude().get(0).setVersion("1"); + + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "valueSet", toExpand) + .andParameter("valueSetVersion", new StringType("1")) + .execute(); + expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + // Should return v1. + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + assertThat(resp, stringContainsInOrder( + "", + "")); + + // Test with version 2 specified. + toExpand.setVersion("2"); + toExpand.setId("ValueSet/vs2"); + toExpand.getCompose().getInclude().get(0).setVersion("2"); + + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "valueSet", toExpand) + .andParameter("valueSetVersion", new StringType("2")) + .execute(); + expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + // Should return v2. + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + assertThat(resp, stringContainsInOrder( + "", + "")); + + } + + @Test + public void testExpandByValueSetWithPreExpansion() throws IOException { + myDaoConfig.setPreExpandValueSets(true); + + loadAndPersistCodeSystem(); + myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); + + // Test with no version specified + ValueSet toExpand = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml"); + + Parameters respParam = myClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "valueSet", toExpand) + .execute(); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + // Should return v2 as this was the last updated. + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + assertThat(resp, stringContainsInOrder( + "", + "")); + + // Test with version 1 specified. + toExpand.setVersion("1"); + toExpand.setId("ValueSet/vs1"); + toExpand.getCompose().getInclude().get(0).setVersion("1"); + + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "valueSet", toExpand) + .andParameter("valueSetVersion", new StringType("1")) + .execute(); + expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + // Should return v1. + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + assertThat(resp, stringContainsInOrder( + "", + "")); + + // Test with version 2 specified. + toExpand.setVersion("2"); + toExpand.setId("ValueSet/vs2"); + toExpand.getCompose().getInclude().get(0).setVersion("2"); + + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "valueSet", toExpand) + .andParameter("valueSetVersion", new StringType("2")) + .execute(); + expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + // Should return v2. + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + assertThat(resp, stringContainsInOrder( + "", + "")); + + + } + + @Test + public void testExpandInlineVsAgainstExternalCs() { + createExternalCsAndLocalVs(); + assertNotNull(myLocalVs_v1); + assertNotNull(myLocalVs_v2); + + myLocalVs_v1.setId(""); + Parameters respParam = myClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "valueSet", myLocalVs_v1) + .execute(); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + + assertThat(resp, containsStringIgnoringCase("")); + assertThat(resp, containsStringIgnoringCase("")); + assertThat(resp, not(containsStringIgnoringCase(""))); + + myLocalVs_v2.setId(""); + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "valueSet", myLocalVs_v2) + .execute(); + expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + + assertThat(resp, containsStringIgnoringCase("")); + assertThat(resp, containsStringIgnoringCase("")); + assertThat(resp, not(containsStringIgnoringCase(""))); + + } + + @Test + public void testExpandLocalVsAgainstExternalCs() { + createExternalCsAndLocalVs(); + assertNotNull(myLocalValueSetId_v1); + assertNotNull(myLocalValueSetId_v2); + + // Validate ValueSet v1 + Parameters respParam = myClient + .operation() + .onInstance(myLocalValueSetId_v1) + .named("expand") + .withNoParameters(Parameters.class) + .execute(); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + + assertThat(resp, containsStringIgnoringCase("")); + assertThat(resp, containsStringIgnoringCase("")); + assertThat(resp, not(containsStringIgnoringCase(""))); + + // Validate ValueSet v2 + respParam = myClient + .operation() + .onInstance(myLocalValueSetId_v2) + .named("expand") + .withNoParameters(Parameters.class) + .execute(); + expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + + assertThat(resp, containsStringIgnoringCase("")); + assertThat(resp, containsStringIgnoringCase("")); + assertThat(resp, not(containsStringIgnoringCase(""))); + + } + + @Test + public void testExpandLocalVsCanonicalAgainstExternalCs() { + createExternalCsAndLocalVs(); + assertNotNull(myLocalValueSetId_v1); + assertNotNull(myLocalValueSetId_v2); + + Parameters respParam = myClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "url", new UriType(URL_MY_VALUE_SET)) + .execute(); + + // Canonical expand should only return most recently updated version, v2. + assertEquals(1, respParam.getParameter().size()); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + + assertThat(resp, containsStringIgnoringCase("")); + assertThat(resp, containsStringIgnoringCase("")); + assertThat(resp, not(containsStringIgnoringCase(""))); + + } + + @Test + public void testExpandValueSetBasedOnCodeSystemWithChangedUrlAndVersion() { + + CodeSystem cs = new CodeSystem(); + cs.setId("CodeSystem/CS"); + cs.setContent(CodeSystemContentMode.COMPLETE); + cs.setUrl("http://foo1"); + cs.setVersion("1"); + cs.addConcept().setCode("foo1").setDisplay("foo1"); + myClient.update().resource(cs).execute(); + + ValueSet vs = new ValueSet(); + vs.setId("ValueSet/VS179789"); + vs.setUrl("http://bar"); + vs.getCompose().addInclude().setSystem("http://foo1").setVersion("1").addConcept().setCode("foo1"); + myClient.update().resource(vs).execute(); + + ValueSet expanded = myClient + .operation() + .onInstance(new IdType("ValueSet/VS179789")) + .named("$expand") + .withNoParameters(Parameters.class) + .returnResourceType(ValueSet.class) + .execute(); + ourLog.info("Expanded: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expanded)); + assertEquals(1, expanded.getExpansion().getContains().size()); + + // Update the CodeSystem Version and Codes + cs = new CodeSystem(); + cs.setId("CodeSystem/CS"); + cs.setContent(CodeSystemContentMode.COMPLETE); + cs.setUrl("http://foo2"); + cs.setVersion("2"); + cs.addConcept().setCode("foo2").setDisplay("foo2"); + myClient.update().resource(cs).execute(); + + vs = new ValueSet(); + vs.setId("ValueSet/VS179789"); + vs.setUrl("http://bar"); + vs.getCompose().addInclude().setSystem("http://foo2").setVersion("2").addConcept().setCode("foo2"); + myClient.update().resource(vs).execute(); + + expanded = myClient + .operation() + .onInstance(new IdType("ValueSet/VS179789")) + .named("$expand") + .withNoParameters(Parameters.class) + .returnResourceType(ValueSet.class) + .execute(); + ourLog.info("Expanded: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expanded)); + assertEquals(1, expanded.getExpansion().getContains().size()); + } + + + @Test + public void testUpdateValueSetTriggersAnotherPreExpansion() throws Exception { + myDaoConfig.setPreExpandValueSets(true); + + loadAndPersistCodeSystemAndValueSetWithDesignations(); + + CodeSystem codeSystem_v1 = myCodeSystemDao.read(myExtensionalCsId_v1); + ourLog.info("CodeSystem:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(codeSystem_v1)); + CodeSystem codeSystem_v2 = myCodeSystemDao.read(myExtensionalCsId_v2); + ourLog.info("CodeSystem:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(codeSystem_v2)); + + ValueSet valueSet_v1 = myValueSetDao.read(myExtensionalVsId_v1); + ourLog.info("ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(valueSet_v1)); + ValueSet valueSet_v2 = myValueSetDao.read(myExtensionalVsId_v2); + ourLog.info("ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(valueSet_v2)); + + String initialValueSetName_v1 = valueSet_v1.getName(); + validateTermValueSetNotExpanded(initialValueSetName_v1, "1", myExtensionalVsIdOnResourceTable_v1); + String initialValueSetName_v2 = valueSet_v2.getName(); + validateTermValueSetNotExpanded(initialValueSetName_v2, "2", myExtensionalVsIdOnResourceTable_v2); + await().until(() -> clearDeferredStorageQueue()); + myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); + validateTermValueSetExpandedAndChildrenV1(initialValueSetName_v1, codeSystem_v1); + validateTermValueSetExpandedAndChildrenV2(initialValueSetName_v2, codeSystem_v2); + + ValueSet updatedValueSet_v1 = valueSet_v1; + updatedValueSet_v1.setName(valueSet_v1.getName().concat(" - MODIFIED")); + persistSingleValueSet(updatedValueSet_v1, HttpVerb.PUT); + updatedValueSet_v1 = myValueSetDao.read(myExtensionalVsId_v1); + ourLog.info("Updated ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(updatedValueSet_v1)); + + String updatedValueSetName_v1 = valueSet_v1.getName(); + validateTermValueSetNotExpanded(updatedValueSetName_v1,"1", myExtensionalVsIdOnResourceTable_v1); + + ValueSet updatedValueSet_v2 = valueSet_v2; + updatedValueSet_v2.setName(valueSet_v2.getName().concat(" - MODIFIED")); + persistSingleValueSet(updatedValueSet_v2, HttpVerb.PUT); + updatedValueSet_v2 = myValueSetDao.read(myExtensionalVsId_v2); + ourLog.info("Updated ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(updatedValueSet_v2)); + + String updatedValueSetName_v2 = valueSet_v2.getName(); + validateTermValueSetNotExpanded(updatedValueSetName_v2,"2", myExtensionalVsIdOnResourceTable_v2); + + myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); + validateTermValueSetExpandedAndChildrenV1(updatedValueSetName_v1, codeSystem_v1); + validateTermValueSetExpandedAndChildrenV2(updatedValueSetName_v2, codeSystem_v2); + } + + + @Test + public void testUpdateValueSetTriggersAnotherPreExpansionUsingTransactionBundle() throws Exception { + myDaoConfig.setPreExpandValueSets(true); + + loadAndPersistCodeSystemAndValueSetWithDesignations(); + + CodeSystem codeSystem_v1 = myCodeSystemDao.read(myExtensionalCsId_v1); + ourLog.info("CodeSystem:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(codeSystem_v1)); + CodeSystem codeSystem_v2 = myCodeSystemDao.read(myExtensionalCsId_v2); + ourLog.info("CodeSystem:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(codeSystem_v2)); + + ValueSet valueSet_v1 = myValueSetDao.read(myExtensionalVsId_v1); + ourLog.info("ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(valueSet_v1)); + ValueSet valueSet_v2 = myValueSetDao.read(myExtensionalVsId_v2); + ourLog.info("ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(valueSet_v2)); + + String initialValueSetName_v1 = valueSet_v1.getName(); + validateTermValueSetNotExpanded(initialValueSetName_v1, "1", myExtensionalVsIdOnResourceTable_v1); + String initialValueSetName_v2 = valueSet_v2.getName(); + validateTermValueSetNotExpanded(initialValueSetName_v2, "2", myExtensionalVsIdOnResourceTable_v2); + myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); + await().until(() -> clearDeferredStorageQueue()); + validateTermValueSetExpandedAndChildrenV1(initialValueSetName_v1, codeSystem_v1); + validateTermValueSetExpandedAndChildrenV2(initialValueSetName_v2, codeSystem_v2); + + ValueSet updatedValueSet_v1 = valueSet_v1; + updatedValueSet_v1.setName(valueSet_v1.getName().concat(" - MODIFIED")); + + String url = myClient.getServerBase().concat("/").concat(myExtensionalVsId_v1.getValueAsString()); + Bundle bundle = new Bundle(); + bundle.setType(Bundle.BundleType.TRANSACTION); + bundle + .addEntry() + .setFullUrl(url) + .setResource(updatedValueSet_v1) + .getRequest() + .setMethod(Bundle.HTTPVerb.PUT) + .setUrl(url); + ourLog.info("Transaction Bundle:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle)); + myClient.transaction().withBundle(bundle).execute(); + + updatedValueSet_v1 = myValueSetDao.read(myExtensionalVsId_v1); + ourLog.info("Updated ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(updatedValueSet_v1)); + + String updatedValueSetName_v1 = valueSet_v1.getName(); + validateTermValueSetNotExpanded(updatedValueSetName_v1, "1", myExtensionalVsIdOnResourceTable_v1); + + ValueSet updatedValueSet_v2 = valueSet_v2; + updatedValueSet_v2.setName(valueSet_v2.getName().concat(" - MODIFIED")); + + url = myClient.getServerBase().concat("/").concat(myExtensionalVsId_v2.getValueAsString()); + bundle = new Bundle(); + bundle.setType(Bundle.BundleType.TRANSACTION); + bundle + .addEntry() + .setFullUrl(url) + .setResource(updatedValueSet_v2) + .getRequest() + .setMethod(Bundle.HTTPVerb.PUT) + .setUrl(url); + ourLog.info("Transaction Bundle:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle)); + myClient.transaction().withBundle(bundle).execute(); + + updatedValueSet_v2 = myValueSetDao.read(myExtensionalVsId_v2); + ourLog.info("Updated ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(updatedValueSet_v2)); + + String updatedValueSetName_v2 = valueSet_v2.getName(); + validateTermValueSetNotExpanded(updatedValueSetName_v2, "2", myExtensionalVsIdOnResourceTable_v2); + + myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); + validateTermValueSetExpandedAndChildrenV1(updatedValueSetName_v1, codeSystem_v1); + validateTermValueSetExpandedAndChildrenV2(updatedValueSetName_v2, codeSystem_v2); + + } + + private void validateTermValueSetNotExpanded(String theValueSetName, String theVersion, Long theId) { + runInTransaction(() -> { + Optional optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(theId); + assertTrue(optionalValueSetByResourcePid.isPresent()); + + Optional optionalValueSetByUrl = myTermValueSetDao.findTermValueSetByUrlAndVersion("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2", theVersion); + assertTrue(optionalValueSetByUrl.isPresent()); + + TermValueSet termValueSet = optionalValueSetByUrl.get(); + assertSame(optionalValueSetByResourcePid.get(), termValueSet); + ourLog.info("ValueSet:\n" + termValueSet.toString()); + assertEquals("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2", termValueSet.getUrl()); + assertEquals(theValueSetName, termValueSet.getName()); + assertEquals(0, termValueSet.getConcepts().size()); + assertEquals(TermValueSetPreExpansionStatusEnum.NOT_EXPANDED, termValueSet.getExpansionStatus()); + }); + } + + private void validateTermValueSetExpandedAndChildrenV1(String theValueSetName, CodeSystem theCodeSystem) { + runInTransaction(() -> { + Optional optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable_v1); + assertTrue(optionalValueSetByResourcePid.isPresent()); + + Optional optionalValueSetByUrl = myTermValueSetDao.findTermValueSetByUrlAndVersion("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2", "1"); + assertTrue(optionalValueSetByUrl.isPresent()); + + TermValueSet termValueSet = optionalValueSetByUrl.get(); + assertSame(optionalValueSetByResourcePid.get(), termValueSet); + ourLog.info("ValueSet:\n" + termValueSet.toString()); + assertEquals("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2", termValueSet.getUrl()); + assertEquals(theValueSetName, termValueSet.getName()); + assertEquals(theCodeSystem.getConcept().size(), termValueSet.getConcepts().size()); + assertEquals(TermValueSetPreExpansionStatusEnum.EXPANDED, termValueSet.getExpansionStatus()); + + TermValueSetConcept concept = termValueSet.getConcepts().get(0); + ourLog.info("Concept:\n" + concept.toString()); + assertEquals("http://acme.org|1", concept.getSystem()); + assertEquals("8450-9", concept.getCode()); + assertEquals("Systolic blood pressure--expiration", concept.getDisplay()); + assertEquals(2, concept.getDesignations().size()); + assertEquals(0, concept.getOrder()); + + TermValueSetConceptDesignation designation = concept.getDesignations().get(0); + assertEquals("nl", designation.getLanguage()); + assertEquals("http://snomed.info/sct", designation.getUseSystem()); + assertEquals("900000000000013009", designation.getUseCode()); + assertEquals("Synonym", designation.getUseDisplay()); + assertEquals("Systolische bloeddruk - expiratie", designation.getValue()); + + designation = concept.getDesignations().get(1); + assertEquals("sv", designation.getLanguage()); + assertEquals("http://snomed.info/sct", designation.getUseSystem()); + assertEquals("900000000000013009", designation.getUseCode()); + assertEquals("Synonym", designation.getUseDisplay()); + assertEquals("Systoliskt blodtryck - utgång", designation.getValue()); + + concept = termValueSet.getConcepts().get(1); + ourLog.info("Concept:\n" + concept.toString()); + assertEquals("http://acme.org|1", concept.getSystem()); + assertEquals("11378-7", concept.getCode()); + assertEquals("Systolic blood pressure at First encounter", concept.getDisplay()); + assertEquals(0, concept.getDesignations().size()); + assertEquals(1, concept.getOrder()); + + // ... + + concept = termValueSet.getConcepts().get(22); + ourLog.info("Concept:\n" + concept.toString()); + assertEquals("http://acme.org|1", concept.getSystem()); + assertEquals("8491-3", concept.getCode()); + assertEquals("Systolic blood pressure 1 hour minimum", concept.getDisplay()); + assertEquals(1, concept.getDesignations().size()); + assertEquals(22, concept.getOrder()); + + designation = concept.getDesignations().get(0); + assertEquals("nl", designation.getLanguage()); + assertEquals("http://snomed.info/sct", designation.getUseSystem()); + assertEquals("900000000000013009", designation.getUseCode()); + assertEquals("Synonym", designation.getUseDisplay()); + assertEquals("Systolische bloeddruk minimaal 1 uur", designation.getValue()); + + concept = termValueSet.getConcepts().get(23); + ourLog.info("Concept:\n" + concept.toString()); + assertEquals("http://acme.org|1", concept.getSystem()); + assertEquals("8492-1", concept.getCode()); + assertEquals("Systolic blood pressure 8 hour minimum", concept.getDisplay()); + assertEquals(0, concept.getDesignations().size()); + assertEquals(23, concept.getOrder()); + }); + } + + private void validateTermValueSetExpandedAndChildrenV2(String theValueSetName, CodeSystem theCodeSystem) { + runInTransaction(() -> { + Optional optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable_v2); + assertTrue(optionalValueSetByResourcePid.isPresent()); + + Optional optionalValueSetByUrl = myTermValueSetDao.findTermValueSetByUrlAndVersion("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2", "2"); + assertTrue(optionalValueSetByUrl.isPresent()); + + TermValueSet termValueSet = optionalValueSetByUrl.get(); + assertSame(optionalValueSetByResourcePid.get(), termValueSet); + ourLog.info("ValueSet:\n" + termValueSet.toString()); + assertEquals("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2", termValueSet.getUrl()); + assertEquals(theValueSetName, termValueSet.getName()); + assertEquals(theCodeSystem.getConcept().size(), termValueSet.getConcepts().size()); + assertEquals(TermValueSetPreExpansionStatusEnum.EXPANDED, termValueSet.getExpansionStatus()); + + TermValueSetConcept concept = termValueSet.getConcepts().get(0); + ourLog.info("Concept:\n" + concept.toString()); + assertEquals("http://acme.org|2", concept.getSystem()); + assertEquals("8450-9", concept.getCode()); + assertEquals("Systolic blood pressure--expiration v2", concept.getDisplay()); + assertEquals(2, concept.getDesignations().size()); + assertEquals(0, concept.getOrder()); + + TermValueSetConceptDesignation designation = concept.getDesignations().get(0); + assertEquals("nl", designation.getLanguage()); + assertEquals("http://snomed.info/sct", designation.getUseSystem()); + assertEquals("900000000000013009", designation.getUseCode()); + assertEquals("Synonym", designation.getUseDisplay()); + assertEquals("Systolische bloeddruk - expiratie", designation.getValue()); + + designation = concept.getDesignations().get(1); + assertEquals("sv", designation.getLanguage()); + assertEquals("http://snomed.info/sct", designation.getUseSystem()); + assertEquals("900000000000013009", designation.getUseCode()); + assertEquals("Synonym", designation.getUseDisplay()); + assertEquals("Systoliskt blodtryck - utgång", designation.getValue()); + + concept = termValueSet.getConcepts().get(1); + ourLog.info("Concept:\n" + concept.toString()); + assertEquals("http://acme.org|2", concept.getSystem()); + assertEquals("11378-7", concept.getCode()); + assertEquals("Systolic blood pressure at First encounter v2", concept.getDisplay()); + assertEquals(0, concept.getDesignations().size()); + assertEquals(1, concept.getOrder()); + + // ... + + concept = termValueSet.getConcepts().get(22); + ourLog.info("Concept:\n" + concept.toString()); + assertEquals("http://acme.org|2", concept.getSystem()); + assertEquals("8491-3", concept.getCode()); + assertEquals("Systolic blood pressure 1 hour minimum v2", concept.getDisplay()); + assertEquals(1, concept.getDesignations().size()); + assertEquals(22, concept.getOrder()); + + designation = concept.getDesignations().get(0); + assertEquals("nl", designation.getLanguage()); + assertEquals("http://snomed.info/sct", designation.getUseSystem()); + assertEquals("900000000000013009", designation.getUseCode()); + assertEquals("Synonym", designation.getUseDisplay()); + assertEquals("Systolische bloeddruk minimaal 1 uur", designation.getValue()); + + concept = termValueSet.getConcepts().get(23); + ourLog.info("Concept:\n" + concept.toString()); + assertEquals("http://acme.org|2", concept.getSystem()); + assertEquals("8492-1", concept.getCode()); + assertEquals("Systolic blood pressure 8 hour minimum v2", concept.getDisplay()); + assertEquals(0, concept.getDesignations().size()); + assertEquals(23, concept.getOrder()); + }); + } + + @Test + public void testValidateCodeOperationByCodeAndSystem() throws Exception { + loadAndPersistCodeSystemAndValueSet(); + + // With correct system version specified. Should pass. + Parameters respParam = myClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "code", new CodeType("8495-4")) + .andParameter("system", new UriType("http://acme.org")) + .andParameter("systemVersion", new StringType("1")) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("1")) + .execute(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "code", new CodeType("8495-4")) + .andParameter("system", new UriType("http://acme.org")) + .andParameter("systemVersion", new StringType("2")) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + // With incorrect version specified. Should fail. + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "code", new CodeType("8495-4")) + .andParameter("system", new UriType("http://acme.org")) + .andParameter("systemVersion", new StringType("1")) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "code", new CodeType("8495-4")) + .andParameter("system", new UriType("http://acme.org")) + .andParameter("systemVersion", new StringType("2")) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("1")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + } + + @Test + public void testValidateCodeOperationOnInstanceByCodeAndSystem() throws Exception { + loadAndPersistCodeSystemAndValueSet(); + + // With correct system version specified. Should pass. + Parameters respParam = myClient + .operation() + .onInstance(myExtensionalVsId_v1) + .named("validate-code") + .withParameter(Parameters.class, "code", new CodeType("8495-4")) + .andParameter("system", new UriType("http://acme.org")) + .andParameter("systemVersion", new StringType("1")) + .execute(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + respParam = myClient + .operation() + .onInstance(myExtensionalVsId_v2) + .named("validate-code") + .withParameter(Parameters.class, "code", new CodeType("8495-4")) + .andParameter("system", new UriType("http://acme.org")) + .andParameter("systemVersion", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + // With incorrect version specified. Should fail. + respParam = myClient + .operation() + .onInstance(myExtensionalVsId_v1) + .named("validate-code") + .withParameter(Parameters.class, "code", new CodeType("8495-4")) + .andParameter("system", new UriType("http://acme.org")) + .andParameter("systemVersion", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + respParam = myClient + .operation() + .onInstance(myExtensionalVsId_v2) + .named("validate-code") + .withParameter(Parameters.class, "code", new CodeType("8495-4")) + .andParameter("system", new UriType("http://acme.org")) + .andParameter("systemVersion", new StringType("1")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + } + + @Test + public void testValidateCodeOperationByCoding() throws Exception { + loadAndPersistCodeSystemAndValueSet(); + + Coding codingToValidate_v1 = new Coding("http://acme.org", "8495-4", "Systolic blood pressure 24 hour minimum"); + codingToValidate_v1.setVersion("1"); + + Coding codingToValidate_v2 = new Coding("http://acme.org", "8495-4", "Systolic blood pressure 24 hour minimum v2"); + codingToValidate_v2.setVersion("2"); + + // With correct system version specified. Should pass. + Parameters respParam = myClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "coding", codingToValidate_v1) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("1")) + .execute(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "coding", codingToValidate_v2) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + // With incorrect version specified. Should fail. + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "coding", codingToValidate_v1) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "coding", codingToValidate_v2) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("1")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + } + + @Test + public void testValidateCodeOperationByCodeableConcept() throws Exception { + loadAndPersistCodeSystemAndValueSet(); + + Coding codingToValidate = new Coding("http://acme.org", "8495-4", "Systolic blood pressure 24 hour minimum"); + codingToValidate.setVersion("1"); + CodeableConcept codeableConceptToValidate_v1 = new CodeableConcept(codingToValidate); + + codingToValidate = new Coding("http://acme.org", "8495-4", "Systolic blood pressure 24 hour minimum v2"); + codingToValidate.setVersion("2"); + CodeableConcept codeableConceptToValidate_v2 = new CodeableConcept(codingToValidate); + + // With correct system version specified. Should pass. + Parameters respParam = myClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "codeableConcept", codeableConceptToValidate_v1) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("1")) + .execute(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "codeableConcept", codeableConceptToValidate_v2) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + // With incorrect version specified. Should fail. + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "codeableConcept", codeableConceptToValidate_v1) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + respParam = myClient + .operation() + .onType(ValueSet.class) + .named("validate-code") + .withParameter(Parameters.class, "codeableConcept", codeableConceptToValidate_v2) + .andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("valueSetVersion", new StringType("1")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + + } + + private boolean clearDeferredStorageQueue() { + + if(!myTermDeferredStorageSvc.isStorageQueueEmpty()) { + myTermDeferredStorageSvc.saveAllDeferred(); + return false; + } else { + return true; + } + + } + + @Test + public void testCreateDuplicatValueSetVersion() { + createExternalCsAndLocalVs(); + try { + persistLocalVs(createLocalVs(URL_MY_CODE_SYSTEM, "1")); + fail(); + } catch (UnprocessableEntityException theE) { + assertThat(theE.getMessage(), containsString("Can not create multiple ValueSet resources with ValueSet.url \"" + URL_MY_VALUE_SET + "\" and ValueSet.version \"1\", already have one with resource ID: ")); + } + + } + + @AfterEach + public void afterResetPreExpansionDefault() { + myDaoConfig.setPreExpandValueSets(new DaoConfig().isPreExpandValueSets()); + } + + public CodeSystem createExternalCs(IFhirResourceDao theCodeSystemDao, IResourceTableDao theResourceTableDao, ITermCodeSystemStorageSvc theTermCodeSystemStorageSvc, ServletRequestDetails theRequestDetails) { + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setUrl(URL_MY_CODE_SYSTEM); + codeSystem.setVersion("SYSTEM VERSION"); + codeSystem.setContent(CodeSystemContentMode.NOTPRESENT); + 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"); + cs.getConcepts().add(parentA); + + TermConcept childAA = new TermConcept(cs, "childAA").setDisplay("Child AA"); + parentA.addChild(childAA, RelationshipTypeEnum.ISA); + + TermConcept childAAA = new TermConcept(cs, "childAAA").setDisplay("Child AAA"); + childAA.addChild(childAAA, RelationshipTypeEnum.ISA); + + TermConcept childAAB = new TermConcept(cs, "childAAB").setDisplay("Child AAB"); + childAA.addChild(childAAB, RelationshipTypeEnum.ISA); + + TermConcept childAB = new TermConcept(cs, "childAB").setDisplay("Child AB"); + parentA.addChild(childAB, RelationshipTypeEnum.ISA); + + TermConcept parentB = new TermConcept(cs, "ParentB").setDisplay("Parent B"); + cs.getConcepts().add(parentB); + + theTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table); + return codeSystem; + } + + public static CodeSystem createExternalCs(IFhirResourceDao theCodeSystemDao, IResourceTableDao theResourceTableDao, ITermCodeSystemStorageSvc theTermCodeSystemStorageSvc, ServletRequestDetails theRequestDetails, String theCodeSystemVersion) { + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setUrl(URL_MY_CODE_SYSTEM); + codeSystem.setVersion(theCodeSystemVersion); + codeSystem.setContent(CodeSystemContentMode.NOTPRESENT); + 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; + } + +} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java index b447c39e160..4028f33c450 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java @@ -326,6 +326,30 @@ public class CommonCodeSystemsTerminologyService implements IValidationSupport { return url; } + public static String getValueSetVersion(@Nonnull IBaseResource theValueSet) { + String version; + switch (theValueSet.getStructureFhirVersionEnum()) { + case DSTU3: { + version = ((org.hl7.fhir.dstu3.model.ValueSet) theValueSet).getVersion(); + break; + } + case R4: { + version = ((org.hl7.fhir.r4.model.ValueSet) theValueSet).getVersion(); + break; + } + case R5: { + version = ((org.hl7.fhir.r5.model.ValueSet) theValueSet).getVersion(); + break; + } + case DSTU2: + case DSTU2_HL7ORG: + case DSTU2_1: + default: + throw new IllegalArgumentException("Can not handle version: " + theValueSet.getStructureFhirVersionEnum()); + } + return version; + } + private static HashMap buildUspsCodes() { HashMap uspsCodes = new HashMap<>(); uspsCodes.put("AK", "Alaska"); diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java index 3fca6d034ac..4704912f7fb 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java @@ -91,23 +91,23 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu return new ValueSetExpansionOutcome(expansion, null); } - private org.hl7.fhir.r5.model.ValueSet expandValueSetToCanonical(ValidationSupportContext theValidationSupportContext, IBaseResource theValueSetToExpand, @Nullable String theWantSystem, @Nullable String theWantCode) { + private org.hl7.fhir.r5.model.ValueSet expandValueSetToCanonical(ValidationSupportContext theValidationSupportContext, IBaseResource theValueSetToExpand, @Nullable String theWantSystemIdentifier, @Nullable String theWantCode) { org.hl7.fhir.r5.model.ValueSet expansionR5; switch (theValueSetToExpand.getStructureFhirVersionEnum()) { case DSTU2: { - expansionR5 = expandValueSetDstu2(theValidationSupportContext, (ca.uhn.fhir.model.dstu2.resource.ValueSet) theValueSetToExpand, theWantSystem, theWantCode); + expansionR5 = expandValueSetDstu2(theValidationSupportContext, (ca.uhn.fhir.model.dstu2.resource.ValueSet) theValueSetToExpand, theWantSystemIdentifier, theWantCode); break; } case DSTU2_HL7ORG: { - expansionR5 = expandValueSetDstu2Hl7Org(theValidationSupportContext, (ValueSet) theValueSetToExpand, theWantSystem, theWantCode); + expansionR5 = expandValueSetDstu2Hl7Org(theValidationSupportContext, (ValueSet) theValueSetToExpand, theWantSystemIdentifier, theWantCode); break; } case DSTU3: { - expansionR5 = expandValueSetDstu3(theValidationSupportContext, (org.hl7.fhir.dstu3.model.ValueSet) theValueSetToExpand, theWantSystem, theWantCode); + expansionR5 = expandValueSetDstu3(theValidationSupportContext, (org.hl7.fhir.dstu3.model.ValueSet) theValueSetToExpand, theWantSystemIdentifier, theWantCode); break; } case R4: { - expansionR5 = expandValueSetR4(theValidationSupportContext, (org.hl7.fhir.r4.model.ValueSet) theValueSetToExpand, theWantSystem, theWantCode); + expansionR5 = expandValueSetR4(theValidationSupportContext, (org.hl7.fhir.r4.model.ValueSet) theValueSetToExpand, theWantSystemIdentifier, theWantCode); break; } case R5: { @@ -119,20 +119,17 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu throw new IllegalArgumentException("Can not handle version: " + myCtx.getVersion().getVersion()); } - if (expansionR5 == null) { - return null; - } return expansionR5; } @Override public CodeValidationResult - validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { - org.hl7.fhir.r5.model.ValueSet expansion = expandValueSetToCanonical(theValidationSupportContext, theValueSet, theCodeSystem, theCode); + validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystemIdentifier, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { + org.hl7.fhir.r5.model.ValueSet expansion = expandValueSetToCanonical(theValidationSupportContext, theValueSet, theCodeSystemIdentifier, theCode); if (expansion == null) { return null; } - return validateCodeInExpandedValueSet(theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, expansion); + return validateCodeInExpandedValueSet(theValidationSupportContext, theOptions, theCodeSystemIdentifier, theCode, theDisplay, expansion); } @@ -145,26 +142,59 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu return null; } } else { + String codeSystemUrl; + String codeSystemVersion = null; + int codeSystemVersionIndex = theCodeSystem.indexOf("|"); + if (codeSystemVersionIndex > -1) { + codeSystemUrl = theCodeSystem.substring(0, codeSystemVersionIndex); + codeSystemVersion = theCodeSystem.substring(codeSystemVersionIndex + 1); + } else { + codeSystemUrl = theCodeSystem; + } switch (myCtx.getVersion().getVersion()) { case DSTU2_HL7ORG: - vs = new org.hl7.fhir.dstu2.model.ValueSet() - .setCompose(new org.hl7.fhir.dstu2.model.ValueSet.ValueSetComposeComponent() - .addInclude(new org.hl7.fhir.dstu2.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); + if (codeSystemVersion != null) { + vs = new org.hl7.fhir.dstu2.model.ValueSet() + .setCompose(new org.hl7.fhir.dstu2.model.ValueSet.ValueSetComposeComponent() + .addInclude(new org.hl7.fhir.dstu2.model.ValueSet.ConceptSetComponent().setSystem(codeSystemUrl).setVersion(codeSystemVersion))); + } else { + vs = new org.hl7.fhir.dstu2.model.ValueSet() + .setCompose(new org.hl7.fhir.dstu2.model.ValueSet.ValueSetComposeComponent() + .addInclude(new org.hl7.fhir.dstu2.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); + } break; case DSTU3: - vs = new org.hl7.fhir.dstu3.model.ValueSet() - .setCompose(new org.hl7.fhir.dstu3.model.ValueSet.ValueSetComposeComponent() - .addInclude(new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); + if (codeSystemVersion != null) { + vs = new org.hl7.fhir.dstu3.model.ValueSet() + .setCompose(new org.hl7.fhir.dstu3.model.ValueSet.ValueSetComposeComponent() + .addInclude(new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent().setSystem(codeSystemUrl).setVersion(codeSystemVersion))); + } else { + vs = new org.hl7.fhir.dstu3.model.ValueSet() + .setCompose(new org.hl7.fhir.dstu3.model.ValueSet.ValueSetComposeComponent() + .addInclude(new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); + } break; case R4: - vs = new org.hl7.fhir.r4.model.ValueSet() - .setCompose(new org.hl7.fhir.r4.model.ValueSet.ValueSetComposeComponent() - .addInclude(new org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); + if (codeSystemVersion != null) { + vs = new org.hl7.fhir.r4.model.ValueSet() + .setCompose(new org.hl7.fhir.r4.model.ValueSet.ValueSetComposeComponent() + .addInclude(new org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent().setSystem(codeSystemUrl).setVersion(codeSystemVersion))); + } else { + vs = new org.hl7.fhir.r4.model.ValueSet() + .setCompose(new org.hl7.fhir.r4.model.ValueSet.ValueSetComposeComponent() + .addInclude(new org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); + } break; case R5: - vs = new org.hl7.fhir.r5.model.ValueSet() - .setCompose(new org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent() - .addInclude(new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); + if (codeSystemVersion != null) { + vs = new org.hl7.fhir.r5.model.ValueSet() + .setCompose(new org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent() + .addInclude(new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent().setSystem(codeSystemUrl).setVersion(codeSystemVersion))); + } else { + vs = new org.hl7.fhir.r5.model.ValueSet() + .setCompose(new org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent() + .addInclude(new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); + } break; case DSTU2: case DSTU2_1: @@ -184,39 +214,39 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu } - private CodeValidationResult validateCodeInExpandedValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, IBaseResource theExpansion) { + private CodeValidationResult validateCodeInExpandedValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystemIdentifierToValidate, String theCodeToValidate, String theDisplayToValidate, IBaseResource theExpansion) { assert theExpansion != null; boolean caseSensitive = true; - IBaseResource system = null; - if (!theOptions.isInferSystem() && isNotBlank(theCodeSystem)) { - system = theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(theCodeSystem); + IBaseResource codeSystemToValidateResource = null; + if (!theOptions.isInferSystem() && isNotBlank(theCodeSystemIdentifierToValidate)) { + codeSystemToValidateResource = theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(theCodeSystemIdentifierToValidate); } - List codes = new ArrayList<>(); + List codesInValueSetExpansion = new ArrayList<>(); switch (theExpansion.getStructureFhirVersionEnum()) { case DSTU2_HL7ORG: { ValueSet expansionVs = (ValueSet) theExpansion; List contains = expansionVs.getExpansion().getContains(); - flattenAndConvertCodesDstu2(contains, codes); + flattenAndConvertCodesDstu2(contains, codesInValueSetExpansion); break; } case DSTU3: { org.hl7.fhir.dstu3.model.ValueSet expansionVs = (org.hl7.fhir.dstu3.model.ValueSet) theExpansion; List contains = expansionVs.getExpansion().getContains(); - flattenAndConvertCodesDstu3(contains, codes); + flattenAndConvertCodesDstu3(contains, codesInValueSetExpansion); break; } case R4: { org.hl7.fhir.r4.model.ValueSet expansionVs = (org.hl7.fhir.r4.model.ValueSet) theExpansion; List contains = expansionVs.getExpansion().getContains(); - flattenAndConvertCodesR4(contains, codes); + flattenAndConvertCodesR4(contains, codesInValueSetExpansion); break; } case R5: { org.hl7.fhir.r5.model.ValueSet expansionVs = (org.hl7.fhir.r5.model.ValueSet) theExpansion; List contains = expansionVs.getExpansion().getContains(); - flattenAndConvertCodesR5(contains, codes); + flattenAndConvertCodesR5(contains, codesInValueSetExpansion); break; } case DSTU2: @@ -225,37 +255,37 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu throw new IllegalArgumentException("Can not handle version: " + myCtx.getVersion().getVersion()); } - String codeSystemName = null; - String codeSystemVersion = null; - String codeSystemContentMode = null; - if (system != null) { - switch (system.getStructureFhirVersionEnum()) { + String codeSystemResourceName = null; + String codeSystemResourceVersion = null; + String codeSystemResourceContentMode = null; + if (codeSystemToValidateResource != null) { + switch (codeSystemToValidateResource.getStructureFhirVersionEnum()) { case DSTU2_HL7ORG: { caseSensitive = true; break; } case DSTU3: { - org.hl7.fhir.dstu3.model.CodeSystem systemDstu3 = (org.hl7.fhir.dstu3.model.CodeSystem) system; + org.hl7.fhir.dstu3.model.CodeSystem systemDstu3 = (org.hl7.fhir.dstu3.model.CodeSystem) codeSystemToValidateResource; caseSensitive = systemDstu3.getCaseSensitive(); - codeSystemName = systemDstu3.getName(); - codeSystemVersion = systemDstu3.getVersion(); - codeSystemContentMode = systemDstu3.getContentElement().getValueAsString(); + codeSystemResourceName = systemDstu3.getName(); + codeSystemResourceVersion = systemDstu3.getVersion(); + codeSystemResourceContentMode = systemDstu3.getContentElement().getValueAsString(); break; } case R4: { - org.hl7.fhir.r4.model.CodeSystem systemR4 = (org.hl7.fhir.r4.model.CodeSystem) system; + org.hl7.fhir.r4.model.CodeSystem systemR4 = (org.hl7.fhir.r4.model.CodeSystem) codeSystemToValidateResource; caseSensitive = systemR4.getCaseSensitive(); - codeSystemName = systemR4.getName(); - codeSystemVersion = systemR4.getVersion(); - codeSystemContentMode = systemR4.getContentElement().getValueAsString(); + codeSystemResourceName = systemR4.getName(); + codeSystemResourceVersion = systemR4.getVersion(); + codeSystemResourceContentMode = systemR4.getContentElement().getValueAsString(); break; } case R5: { - CodeSystem systemR5 = (CodeSystem) system; + CodeSystem systemR5 = (CodeSystem) codeSystemToValidateResource; caseSensitive = systemR5.getCaseSensitive(); - codeSystemName = systemR5.getName(); - codeSystemVersion = systemR5.getVersion(); - codeSystemContentMode = systemR5.getContentElement().getValueAsString(); + codeSystemResourceName = systemR5.getName(); + codeSystemResourceVersion = systemR5.getVersion(); + codeSystemResourceContentMode = systemR5.getContentElement().getValueAsString(); break; } case DSTU2: @@ -265,29 +295,40 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu } } - for (VersionIndependentConcept nextExpansionCode : codes) { + String codeSystemUrlToValidate=null; + String codeSystemVersionToValidate=null; + if (theCodeSystemIdentifierToValidate != null) { + int versionIndex = theCodeSystemIdentifierToValidate.indexOf("|"); + if (versionIndex > -1) { + codeSystemUrlToValidate = theCodeSystemIdentifierToValidate.substring(0, versionIndex); + codeSystemVersionToValidate = theCodeSystemIdentifierToValidate.substring(versionIndex+1); + } else { + codeSystemUrlToValidate = theCodeSystemIdentifierToValidate; + } + } + for (VersionIndependentConcept nextExpansionCode : codesInValueSetExpansion) { boolean codeMatches; if (caseSensitive) { - codeMatches = defaultString(theCode).equals(nextExpansionCode.getCode()); + codeMatches = defaultString(theCodeToValidate).equals(nextExpansionCode.getCode()); } else { - codeMatches = defaultString(theCode).equalsIgnoreCase(nextExpansionCode.getCode()); + codeMatches = defaultString(theCodeToValidate).equalsIgnoreCase(nextExpansionCode.getCode()); } if (codeMatches) { - if (theOptions.isInferSystem() || nextExpansionCode.getSystem().equals(theCodeSystem)) { - if (!theOptions.isValidateDisplay() || (isBlank(nextExpansionCode.getDisplay()) || isBlank(theDisplay) || nextExpansionCode.getDisplay().equals(theDisplay))) { + if (theOptions.isInferSystem() || (nextExpansionCode.getSystem().equals(codeSystemUrlToValidate) && (codeSystemVersionToValidate == null || codeSystemVersionToValidate.equals(nextExpansionCode.getSystemVersion())))) { + if (!theOptions.isValidateDisplay() || (isBlank(nextExpansionCode.getDisplay()) || isBlank(theDisplayToValidate) || nextExpansionCode.getDisplay().equals(theDisplayToValidate))) { return new CodeValidationResult() - .setCode(theCode) + .setCode(theCodeToValidate) .setDisplay(nextExpansionCode.getDisplay()) - .setCodeSystemName(codeSystemName) - .setCodeSystemVersion(codeSystemVersion); + .setCodeSystemName(codeSystemResourceName) + .setCodeSystemVersion(codeSystemResourceVersion); } else { return new CodeValidationResult() .setSeverity(IssueSeverity.ERROR) .setDisplay(nextExpansionCode.getDisplay()) - .setMessage("Concept Display \"" + theDisplay + "\" does not match expected \"" + nextExpansionCode.getDisplay() + "\"") - .setCodeSystemName(codeSystemName) - .setCodeSystemVersion(codeSystemVersion); + .setMessage("Concept Display \"" + theDisplayToValidate + "\" does not match expected \"" + nextExpansionCode.getDisplay() + "\"") + .setCodeSystemName(codeSystemResourceName) + .setCodeSystemVersion(codeSystemResourceVersion); } } } @@ -295,12 +336,12 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu ValidationMessage.IssueSeverity severity; String message; - if ("fragment".equals(codeSystemContentMode)) { + if ("fragment".equals(codeSystemResourceContentMode)) { severity = ValidationMessage.IssueSeverity.WARNING; - message = "Unknown code in fragment CodeSystem '" + (isNotBlank(theCodeSystem) ? theCodeSystem + "#" : "") + theCode + "'"; + message = "Unknown code in fragment CodeSystem '" + (isNotBlank(theCodeSystemIdentifierToValidate) ? theCodeSystemIdentifierToValidate + "#" : "") + theCodeToValidate + "'"; } else { severity = ValidationMessage.IssueSeverity.ERROR; - message = "Unknown code '" + (isNotBlank(theCodeSystem) ? theCodeSystem + "#" : "") + theCode + "'"; + message = "Unknown code '" + (isNotBlank(theCodeSystemIdentifierToValidate) ? theCodeSystemIdentifierToValidate + "#" : "") + theCodeToValidate + "'"; } return new CodeValidationResult() @@ -314,7 +355,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu } @Nullable - private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2Hl7Org(ValidationSupportContext theValidationSupportContext, ValueSet theInput, @Nullable String theWantSystem, @Nullable String theWantCode) { + private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2Hl7Org(ValidationSupportContext theValidationSupportContext, ValueSet theInput, @Nullable String theWantSystemIdentifier, @Nullable String theWantCode) { Function codeSystemLoader = t -> { org.hl7.fhir.dstu2.model.ValueSet codeSystem = (org.hl7.fhir.dstu2.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); CodeSystem retVal = new CodeSystem(); @@ -327,12 +368,12 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu }; org.hl7.fhir.r5.model.ValueSet input = ValueSet10_50.convertValueSet(theInput); - org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(theValidationSupportContext, input, codeSystemLoader, valueSetLoader, theWantSystem, theWantCode); + org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(theValidationSupportContext, input, codeSystemLoader, valueSetLoader, theWantSystemIdentifier, theWantCode); return (output); } @Nullable - private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2(ValidationSupportContext theValidationSupportContext, ca.uhn.fhir.model.dstu2.resource.ValueSet theInput, @Nullable String theWantSystem, @Nullable String theWantCode) { + private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2(ValidationSupportContext theValidationSupportContext, ca.uhn.fhir.model.dstu2.resource.ValueSet theInput, @Nullable String theWantSystemIdentifier, @Nullable String theWantCode) { IParser parserRi = FhirContext.forCached(FhirVersionEnum.DSTU2_HL7ORG).newJsonParser(); IParser parserHapi = FhirContext.forCached(FhirVersionEnum.DSTU2).newJsonParser(); @@ -355,7 +396,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu org.hl7.fhir.dstu2.model.ValueSet valueSetRi = parserRi.parseResource(org.hl7.fhir.dstu2.model.ValueSet.class, parserHapi.encodeResourceToString(theInput)); org.hl7.fhir.r5.model.ValueSet input = ValueSet10_50.convertValueSet(valueSetRi); - org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(theValidationSupportContext, input, codeSystemLoader, valueSetLoader, theWantSystem, theWantCode); + org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(theValidationSupportContext, input, codeSystemLoader, valueSetLoader, theWantSystemIdentifier, theWantCode); return (output); } @@ -404,7 +445,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu } @Nullable - private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu3(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.dstu3.model.ValueSet theInput, @Nullable String theWantSystem, @Nullable String theWantCode) { + private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu3(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.dstu3.model.ValueSet theInput, @Nullable String theWantSystemIdentifier, @Nullable String theWantCode) { Function codeSystemLoader = t -> { org.hl7.fhir.dstu3.model.CodeSystem codeSystem = (org.hl7.fhir.dstu3.model.CodeSystem) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); return CodeSystem30_50.convertCodeSystem(codeSystem); @@ -415,12 +456,12 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu }; org.hl7.fhir.r5.model.ValueSet input = ValueSet30_50.convertValueSet(theInput); - org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(theValidationSupportContext, input, codeSystemLoader, valueSetLoader, theWantSystem, theWantCode); + org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(theValidationSupportContext, input, codeSystemLoader, valueSetLoader, theWantSystemIdentifier, theWantCode); return (output); } @Nullable - private org.hl7.fhir.r5.model.ValueSet expandValueSetR4(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r4.model.ValueSet theInput, @Nullable String theWantSystem, @Nullable String theWantCode) { + private org.hl7.fhir.r5.model.ValueSet expandValueSetR4(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r4.model.ValueSet theInput, @Nullable String theWantSystemIdentifier, @Nullable String theWantCode) { Function codeSystemLoader = t -> { org.hl7.fhir.r4.model.CodeSystem codeSystem = (org.hl7.fhir.r4.model.CodeSystem) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); return CodeSystem40_50.convertCodeSystem(codeSystem); @@ -431,7 +472,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu }; org.hl7.fhir.r5.model.ValueSet input = ValueSet40_50.convertValueSet(theInput); - org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(theValidationSupportContext, input, codeSystemLoader, valueSetLoader, theWantSystem, theWantCode); + org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(theValidationSupportContext, input, codeSystemLoader, valueSetLoader, theWantSystemIdentifier, theWantCode); return (output); } @@ -444,12 +485,12 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu } @Nullable - private org.hl7.fhir.r5.model.ValueSet expandValueSetR5(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r5.model.ValueSet theInput, Function theCodeSystemLoader, Function theValueSetLoader, @Nullable String theWantSystem, @Nullable String theWantCode) { + private org.hl7.fhir.r5.model.ValueSet expandValueSetR5(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r5.model.ValueSet theInput, Function theCodeSystemLoader, Function theValueSetLoader, @Nullable String theWantSystemIdentifier, @Nullable String theWantCode) { Set concepts = new HashSet<>(); try { - expandValueSetR5IncludeOrExclude(theValidationSupportContext, concepts, theCodeSystemLoader, theValueSetLoader, theInput.getCompose().getInclude(), true, theWantSystem, theWantCode); - expandValueSetR5IncludeOrExclude(theValidationSupportContext, concepts, theCodeSystemLoader, theValueSetLoader, theInput.getCompose().getExclude(), false, theWantSystem, theWantCode); + expandValueSetR5IncludeOrExclude(theValidationSupportContext, concepts, theCodeSystemLoader, theValueSetLoader, theInput.getCompose().getInclude(), true, theWantSystemIdentifier, theWantCode); + expandValueSetR5IncludeOrExclude(theValidationSupportContext, concepts, theCodeSystemLoader, theValueSetLoader, theInput.getCompose().getExclude(), false, theWantSystemIdentifier, theWantCode); } catch (ExpansionCouldNotBeCompletedInternallyException e) { return null; } @@ -460,23 +501,46 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu contains.setSystem(next.getSystem()); contains.setCode(next.getCode()); contains.setDisplay(next.getDisplay()); + contains.setVersion(next.getSystemVersion()); } return retVal; } - private void expandValueSetR5IncludeOrExclude(ValidationSupportContext theValidationSupportContext, Set theConcepts, Function theCodeSystemLoader, Function theValueSetLoader, List theComposeList, boolean theComposeListIsInclude, @Nullable String theWantSystem, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException { + private void expandValueSetR5IncludeOrExclude(ValidationSupportContext theValidationSupportContext, Set theConcepts, Function theCodeSystemLoader, Function theValueSetLoader, List theComposeList, boolean theComposeListIsInclude, @Nullable String theWantSystemIdentifier, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException { + String wantSystemUrl = null; + String wantSystemVersion = null; + if (theWantSystemIdentifier != null) { + int versionIndex = theWantSystemIdentifier.indexOf("|"); + if (versionIndex > -1) { + wantSystemUrl = theWantSystemIdentifier.substring(0,versionIndex); + wantSystemVersion = theWantSystemIdentifier.substring(versionIndex+1); + } else { + wantSystemUrl = theWantSystemIdentifier; + } + } + for (org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent nextInclude : theComposeList) { List nextCodeList = new ArrayList<>(); - String system = nextInclude.getSystem(); - if (isNotBlank(system)) { + String includeOrExcludeConceptSystemUrl = nextInclude.getSystem(); + String includeOrExcludeConceptSystemVersion = nextInclude.getVersion(); + if (isNotBlank(includeOrExcludeConceptSystemUrl)) { - if (theWantSystem != null && !theWantSystem.equals(system)) { + if (wantSystemUrl != null && !wantSystemUrl.equals(includeOrExcludeConceptSystemUrl)) { continue; } - CodeSystem codeSystem = theCodeSystemLoader.apply(system); + if (wantSystemVersion != null && !wantSystemVersion.equals(includeOrExcludeConceptSystemVersion)) { + continue; + } + + CodeSystem includeOrExcludeSystemResource; + if (includeOrExcludeConceptSystemVersion != null) { + includeOrExcludeSystemResource = theCodeSystemLoader.apply(includeOrExcludeConceptSystemUrl + "|" + includeOrExcludeConceptSystemVersion); + } else { + includeOrExcludeSystemResource = theCodeSystemLoader.apply(includeOrExcludeConceptSystemUrl); + } Set wantCodes; if (nextInclude.getConcept().isEmpty()) { @@ -488,18 +552,18 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu } boolean ableToHandleCode = false; - if (codeSystem == null || codeSystem.getContent() == CodeSystem.CodeSystemContentMode.NOTPRESENT) { + if (includeOrExcludeSystemResource == null || includeOrExcludeSystemResource.getContent() == CodeSystem.CodeSystemContentMode.NOTPRESENT) { if (theWantCode != null) { - if (theValidationSupportContext.getRootValidationSupport().isCodeSystemSupported(theValidationSupportContext, system)) { - LookupCodeResult lookup = theValidationSupportContext.getRootValidationSupport().lookupCode(theValidationSupportContext, system, theWantCode); + if (theValidationSupportContext.getRootValidationSupport().isCodeSystemSupported(theValidationSupportContext, includeOrExcludeConceptSystemUrl)) { + LookupCodeResult lookup = theValidationSupportContext.getRootValidationSupport().lookupCode(theValidationSupportContext, includeOrExcludeConceptSystemUrl, theWantCode); if (lookup != null && lookup.isFound()) { CodeSystem.ConceptDefinitionComponent conceptDefinition = new CodeSystem.ConceptDefinitionComponent() .addConcept() .setCode(theWantCode) .setDisplay(lookup.getCodeDisplay()); List codesList = Collections.singletonList(conceptDefinition); - addCodes(system, codesList, nextCodeList, wantCodes); + addCodes(includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion, codesList, nextCodeList, wantCodes); ableToHandleCode = true; } } else if (theComposeListIsInclude) { @@ -514,7 +578,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu * enumerate a set of good codes for them is a nice compromise there. */ for (org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent next : theComposeList) { - if (Objects.equals(next.getSystem(), theWantSystem)) { + if (Objects.equals(next.getSystem(), theWantSystemIdentifier)) { Optional matchingEnumeratedConcept = next.getConcept().stream().filter(t -> Objects.equals(t.getCode(), theWantCode)).findFirst(); if (matchingEnumeratedConcept.isPresent()) { CodeSystem.ConceptDefinitionComponent conceptDefinition = new CodeSystem.ConceptDefinitionComponent() @@ -522,7 +586,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu .setCode(theWantCode) .setDisplay(matchingEnumeratedConcept.get().getDisplay()); List codesList = Collections.singletonList(conceptDefinition); - addCodes(system, codesList, nextCodeList, wantCodes); + addCodes(includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion, codesList, nextCodeList, wantCodes); ableToHandleCode = true; break; } @@ -540,8 +604,8 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu throw new ExpansionCouldNotBeCompletedInternallyException(); } - if (codeSystem != null && codeSystem.getContent() != CodeSystem.CodeSystemContentMode.NOTPRESENT) { - addCodes(system, codeSystem.getConcept(), nextCodeList, wantCodes); + if (includeOrExcludeSystemResource != null && includeOrExcludeSystemResource.getContent() != CodeSystem.CodeSystemContentMode.NOTPRESENT) { + addCodes(includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion, includeOrExcludeSystemResource.getConcept(), nextCodeList, wantCodes); } } @@ -549,12 +613,12 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu for (CanonicalType nextValueSetInclude : nextInclude.getValueSet()) { org.hl7.fhir.r5.model.ValueSet vs = theValueSetLoader.apply(nextValueSetInclude.getValueAsString()); if (vs != null) { - org.hl7.fhir.r5.model.ValueSet subExpansion = expandValueSetR5(theValidationSupportContext, vs, theCodeSystemLoader, theValueSetLoader, theWantSystem, theWantCode); + org.hl7.fhir.r5.model.ValueSet subExpansion = expandValueSetR5(theValidationSupportContext, vs, theCodeSystemLoader, theValueSetLoader, theWantSystemIdentifier, theWantCode); if (subExpansion == null) { throw new ExpansionCouldNotBeCompletedInternallyException(); } for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : subExpansion.getExpansion().getContains()) { - nextCodeList.add(new VersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); + nextCodeList.add(new VersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); } } } @@ -569,14 +633,14 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu } - private void addCodes(String theSystem, List theSource, List theTarget, Set theCodeFilter) { + private void addCodes(String theCodeSystemUrl, String theCodeSystemVersion, List theSource, List theTarget, Set theCodeFilter) { for (CodeSystem.ConceptDefinitionComponent next : theSource) { if (isNotBlank(next.getCode())) { if (theCodeFilter == null || theCodeFilter.contains(next.getCode())) { - theTarget.add(new VersionIndependentConcept(theSystem, next.getCode(), next.getDisplay())); + theTarget.add(new VersionIndependentConcept(theCodeSystemUrl, next.getCode(), next.getDisplay(), theCodeSystemVersion)); } } - addCodes(theSystem, next.getConcept(), theTarget, theCodeFilter); + addCodes(theCodeSystemUrl, theCodeSystemVersion, next.getConcept(), theTarget, theCodeFilter); } } @@ -593,21 +657,21 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu private static void flattenAndConvertCodesDstu3(List theInput, List theVersionIndependentConcepts) { for (org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { - theVersionIndependentConcepts.add(new VersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); + theVersionIndependentConcepts.add(new VersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); flattenAndConvertCodesDstu3(next.getContains(), theVersionIndependentConcepts); } } private static void flattenAndConvertCodesR4(List theInput, List theVersionIndependentConcepts) { for (org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { - theVersionIndependentConcepts.add(new VersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); + theVersionIndependentConcepts.add(new VersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); flattenAndConvertCodesR4(next.getContains(), theVersionIndependentConcepts); } } private static void flattenAndConvertCodesR5(List theInput, List theVersionIndependentConcepts) { for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { - theVersionIndependentConcepts.add(new VersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); + theVersionIndependentConcepts.add(new VersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); flattenAndConvertCodesR5(next.getContains(), theVersionIndependentConcepts); } }