From a561b67fa097f122cc230614c4f85f9007b384b4 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Sat, 16 Sep 2023 13:46:26 -0400 Subject: [PATCH 01/10] Test fixes --- .../extractor/SearchParamExtractorR4.java | 3 +- .../extractor/SearchParamExtractorR4B.java | 3 +- .../extractor/SearchParamExtractorR5.java | 3 +- .../dao/r4/FhirResourceDaoR4ValidateTest.java | 16 +- ...ownCodeSystemWarningValidationSupport.java | 7 +- .../validator/BaseValidatorBridge.java | 8 +- .../validator/FhirInstanceValidator.java | 3 +- .../VersionSpecificWorkerContextWrapper.java | 1421 +++++++++-------- 8 files changed, 753 insertions(+), 711 deletions(-) diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java index 32a22686730c..0e7f6869554a 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java @@ -39,6 +39,7 @@ import org.hl7.fhir.r4.model.TypeDetails; import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.utils.FHIRPathEngine; +import org.hl7.fhir.r4.utils.FHIRPathUtilityClasses; import java.util.Collections; import java.util.HashMap; @@ -120,7 +121,7 @@ public boolean log(String argument, List focus) { } @Override - public FunctionDetails resolveFunction(String functionName) { + public FHIRPathUtilityClasses.FunctionDetails resolveFunction(String functionName) { return null; } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4B.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4B.java index 749df636d955..08a54dd975f1 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4B.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4B.java @@ -39,6 +39,7 @@ import org.hl7.fhir.r4b.model.TypeDetails; import org.hl7.fhir.r4b.model.ValueSet; import org.hl7.fhir.r4b.utils.FHIRPathEngine; +import org.hl7.fhir.r4b.utils.FHIRPathUtilityClasses; import java.util.Collections; import java.util.HashMap; @@ -120,7 +121,7 @@ public boolean log(String argument, List focus) { } @Override - public FunctionDetails resolveFunction(String functionName) { + public FHIRPathUtilityClasses.FunctionDetails resolveFunction(String functionName) { return null; } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java index 142432415f1b..4bce79a31fcf 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java @@ -38,6 +38,7 @@ import org.hl7.fhir.r5.model.TypeDetails; import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.utils.FHIRPathEngine; +import org.hl7.fhir.r5.utils.FHIRPathUtilityClasses; import java.util.Collections; import java.util.HashMap; @@ -117,7 +118,7 @@ public boolean log(String argument, List focus) { } @Override - public FunctionDetails resolveFunction(String functionName) { + public FHIRPathUtilityClasses.FunctionDetails resolveFunction(String functionName) { return null; } diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java index b8ece1bac318..1f5a4ca2dbf5 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java @@ -125,7 +125,7 @@ public void testValidateCodeInValueSetWithUnknownCodeSystem_FailValidation() { ourLog.info(encoded); assertEquals(1, oo.getIssue().size(), encoded); assertThat(oo.getIssue().get(0).getDiagnostics(), - containsString("The code provided (http://cs#code99) is not in the value set")); + containsString("provided (http://cs#code99) is not in the value set")); assertThat(oo.getIssue().get(0).getDiagnostics(), containsString("Unknown code 'http://cs#code99' for in-memory expansion of ValueSet 'http://vs'")); assertEquals(OperationOutcome.IssueSeverity.ERROR, oo.getIssueFirstRep().getSeverity(), encoded); @@ -159,7 +159,7 @@ public void testValidateCodeInEnumeratedValueSetWithUnknownCodeSystem_Informatio ourLog.info(encoded); assertEquals(1, oo.getIssue().size()); assertThat(oo.getIssueFirstRep().getDiagnostics(), - containsString("The code provided (http://cs#code99) is not in the value set")); + containsString("provided (http://cs#code99) is not in the value set")); assertThat(oo.getIssueFirstRep().getDiagnostics(), containsString("Unknown code 'http://cs#code99' for in-memory expansion of ValueSet 'http://vs'")); assertEquals(OperationOutcome.IssueSeverity.ERROR, oo.getIssueFirstRep().getSeverity()); @@ -199,7 +199,7 @@ public void testValidateCodeInEnumeratedValueSetWithUnknownCodeSystem_Warning() containsString("CodeSystem is unknown and can't be validated: http://cs for 'http://cs#code99'")); assertEquals(OperationOutcome.IssueSeverity.WARNING, oo.getIssue().get(0).getSeverity()); assertThat(oo.getIssue().get(1).getDiagnostics(), - containsString("The code provided (http://cs#code99) is not in the value set 'ValueSet[http://vs]'")); + containsString("provided (http://cs#code99) is not in the value set 'ValueSet[http://vs]'")); assertEquals(OperationOutcome.IssueSeverity.ERROR, oo.getIssue().get(1).getSeverity()); } @@ -239,7 +239,7 @@ public void testValidateCodeInEnumeratedValueSetWithUnknownCodeSystem_Error() { ourLog.info(encoded); assertEquals(1, oo.getIssue().size()); assertThat(oo.getIssue().get(0).getDiagnostics(), - containsString("The code provided (http://cs#code99) is not in the value set")); + containsString("provided (http://cs#code99) is not in the value set")); assertEquals(OperationOutcome.IssueSeverity.ERROR, oo.getIssueFirstRep().getSeverity()); } @@ -335,7 +335,7 @@ public void testValidateCodeInNonEnumeratedValueSetWithUnknownCodeSystem_Error() ourLog.info(encoded); assertEquals(1, oo.getIssue().size()); assertThat(oo.getIssue().get(0).getDiagnostics(), - containsString("The code provided (http://cs#code1) is not in the value set")); + containsString("provided (http://cs#code1) is not in the value set")); assertThat(oo.getIssue().get(0).getDiagnostics(), containsString("Failed to expand ValueSet 'http://vs' (in-memory). Could not validate code http://cs#code1")); assertThat(oo.getIssue().get(0).getDiagnostics(), @@ -343,7 +343,7 @@ public void testValidateCodeInNonEnumeratedValueSetWithUnknownCodeSystem_Error() assertEquals(OperationOutcome.IssueSeverity.ERROR, oo.getIssueFirstRep().getSeverity()); assertEquals(27, ((IntegerType)oo.getIssue().get(0).getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-line").getValue()).getValue()); assertEquals(4, ((IntegerType)oo.getIssue().get(0).getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-col").getValue()).getValue()); - assertEquals("Terminology_TX_Confirm_4a", ((StringType)oo.getIssue().get(0).getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-message-id").getValue()).getValue()); + assertEquals("Terminology_TX_NoValid_12", ((StringType)oo.getIssue().get(0).getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-message-id").getValue()).getValue()); assertEquals(OperationOutcome.IssueType.PROCESSING, oo.getIssue().get(0).getCode()); assertEquals(OperationOutcome.IssueSeverity.ERROR, oo.getIssue().get(0).getSeverity()); assertEquals(2, oo.getIssue().get(0).getLocation().size()); @@ -509,7 +509,7 @@ public void testValidateCodeInValueSetWithBuiltInCodeSystem() throws IOException String outcomeStr = myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome); ourLog.info("Validation outcome: {}", outcomeStr); assertThat(outcomeStr, - containsString("The code provided (http://unitsofmeasure.org#cm) is not in the value set")); + containsString("provided (http://unitsofmeasure.org#cm) is not in the value set")); // Before, the VS wasn't pre-expanded. Try again with it pre-expanded runInTransaction(() -> { @@ -538,7 +538,7 @@ public void testValidateCodeInValueSetWithBuiltInCodeSystem() throws IOException outcomeStr = myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome); ourLog.info("Validation outcome: {}", outcomeStr); assertThat(outcomeStr, - containsString("The code provided (http://unitsofmeasure.org#cm) is not in the value set")); + containsString("provided (http://unitsofmeasure.org#cm) is not in the value set")); } diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/UnknownCodeSystemWarningValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/UnknownCodeSystemWarningValidationSupport.java index 266179143052..3d982b604b9a 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/UnknownCodeSystemWarningValidationSupport.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/UnknownCodeSystemWarningValidationSupport.java @@ -76,11 +76,12 @@ public CodeValidationResult validateCode( result.setSeverity(myNonExistentCodeSystemSeverity); result.setMessage("CodeSystem is unknown and can't be validated: " + theCodeSystem); + // For information level, we just strip out the severity+message entirely + // so that nothing appears in the validation result if (myNonExistentCodeSystemSeverity == IssueSeverity.INFORMATION) { - // for warnings, we don't set the code - // cause if we do, the severity is stripped out - // (see VersionSpecificWorkerContextWrapper.convertValidationResult) result.setCode(theCode); + result.setSeverity(null); + result.setMessage(null); } return result; diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/BaseValidatorBridge.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/BaseValidatorBridge.java index 3f6e13c80ea3..4653100aa554 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/BaseValidatorBridge.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/BaseValidatorBridge.java @@ -37,7 +37,13 @@ private void doValidate(IValidationContext theCtx) { ResultSeverityEnum.fromCode(riMessage.getLevel().toCode())); } if (riMessage.getMessageId() != null) { - hapiMessage.setMessageId(riMessage.getMessageId()); + // In BaseValidator, the messageId gets populated with the raw message because + // there is an assumption that it's a message key and not an actual message. But + // messsages coming from our internal terminology service don't work that + // way, so we strip them by checking if the ID is actually a sentence + if (!riMessage.getMessageId().contains(" ")) { + hapiMessage.setMessageId(riMessage.getMessageId()); + } } if (riMessage.sliceText != null && riMessage.sliceText.length > 0) { hapiMessage.setSliceMessages(Arrays.asList(riMessage.sliceText)); diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirInstanceValidator.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirInstanceValidator.java index b1e76206df0d..bf74454d42ca 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirInstanceValidator.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirInstanceValidator.java @@ -13,6 +13,7 @@ import org.hl7.fhir.r5.model.TypeDetails; import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.utils.FHIRPathEngine; +import org.hl7.fhir.r5.utils.FHIRPathUtilityClasses; import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher; import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel; @@ -303,7 +304,7 @@ public boolean log(String argument, List focus) { } @Override - public FunctionDetails resolveFunction(String functionName) { + public FHIRPathUtilityClasses.FunctionDetails resolveFunction(String functionName) { return null; } diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java index bb5a23d0a1b3..bac8423c7fd9 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java @@ -41,6 +41,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; @@ -48,704 +50,733 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWorkerContext { - private static final Logger ourLog = LoggerFactory.getLogger(VersionSpecificWorkerContextWrapper.class); - private final ValidationSupportContext myValidationSupportContext; - private final VersionCanonicalizer myVersionCanonicalizer; - private final LoadingCache myFetchResourceCache; - private volatile List myAllStructures; - private org.hl7.fhir.r5.model.Parameters myExpansionProfile; - - public VersionSpecificWorkerContextWrapper( - ValidationSupportContext theValidationSupportContext, VersionCanonicalizer theVersionCanonicalizer) { - myValidationSupportContext = theValidationSupportContext; - myVersionCanonicalizer = theVersionCanonicalizer; - - long timeoutMillis = HapiSystemProperties.getTestValidationResourceCachesMs(); - - myFetchResourceCache = CacheFactory.build(timeoutMillis, 10000, key -> { - String fetchResourceName = key.getResourceName(); - if (myValidationSupportContext - .getRootValidationSupport() - .getFhirContext() - .getVersion() - .getVersion() - == FhirVersionEnum.DSTU2) { - if ("CodeSystem".equals(fetchResourceName)) { - fetchResourceName = "ValueSet"; - } - } - - Class fetchResourceType; - if (fetchResourceName.equals("Resource")) { - fetchResourceType = null; - } else { - fetchResourceType = myValidationSupportContext - .getRootValidationSupport() - .getFhirContext() - .getResourceDefinition(fetchResourceName) - .getImplementingClass(); - } - - IBaseResource fetched = myValidationSupportContext - .getRootValidationSupport() - .fetchResource(fetchResourceType, key.getUri()); - - Resource canonical = myVersionCanonicalizer.resourceToValidatorCanonical(fetched); - - if (canonical instanceof StructureDefinition) { - StructureDefinition canonicalSd = (StructureDefinition) canonical; - if (canonicalSd.getSnapshot().isEmpty()) { - ourLog.info("Generating snapshot for StructureDefinition: {}", canonicalSd.getUrl()); - fetched = myValidationSupportContext - .getRootValidationSupport() - .generateSnapshot(theValidationSupportContext, fetched, "", null, ""); - Validate.isTrue( - fetched != null, - "StructureDefinition %s has no snapshot, and no snapshot generator is configured", - key.getUri()); - canonical = myVersionCanonicalizer.resourceToValidatorCanonical(fetched); - } - } - - return canonical; - }); - - setValidationMessageLanguage(getLocale()); - } - - @Override - public Set getBinaryKeysAsSet() { - throw new UnsupportedOperationException(Msg.code(2118)); - } - - @Override - public boolean hasBinaryKey(String s) { - return myValidationSupportContext.getRootValidationSupport().fetchBinary(s) != null; - } - - @Override - public byte[] getBinaryForKey(String s) { - return myValidationSupportContext.getRootValidationSupport().fetchBinary(s); - } - - @Override - public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader) throws FHIRException { - throw new UnsupportedOperationException(Msg.code(652)); - } - - @Override - public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader, List types) - throws FileNotFoundException, IOException, FHIRException { - throw new UnsupportedOperationException(Msg.code(653)); - } - - @Override - public int loadFromPackageAndDependencies(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm) - throws FHIRException { - throw new UnsupportedOperationException(Msg.code(654)); - } - - @Override - public boolean hasPackage(String id, String ver) { - throw new UnsupportedOperationException(Msg.code(655)); - } - - @Override - public boolean hasPackage(PackageInformation packageInformation) { - return false; - } - - @Override - public PackageInformation getPackage(String id, String ver) { - return null; - } - - @Override - public int getClientRetryCount() { - throw new UnsupportedOperationException(Msg.code(656)); - } - - @Override - public IWorkerContext setClientRetryCount(int value) { - throw new UnsupportedOperationException(Msg.code(657)); - } - - @Override - public TimeTracker clock() { - return null; - } - - @Override - public IWorkerContextManager.IPackageLoadingTracker getPackageTracker() { - throw new UnsupportedOperationException(Msg.code(2235)); - } - - @Override - public IWorkerContext setPackageTracker(IWorkerContextManager.IPackageLoadingTracker packageTracker) { - throw new UnsupportedOperationException(Msg.code(2266)); - } - - @Override - public String getSpecUrl() { - return ""; - } - - @Override - public PEBuilder getProfiledElementBuilder( - PEBuilder.PEElementPropertiesPolicy thePEElementPropertiesPolicy, boolean theB) { - throw new UnsupportedOperationException(Msg.code(2264)); - } - - @Override - public PackageInformation getPackageForUrl(String s) { - throw new UnsupportedOperationException(Msg.code(2236)); - } - - @Override - public org.hl7.fhir.r5.model.Parameters getExpansionParameters() { - return myExpansionProfile; - } - - @Override - public void setExpansionProfile(org.hl7.fhir.r5.model.Parameters expParameters) { - myExpansionProfile = expParameters; - } - - private List allStructures() { - - List retVal = myAllStructures; - if (retVal == null) { - retVal = new ArrayList<>(); - for (IBaseResource next : - myValidationSupportContext.getRootValidationSupport().fetchAllStructureDefinitions()) { - try { - StructureDefinition converted = myVersionCanonicalizer.structureDefinitionToCanonical(next); - retVal.add(converted); - } catch (FHIRException e) { - throw new InternalErrorException(Msg.code(659) + e); - } - } - myAllStructures = retVal; - } - - return retVal; - } - - @Override - public void cacheResource(Resource res) {} - - @Override - public void cacheResourceFromPackage(Resource res, PackageInformation packageDetails) throws FHIRException {} - - @Override - public void cachePackage(PackageInformation packageInformation) {} - - @Nonnull - private ValidationResult convertValidationResult( - String theSystem, @Nullable IValidationSupport.CodeValidationResult theResult) { - ValidationResult retVal = null; - if (theResult != null) { - String code = theResult.getCode(); - String display = theResult.getDisplay(); - - String issueSeverity = theResult.getSeverityCode(); - String message = theResult.getMessage(); - if (isNotBlank(code)) { - retVal = new ValidationResult( - theSystem, - null, - new org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent() - .setCode(code) - .setDisplay(display), - null); - } else if (isNotBlank(issueSeverity)) { - retVal = new ValidationResult( - ValidationMessage.IssueSeverity.fromCode(issueSeverity), - message, - TerminologyServiceErrorClass.UNKNOWN, - null); - } - } - - if (retVal == null) { - retVal = new ValidationResult(ValidationMessage.IssueSeverity.ERROR, "Validation failed", null); - } - - return retVal; - } - - @Override - public ValueSetExpansionOutcome expandVS( - org.hl7.fhir.r5.model.ValueSet source, boolean cacheOk, boolean Hierarchical) { - IBaseResource convertedSource; - try { - convertedSource = myVersionCanonicalizer.valueSetFromValidatorCanonical(source); - } catch (FHIRException e) { - throw new InternalErrorException(Msg.code(661) + e); - } - IValidationSupport.ValueSetExpansionOutcome expanded = myValidationSupportContext - .getRootValidationSupport() - .expandValueSet(myValidationSupportContext, null, convertedSource); - - org.hl7.fhir.r5.model.ValueSet convertedResult = null; - if (expanded.getValueSet() != null) { - try { - convertedResult = myVersionCanonicalizer.valueSetToValidatorCanonical(expanded.getValueSet()); - } catch (FHIRException e) { - throw new InternalErrorException(Msg.code(662) + e); - } - } - - String error = expanded.getError(); - TerminologyServiceErrorClass result = null; - - return new ValueSetExpansionOutcome(convertedResult, error, result); - } - - @Override - public ValueSetExpansionOutcome expandVS( - Resource src, - org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent binding, - boolean cacheOk, - boolean Hierarchical) { - throw new UnsupportedOperationException(Msg.code(663)); - } - - @Override - public ValueSetExpansionOutcome expandVS(ValueSet.ConceptSetComponent inc, boolean hierarchical, boolean noInactive) - throws TerminologyServiceException { - throw new UnsupportedOperationException(Msg.code(664)); - } - - @Override - public Locale getLocale() { - return myValidationSupportContext - .getRootValidationSupport() - .getFhirContext() - .getLocalizer() - .getLocale(); - } - - @Override - public void setLocale(Locale locale) { - // ignore - } - - @Override - public org.hl7.fhir.r5.model.CodeSystem fetchCodeSystem(String system) { - IBaseResource fetched = - myValidationSupportContext.getRootValidationSupport().fetchCodeSystem(system); - if (fetched == null) { - return null; - } - try { - return (org.hl7.fhir.r5.model.CodeSystem) myVersionCanonicalizer.codeSystemToValidatorCanonical(fetched); - } catch (FHIRException e) { - throw new InternalErrorException(Msg.code(665) + e); - } - } - - @Override - public CodeSystem fetchCodeSystem(String system, String verison) { - IBaseResource fetched = - myValidationSupportContext.getRootValidationSupport().fetchCodeSystem(system); - if (fetched == null) { - return null; - } - try { - return (org.hl7.fhir.r5.model.CodeSystem) myVersionCanonicalizer.codeSystemToValidatorCanonical(fetched); - } catch (FHIRException e) { - throw new InternalErrorException(Msg.code(1992) + e); - } - } - - @Override - public CodeSystem fetchSupplementedCodeSystem(String system) { - return null; - } - - @Override - public CodeSystem fetchSupplementedCodeSystem(String system, String version) { - return null; - } - - @Override - public T fetchResourceRaw(Class class_, String uri) { - return fetchResource(class_, uri); - } - - @Override - public T fetchResource(Class class_, String uri) { - - if (isBlank(uri)) { - return null; - } - - ResourceKey key = new ResourceKey(class_.getSimpleName(), uri); - @SuppressWarnings("unchecked") - T retVal = (T) myFetchResourceCache.get(key); - - return retVal; - } - - @Override - public Resource fetchResourceById(String type, String uri) { - throw new UnsupportedOperationException(Msg.code(666)); - } - - @Override - public T fetchResourceWithException(Class class_, String uri) throws FHIRException { - T retVal = fetchResource(class_, uri); - if (retVal == null) { - throw new FHIRException( - Msg.code(667) + "Can not find resource of type " + class_.getSimpleName() + " with uri " + uri); - } - return retVal; - } - - @Override - public T fetchResource(Class class_, String uri, String version) { - return fetchResource(class_, uri + "|" + version); - } - - @Override - public T fetchResource(Class class_, String uri, Resource canonicalForSource) { - return fetchResource(class_, uri); - } - - @Override - public T fetchResourceWithException(Class class_, String uri, Resource sourceOfReference) - throws FHIRException { - throw new UnsupportedOperationException(Msg.code(2214)); - } - - @Override - public List getResourceNames() { - return new ArrayList<>(myValidationSupportContext - .getRootValidationSupport() - .getFhirContext() - .getResourceTypes()); - } - - @Override - public Set getResourceNamesAsSet() { - return myValidationSupportContext - .getRootValidationSupport() - .getFhirContext() - .getResourceTypes(); - } - - @Override - public StructureDefinition fetchTypeDefinition(String typeName) { - return fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + typeName); - } - - @Override - public List fetchTypeDefinitions(String typeName) { - List allStructures = new ArrayList<>(allStructures()); - allStructures.removeIf(sd -> !sd.hasType() || !sd.getType().equals(typeName)); - return allStructures; - } - - @Override - public UcumService getUcumService() { - throw new UnsupportedOperationException(Msg.code(676)); - } - - @Override - public void setUcumService(UcumService ucumService) { - throw new UnsupportedOperationException(Msg.code(677)); - } - - @Override - public String getVersion() { - return myValidationSupportContext - .getRootValidationSupport() - .getFhirContext() - .getVersion() - .getVersion() - .getFhirVersionString(); - } - - @Override - public boolean hasResource(Class class_, String uri) { - throw new UnsupportedOperationException(Msg.code(680)); - } - - @Override - public boolean isNoTerminologyServer() { - return false; - } - - @Override - public Set getCodeSystemsUsed() { - throw new UnsupportedOperationException(Msg.code(681)); - } - - @Override - public IResourceValidator newValidator() { - throw new UnsupportedOperationException(Msg.code(684)); - } - - @Override - public Map getNSUrlMap() { - throw new UnsupportedOperationException(Msg.code(2265)); - } - - @Override - public ILoggingService getLogger() { - return null; - } - - @Override - public void setLogger(ILoggingService logger) { - throw new UnsupportedOperationException(Msg.code(687)); - } - - @Override - public boolean supportsSystem(String system) { - return myValidationSupportContext - .getRootValidationSupport() - .isCodeSystemSupported(myValidationSupportContext, system); - } - - @Override - public TranslationServices translator() { - throw new UnsupportedOperationException(Msg.code(688)); - } - - @Override - public ValueSetExpansionOutcome expandVS( - ValueSet source, boolean cacheOk, boolean heiarchical, boolean incompleteOk) { - return null; - } - - @Override - public ValidationResult validateCode( - ValidationOptions theOptions, String system, String version, String code, String display) { - ConceptValidationOptions validationOptions = convertConceptValidationOptions(theOptions); - return doValidation(null, validationOptions, system, code, display); - } - - @Override - public ValidationResult validateCode( - ValidationOptions theOptions, - String theSystem, - String version, - String theCode, - String display, - ValueSet theValueSet) { - IBaseResource convertedVs = null; - - try { - if (theValueSet != null) { - convertedVs = myVersionCanonicalizer.valueSetFromValidatorCanonical(theValueSet); - } - } catch (FHIRException e) { - throw new InternalErrorException(Msg.code(689) + e); - } - - ConceptValidationOptions validationOptions = convertConceptValidationOptions(theOptions); - - return doValidation(convertedVs, validationOptions, theSystem, theCode, display); - } - - @Override - public ValidationResult validateCode( - ValidationOptions theOptions, String code, org.hl7.fhir.r5.model.ValueSet theValueSet) { - IBaseResource convertedVs = null; - try { - if (theValueSet != null) { - convertedVs = myVersionCanonicalizer.valueSetFromValidatorCanonical(theValueSet); - } - } catch (FHIRException e) { - throw new InternalErrorException(Msg.code(690) + e); - } - - ConceptValidationOptions validationOptions = - convertConceptValidationOptions(theOptions).setInferSystem(true); - - return doValidation(convertedVs, validationOptions, null, code, null); - } - - @Override - public ValidationResult validateCode( - ValidationOptions theOptions, - org.hl7.fhir.r5.model.Coding theCoding, - org.hl7.fhir.r5.model.ValueSet theValueSet) { - IBaseResource convertedVs = null; - - try { - if (theValueSet != null) { - convertedVs = myVersionCanonicalizer.valueSetFromValidatorCanonical(theValueSet); - } - } catch (FHIRException e) { - throw new InternalErrorException(Msg.code(691) + e); - } - - ConceptValidationOptions validationOptions = convertConceptValidationOptions(theOptions); - String system = theCoding.getSystem(); - String code = theCoding.getCode(); - String display = theCoding.getDisplay(); - - return doValidation(convertedVs, validationOptions, system, code, display); - } - - @Override - public ValidationResult validateCode( - ValidationOptions options, Coding code, ValueSet vs, ValidationContextCarrier ctxt) { - return validateCode(options, code, vs); - } - - @Override - public void validateCodeBatch( - ValidationOptions options, List codes, ValueSet vs) { - for (CodingValidationRequest next : codes) { - ValidationResult outcome = validateCode(options, next.getCoding(), vs); - next.setResult(outcome); - } - } - - @Nonnull - private ValidationResult doValidation( - IBaseResource theValueSet, - ConceptValidationOptions theValidationOptions, - String theSystem, - String theCode, - String theDisplay) { - IValidationSupport.CodeValidationResult result; - if (theValueSet != null) { - result = myValidationSupportContext - .getRootValidationSupport() - .validateCodeInValueSet( - myValidationSupportContext, - theValidationOptions, - theSystem, - theCode, - theDisplay, - theValueSet); - } else { - result = myValidationSupportContext - .getRootValidationSupport() - .validateCode( - myValidationSupportContext, theValidationOptions, theSystem, theCode, theDisplay, null); - } - return convertValidationResult(theSystem, result); - } - - @Override - public ValidationResult validateCode( - ValidationOptions theOptions, - org.hl7.fhir.r5.model.CodeableConcept code, - org.hl7.fhir.r5.model.ValueSet theVs) { - List validationResultsOk = new ArrayList<>(); - for (Coding next : code.getCoding()) { - ValidationResult retVal = validateCode(theOptions, next, theVs); - if (retVal.isOk()) { - if (myValidationSupportContext.isEnabledValidationForCodingsLogicalAnd()) { - validationResultsOk.add(retVal); - } else { - return retVal; - } - } - } - - if (code.getCoding().size() > 0 - && validationResultsOk.size() == code.getCoding().size()) { - return validationResultsOk.get(0); - } - - return new ValidationResult(ValidationMessage.IssueSeverity.ERROR, null, null); - } - - public void invalidateCaches() { - myFetchResourceCache.invalidateAll(); - } - - @Override - public List fetchResourcesByType(Class theClass) { - if (theClass.equals(StructureDefinition.class)) { - return (List) allStructures(); - } - throw new UnsupportedOperationException(Msg.code(650) + "Unable to fetch resources of type: " + theClass); - } - - private static class ResourceKey { - private final int myHashCode; - private final String myResourceName; - private final String myUri; - - private ResourceKey(String theResourceName, String theUri) { - myResourceName = theResourceName; - myUri = theUri; - myHashCode = new HashCodeBuilder(17, 37) - .append(myResourceName) - .append(myUri) - .toHashCode(); - } - - @Override - public boolean equals(Object theO) { - if (this == theO) { - return true; - } - - if (theO == null || getClass() != theO.getClass()) { - return false; - } - - ResourceKey that = (ResourceKey) theO; - - return new EqualsBuilder() - .append(myResourceName, that.myResourceName) - .append(myUri, that.myUri) - .isEquals(); - } - - public String getResourceName() { - return myResourceName; - } - - public String getUri() { - return myUri; - } - - @Override - public int hashCode() { - return myHashCode; - } - } - - public static ConceptValidationOptions convertConceptValidationOptions(ValidationOptions theOptions) { - ConceptValidationOptions retVal = new ConceptValidationOptions(); - if (theOptions.isGuessSystem()) { - retVal = retVal.setInferSystem(true); - } - return retVal; - } - - @Nonnull - public static VersionSpecificWorkerContextWrapper newVersionSpecificWorkerContextWrapper( - IValidationSupport theValidationSupport) { - VersionCanonicalizer versionCanonicalizer = new VersionCanonicalizer(theValidationSupport.getFhirContext()); - return new VersionSpecificWorkerContextWrapper( - new ValidationSupportContext(theValidationSupport), versionCanonicalizer); - } - - @Override - public boolean isForPublication() { - return false; - } - - @Override - public void setForPublication(boolean b) { - throw new UnsupportedOperationException(Msg.code(2351)); - } + private static final Logger ourLog = LoggerFactory.getLogger(VersionSpecificWorkerContextWrapper.class); + private final ValidationSupportContext myValidationSupportContext; + private final VersionCanonicalizer myVersionCanonicalizer; + private final LoadingCache myFetchResourceCache; + private volatile List myAllStructures; + private org.hl7.fhir.r5.model.Parameters myExpansionProfile; + + public VersionSpecificWorkerContextWrapper( + ValidationSupportContext theValidationSupportContext, VersionCanonicalizer theVersionCanonicalizer) { + myValidationSupportContext = theValidationSupportContext; + myVersionCanonicalizer = theVersionCanonicalizer; + + long timeoutMillis = HapiSystemProperties.getTestValidationResourceCachesMs(); + + myFetchResourceCache = CacheFactory.build(timeoutMillis, 10000, key -> { + String fetchResourceName = key.getResourceName(); + if (myValidationSupportContext + .getRootValidationSupport() + .getFhirContext() + .getVersion() + .getVersion() + == FhirVersionEnum.DSTU2) { + if ("CodeSystem".equals(fetchResourceName)) { + fetchResourceName = "ValueSet"; + } + } + + Class fetchResourceType; + if (fetchResourceName.equals("Resource")) { + fetchResourceType = null; + } else { + fetchResourceType = myValidationSupportContext + .getRootValidationSupport() + .getFhirContext() + .getResourceDefinition(fetchResourceName) + .getImplementingClass(); + } + + IBaseResource fetched = myValidationSupportContext + .getRootValidationSupport() + .fetchResource(fetchResourceType, key.getUri()); + + Resource canonical = myVersionCanonicalizer.resourceToValidatorCanonical(fetched); + + if (canonical instanceof StructureDefinition) { + StructureDefinition canonicalSd = (StructureDefinition) canonical; + if (canonicalSd.getSnapshot().isEmpty()) { + ourLog.info("Generating snapshot for StructureDefinition: {}", canonicalSd.getUrl()); + fetched = myValidationSupportContext + .getRootValidationSupport() + .generateSnapshot(theValidationSupportContext, fetched, "", null, ""); + Validate.isTrue( + fetched != null, + "StructureDefinition %s has no snapshot, and no snapshot generator is configured", + key.getUri()); + canonical = myVersionCanonicalizer.resourceToValidatorCanonical(fetched); + } + } + + return canonical; + }); + + setValidationMessageLanguage(getLocale()); + } + + @Override + public Set getBinaryKeysAsSet() { + throw new UnsupportedOperationException(Msg.code(2118)); + } + + @Override + public boolean hasBinaryKey(String s) { + return myValidationSupportContext.getRootValidationSupport().fetchBinary(s) != null; + } + + @Override + public byte[] getBinaryForKey(String s) { + return myValidationSupportContext.getRootValidationSupport().fetchBinary(s); + } + + @Override + public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader) throws FHIRException { + throw new UnsupportedOperationException(Msg.code(652)); + } + + @Override + public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader, List types) + throws FileNotFoundException, IOException, FHIRException { + throw new UnsupportedOperationException(Msg.code(653)); + } + + @Override + public int loadFromPackageAndDependencies(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm) + throws FHIRException { + throw new UnsupportedOperationException(Msg.code(654)); + } + + @Override + public boolean hasPackage(String id, String ver) { + throw new UnsupportedOperationException(Msg.code(655)); + } + + @Override + public boolean hasPackage(PackageInformation packageInformation) { + return false; + } + + @Override + public PackageInformation getPackage(String id, String ver) { + return null; + } + + @Override + public int getClientRetryCount() { + throw new UnsupportedOperationException(Msg.code(656)); + } + + @Override + public IWorkerContext setClientRetryCount(int value) { + throw new UnsupportedOperationException(Msg.code(657)); + } + + @Override + public TimeTracker clock() { + return null; + } + + @Override + public IWorkerContextManager.IPackageLoadingTracker getPackageTracker() { + throw new UnsupportedOperationException(Msg.code(2235)); + } + + @Override + public IWorkerContext setPackageTracker(IWorkerContextManager.IPackageLoadingTracker packageTracker) { + throw new UnsupportedOperationException(Msg.code(2266)); + } + + @Override + public String getSpecUrl() { + return ""; + } + + @Override + public PEBuilder getProfiledElementBuilder( + PEBuilder.PEElementPropertiesPolicy thePEElementPropertiesPolicy, boolean theB) { + throw new UnsupportedOperationException(Msg.code(2264)); + } + + @Override + public PackageInformation getPackageForUrl(String s) { + throw new UnsupportedOperationException(Msg.code(2236)); + } + + @Override + public org.hl7.fhir.r5.model.Parameters getExpansionParameters() { + return myExpansionProfile; + } + + @Override + public void setExpansionProfile(org.hl7.fhir.r5.model.Parameters expParameters) { + myExpansionProfile = expParameters; + } + + private List allStructures() { + + List retVal = myAllStructures; + if (retVal == null) { + retVal = new ArrayList<>(); + for (IBaseResource next : + myValidationSupportContext.getRootValidationSupport().fetchAllStructureDefinitions()) { + try { + StructureDefinition converted = myVersionCanonicalizer.structureDefinitionToCanonical(next); + retVal.add(converted); + } catch (FHIRException e) { + throw new InternalErrorException(Msg.code(659) + e); + } + } + myAllStructures = retVal; + } + + return retVal; + } + + @Override + public void cacheResource(Resource res) { + } + + @Override + public void cacheResourceFromPackage(Resource res, PackageInformation packageDetails) throws FHIRException { + } + + @Override + public void cachePackage(PackageInformation packageInformation) { + } + + @Nonnull + private ValidationResult convertValidationResult( + String theSystem, @Nullable IValidationSupport.CodeValidationResult theResult) { + ValidationResult retVal = null; + if (theResult != null) { + String code = theResult.getCode(); + String display = theResult.getDisplay(); + + String issueSeverityCode = theResult.getSeverityCode(); + String message = theResult.getMessage(); + ValidationMessage.IssueSeverity issueSeverity = null; + if (issueSeverityCode != null) { + issueSeverity = ValidationMessage.IssueSeverity.fromCode(issueSeverityCode); + } else if (isNotBlank(message)) { + issueSeverity = ValidationMessage.IssueSeverity.INFORMATION; + } + + CodeSystem.ConceptDefinitionComponent conceptDefinitionComponent = null; + if (code != null) { + conceptDefinitionComponent = new CodeSystem.ConceptDefinitionComponent() + .setCode(code) + .setDisplay(display); + } + + retVal = new ValidationResult( + issueSeverity, + message, + theSystem, + theResult.getCodeSystemVersion(), + conceptDefinitionComponent, + display, + null); + } + + if (retVal == null) { + retVal = new ValidationResult(ValidationMessage.IssueSeverity.ERROR, "Validation failed", null); + } + + return retVal; + } + + @Override + public ValueSetExpansionOutcome expandVS( + org.hl7.fhir.r5.model.ValueSet source, boolean cacheOk, boolean Hierarchical) { + IBaseResource convertedSource; + try { + convertedSource = myVersionCanonicalizer.valueSetFromValidatorCanonical(source); + } catch (FHIRException e) { + throw new InternalErrorException(Msg.code(661) + e); + } + IValidationSupport.ValueSetExpansionOutcome expanded = myValidationSupportContext + .getRootValidationSupport() + .expandValueSet(myValidationSupportContext, null, convertedSource); + + org.hl7.fhir.r5.model.ValueSet convertedResult = null; + if (expanded.getValueSet() != null) { + try { + convertedResult = myVersionCanonicalizer.valueSetToValidatorCanonical(expanded.getValueSet()); + } catch (FHIRException e) { + throw new InternalErrorException(Msg.code(662) + e); + } + } + + String error = expanded.getError(); + TerminologyServiceErrorClass result = null; + + return new ValueSetExpansionOutcome(convertedResult, error, result); + } + + @Override + public ValueSetExpansionOutcome expandVS( + Resource src, + org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent binding, + boolean cacheOk, + boolean Hierarchical) { + throw new UnsupportedOperationException(Msg.code(663)); + } + + @Override + public ValueSetExpansionOutcome expandVS(ValueSet.ConceptSetComponent inc, boolean hierarchical, boolean noInactive) + throws TerminologyServiceException { + throw new UnsupportedOperationException(Msg.code(664)); + } + + @Override + public Locale getLocale() { + return myValidationSupportContext + .getRootValidationSupport() + .getFhirContext() + .getLocalizer() + .getLocale(); + } + + @Override + public void setLocale(Locale locale) { + // ignore + } + + @Override + public org.hl7.fhir.r5.model.CodeSystem fetchCodeSystem(String system) { + IBaseResource fetched = + myValidationSupportContext.getRootValidationSupport().fetchCodeSystem(system); + if (fetched == null) { + return null; + } + try { + return (org.hl7.fhir.r5.model.CodeSystem) myVersionCanonicalizer.codeSystemToValidatorCanonical(fetched); + } catch (FHIRException e) { + throw new InternalErrorException(Msg.code(665) + e); + } + } + + @Override + public CodeSystem fetchCodeSystem(String system, String verison) { + IBaseResource fetched = + myValidationSupportContext.getRootValidationSupport().fetchCodeSystem(system); + if (fetched == null) { + return null; + } + try { + return (org.hl7.fhir.r5.model.CodeSystem) myVersionCanonicalizer.codeSystemToValidatorCanonical(fetched); + } catch (FHIRException e) { + throw new InternalErrorException(Msg.code(1992) + e); + } + } + + @Override + public CodeSystem fetchSupplementedCodeSystem(String system) { + return null; + } + + @Override + public CodeSystem fetchSupplementedCodeSystem(String system, String version) { + return null; + } + + @Override + public T fetchResourceRaw(Class class_, String uri) { + return fetchResource(class_, uri); + } + + @Override + public T fetchResource(Class class_, String uri) { + + if (isBlank(uri)) { + return null; + } + + ResourceKey key = new ResourceKey(class_.getSimpleName(), uri); + @SuppressWarnings("unchecked") + T retVal = (T) myFetchResourceCache.get(key); + + return retVal; + } + + @Override + public Resource fetchResourceById(String type, String uri) { + throw new UnsupportedOperationException(Msg.code(666)); + } + + @Override + public T fetchResourceWithException(Class class_, String uri) throws FHIRException { + T retVal = fetchResource(class_, uri); + if (retVal == null) { + throw new FHIRException( + Msg.code(667) + "Can not find resource of type " + class_.getSimpleName() + " with uri " + uri); + } + return retVal; + } + + @Override + public T fetchResource(Class class_, String uri, String version) { + return fetchResource(class_, uri + "|" + version); + } + + @Override + public T fetchResource(Class class_, String uri, Resource canonicalForSource) { + return fetchResource(class_, uri); + } + + @Override + public T fetchResourceWithException(Class class_, String uri, Resource sourceOfReference) + throws FHIRException { + throw new UnsupportedOperationException(Msg.code(2214)); + } + + @Override + public List getResourceNames() { + return new ArrayList<>(myValidationSupportContext + .getRootValidationSupport() + .getFhirContext() + .getResourceTypes()); + } + + @Override + public Set getResourceNamesAsSet() { + return myValidationSupportContext + .getRootValidationSupport() + .getFhirContext() + .getResourceTypes(); + } + + @Override + public StructureDefinition fetchTypeDefinition(String typeName) { + return fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + typeName); + } + + @Override + public List fetchTypeDefinitions(String typeName) { + List allStructures = new ArrayList<>(allStructures()); + allStructures.removeIf(sd -> !sd.hasType() || !sd.getType().equals(typeName)); + return allStructures; + } + + @Override + public UcumService getUcumService() { + throw new UnsupportedOperationException(Msg.code(676)); + } + + @Override + public void setUcumService(UcumService ucumService) { + throw new UnsupportedOperationException(Msg.code(677)); + } + + @Override + public String getVersion() { + return myValidationSupportContext + .getRootValidationSupport() + .getFhirContext() + .getVersion() + .getVersion() + .getFhirVersionString(); + } + + @Override + public boolean hasResource(Class class_, String uri) { + throw new UnsupportedOperationException(Msg.code(680)); + } + + @Override + public boolean isNoTerminologyServer() { + return false; + } + + @Override + public Set getCodeSystemsUsed() { + throw new UnsupportedOperationException(Msg.code(681)); + } + + @Override + public IResourceValidator newValidator() { + throw new UnsupportedOperationException(Msg.code(684)); + } + + @Override + public Map getNSUrlMap() { + throw new UnsupportedOperationException(Msg.code(2265)); + } + + @Override + public ILoggingService getLogger() { + return null; + } + + @Override + public void setLogger(ILoggingService logger) { + throw new UnsupportedOperationException(Msg.code(687)); + } + + @Override + public boolean supportsSystem(String system) { + return myValidationSupportContext + .getRootValidationSupport() + .isCodeSystemSupported(myValidationSupportContext, system); + } + + @Override + public TranslationServices translator() { + throw new UnsupportedOperationException(Msg.code(688)); + } + + @Override + public ValueSetExpansionOutcome expandVS( + ValueSet source, boolean cacheOk, boolean heiarchical, boolean incompleteOk) { + return null; + } + + @Override + public ValidationResult validateCode( + ValidationOptions theOptions, String system, String version, String code, String display) { + ConceptValidationOptions validationOptions = convertConceptValidationOptions(theOptions); + return doValidation(null, validationOptions, system, code, display); + } + + @Override + public ValidationResult validateCode( + ValidationOptions theOptions, + String theSystem, + String version, + String theCode, + String display, + ValueSet theValueSet) { + IBaseResource convertedVs = null; + + try { + if (theValueSet != null) { + convertedVs = myVersionCanonicalizer.valueSetFromValidatorCanonical(theValueSet); + } + } catch (FHIRException e) { + throw new InternalErrorException(Msg.code(689) + e); + } + + ConceptValidationOptions validationOptions = convertConceptValidationOptions(theOptions); + + return doValidation(convertedVs, validationOptions, theSystem, theCode, display); + } + + @Override + public ValidationResult validateCode( + ValidationOptions theOptions, String code, org.hl7.fhir.r5.model.ValueSet theValueSet) { + IBaseResource convertedVs = null; + try { + if (theValueSet != null) { + convertedVs = myVersionCanonicalizer.valueSetFromValidatorCanonical(theValueSet); + } + } catch (FHIRException e) { + throw new InternalErrorException(Msg.code(690) + e); + } + + ConceptValidationOptions validationOptions = + convertConceptValidationOptions(theOptions).setInferSystem(true); + + return doValidation(convertedVs, validationOptions, null, code, null); + } + + @Override + public ValidationResult validateCode( + ValidationOptions theOptions, + org.hl7.fhir.r5.model.Coding theCoding, + org.hl7.fhir.r5.model.ValueSet theValueSet) { + IBaseResource convertedVs = null; + + try { + if (theValueSet != null) { + convertedVs = myVersionCanonicalizer.valueSetFromValidatorCanonical(theValueSet); + } + } catch (FHIRException e) { + throw new InternalErrorException(Msg.code(691) + e); + } + + ConceptValidationOptions validationOptions = convertConceptValidationOptions(theOptions); + String system = theCoding.getSystem(); + String code = theCoding.getCode(); + String display = theCoding.getDisplay(); + + return doValidation(convertedVs, validationOptions, system, code, display); + } + + @Override + public ValidationResult validateCode( + ValidationOptions options, Coding code, ValueSet vs, ValidationContextCarrier ctxt) { + return validateCode(options, code, vs); + } + + @Override + public void validateCodeBatch( + ValidationOptions options, List codes, ValueSet vs) { + for (CodingValidationRequest next : codes) { + ValidationResult outcome = validateCode(options, next.getCoding(), vs); + next.setResult(outcome); + } + } + + @Override + public void validateCodeBatchByRef(ValidationOptions options, List codes, String vsUrl) { + + ConceptValidationOptions validationOptions = + convertConceptValidationOptions(options); + + for (CodingValidationRequest next : codes) { + IValidationSupport.CodeValidationResult result = myValidationSupportContext + .getRootValidationSupport() + .validateCode( + myValidationSupportContext, + validationOptions, + next.getCoding().getSystem(), + next.getCoding().getCode(), + next.getCoding().getDisplay(), + vsUrl); + ValidationResult outcome = convertValidationResult(next.getCoding().getSystem(), result); + next.setResult(outcome); + } + } + + @Nonnull + private ValidationResult doValidation( + IBaseResource theValueSet, + ConceptValidationOptions theValidationOptions, + String theSystem, + String theCode, + String theDisplay) { + IValidationSupport.CodeValidationResult result; + if (theValueSet != null) { + result = myValidationSupportContext + .getRootValidationSupport() + .validateCodeInValueSet( + myValidationSupportContext, + theValidationOptions, + theSystem, + theCode, + theDisplay, + theValueSet); + } else { + result = myValidationSupportContext + .getRootValidationSupport() + .validateCode( + myValidationSupportContext, theValidationOptions, theSystem, theCode, theDisplay, null); + } + return convertValidationResult(theSystem, result); + } + + @Override + public ValidationResult validateCode( + ValidationOptions theOptions, + org.hl7.fhir.r5.model.CodeableConcept code, + org.hl7.fhir.r5.model.ValueSet theVs) { + List validationResultsOk = new ArrayList<>(); + for (Coding next : code.getCoding()) { + ValidationResult retVal = validateCode(theOptions, next, theVs); + if (retVal.isOk()) { + if (myValidationSupportContext.isEnabledValidationForCodingsLogicalAnd()) { + validationResultsOk.add(retVal); + } else { + return retVal; + } + } + } + + if (code.getCoding().size() > 0 + && validationResultsOk.size() == code.getCoding().size()) { + return validationResultsOk.get(0); + } + + return new ValidationResult(ValidationMessage.IssueSeverity.ERROR, null, null); + } + + public void invalidateCaches() { + myFetchResourceCache.invalidateAll(); + } + + @Override + public List fetchResourcesByType(Class theClass) { + if (theClass.equals(StructureDefinition.class)) { + return (List) allStructures(); + } + throw new UnsupportedOperationException(Msg.code(650) + "Unable to fetch resources of type: " + theClass); + } + + @Override + public boolean isForPublication() { + return false; + } + + @Override + public void setForPublication(boolean b) { + throw new UnsupportedOperationException(Msg.code(2351)); + } + + public static ConceptValidationOptions convertConceptValidationOptions(ValidationOptions theOptions) { + ConceptValidationOptions retVal = new ConceptValidationOptions(); + if (theOptions.isGuessSystem()) { + retVal = retVal.setInferSystem(true); + } + return retVal; + } + + @Nonnull + public static VersionSpecificWorkerContextWrapper newVersionSpecificWorkerContextWrapper( + IValidationSupport theValidationSupport) { + VersionCanonicalizer versionCanonicalizer = new VersionCanonicalizer(theValidationSupport.getFhirContext()); + return new VersionSpecificWorkerContextWrapper( + new ValidationSupportContext(theValidationSupport), versionCanonicalizer); + } + + private static class ResourceKey { + private final int myHashCode; + private final String myResourceName; + private final String myUri; + + private ResourceKey(String theResourceName, String theUri) { + myResourceName = theResourceName; + myUri = theUri; + myHashCode = new HashCodeBuilder(17, 37) + .append(myResourceName) + .append(myUri) + .toHashCode(); + } + + @Override + public boolean equals(Object theO) { + if (this == theO) { + return true; + } + + if (theO == null || getClass() != theO.getClass()) { + return false; + } + + ResourceKey that = (ResourceKey) theO; + + return new EqualsBuilder() + .append(myResourceName, that.myResourceName) + .append(myUri, that.myUri) + .isEquals(); + } + + public String getResourceName() { + return myResourceName; + } + + public String getUri() { + return myUri; + } + + @Override + public int hashCode() { + return myHashCode; + } + } } From 67d55c62297f556105496250938787835eb25dc8 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Sat, 16 Sep 2023 13:48:45 -0400 Subject: [PATCH 02/10] Build cleanup --- .../extractor/SearchParamExtractorR4.java | 5 ++--- .../extractor/SearchParamExtractorR4B.java | 5 ++--- .../extractor/SearchParamExtractorR5.java | 5 ++--- .../validator/FhirInstanceValidator.java | 3 +-- .../VersionSpecificWorkerContextWrapper.java | 20 ------------------- 5 files changed, 7 insertions(+), 31 deletions(-) diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java index 0e7f6869554a..486c7d157d96 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java @@ -39,14 +39,13 @@ import org.hl7.fhir.r4.model.TypeDetails; import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.utils.FHIRPathEngine; -import org.hl7.fhir.r4.utils.FHIRPathUtilityClasses; +import javax.annotation.PostConstruct; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; -import javax.annotation.PostConstruct; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -121,7 +120,7 @@ public boolean log(String argument, List focus) { } @Override - public FHIRPathUtilityClasses.FunctionDetails resolveFunction(String functionName) { + public FunctionDetails resolveFunction(String functionName) { return null; } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4B.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4B.java index 08a54dd975f1..2b34730dc48e 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4B.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4B.java @@ -39,14 +39,13 @@ import org.hl7.fhir.r4b.model.TypeDetails; import org.hl7.fhir.r4b.model.ValueSet; import org.hl7.fhir.r4b.utils.FHIRPathEngine; -import org.hl7.fhir.r4b.utils.FHIRPathUtilityClasses; +import javax.annotation.PostConstruct; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; -import javax.annotation.PostConstruct; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -121,7 +120,7 @@ public boolean log(String argument, List focus) { } @Override - public FHIRPathUtilityClasses.FunctionDetails resolveFunction(String functionName) { + public FunctionDetails resolveFunction(String functionName) { return null; } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java index 4bce79a31fcf..18acbd862721 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java @@ -38,14 +38,13 @@ import org.hl7.fhir.r5.model.TypeDetails; import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.utils.FHIRPathEngine; -import org.hl7.fhir.r5.utils.FHIRPathUtilityClasses; +import javax.annotation.PostConstruct; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; -import javax.annotation.PostConstruct; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -118,7 +117,7 @@ public boolean log(String argument, List focus) { } @Override - public FHIRPathUtilityClasses.FunctionDetails resolveFunction(String functionName) { + public FunctionDetails resolveFunction(String functionName) { return null; } diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirInstanceValidator.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirInstanceValidator.java index bf74454d42ca..b1e76206df0d 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirInstanceValidator.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirInstanceValidator.java @@ -13,7 +13,6 @@ import org.hl7.fhir.r5.model.TypeDetails; import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.utils.FHIRPathEngine; -import org.hl7.fhir.r5.utils.FHIRPathUtilityClasses; import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher; import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel; @@ -304,7 +303,7 @@ public boolean log(String argument, List focus) { } @Override - public FHIRPathUtilityClasses.FunctionDetails resolveFunction(String functionName) { + public FunctionDetails resolveFunction(String functionName) { return null; } diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java index bac8423c7fd9..ec5303637ee0 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java @@ -623,26 +623,6 @@ public void validateCodeBatch( } } - @Override - public void validateCodeBatchByRef(ValidationOptions options, List codes, String vsUrl) { - - ConceptValidationOptions validationOptions = - convertConceptValidationOptions(options); - - for (CodingValidationRequest next : codes) { - IValidationSupport.CodeValidationResult result = myValidationSupportContext - .getRootValidationSupport() - .validateCode( - myValidationSupportContext, - validationOptions, - next.getCoding().getSystem(), - next.getCoding().getCode(), - next.getCoding().getDisplay(), - vsUrl); - ValidationResult outcome = convertValidationResult(next.getCoding().getSystem(), result); - next.setResult(outcome); - } - } @Nonnull private ValidationResult doValidation( From cd60b1db7fec554201fc173eac993d3a4087dad9 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Sat, 16 Sep 2023 13:56:53 -0400 Subject: [PATCH 03/10] Initial test passing --- .../context/support/IValidationSupport.java | 18 ++++++ .../ca/uhn/fhir/i18n/hapi-messages.properties | 7 ++- .../ca/uhn/fhir/jpa/term/TermReadSvcImpl.java | 27 +++----- .../dao/r4/FhirResourceDaoR4ValidateTest.java | 4 +- ...oryTerminologyServerValidationSupport.java | 61 +++++++++++++++---- 5 files changed, 83 insertions(+), 34 deletions(-) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java index 6ae1939f6115..b08d10cadc5b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java @@ -553,11 +553,29 @@ class CodeValidationResult { private String myCodeSystemVersion; private List myProperties; private String myDisplay; + private String mySourceDetails; public CodeValidationResult() { super(); } + /** + * This field may contain information about what the source of the + * validation information was. + */ + public String getSourceDetails() { + return mySourceDetails; + } + + /** + * This field may contain information about what the source of the + * validation information was. + */ + public CodeValidationResult setSourceDetails(String theSourceDetails) { + mySourceDetails = theSourceDetails; + return this; + } + public String getDisplay() { return myDisplay; } diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties index 17632574a82d..f0375733e443 100644 --- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties +++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties @@ -1,4 +1,7 @@ +org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport.displayMismatch=Concept Display "{0}" does not match expected "{1}" + + ca.uhn.fhir.jpa.term.TermReadSvcImpl.expansionRefersToUnknownCs=Unknown CodeSystem URI "{0}" referenced from ValueSet ca.uhn.fhir.jpa.term.TermReadSvcImpl.valueSetNotYetExpanded=ValueSet "{0}" has not yet been pre-expanded. Performing in-memory expansion without parameters. Current status: {1} | {2} ca.uhn.fhir.jpa.term.TermReadSvcImpl.valueSetNotYetExpanded_OffsetNotAllowed=ValueSet expansion can not combine "offset" with "ValueSet.compose.exclude" unless the ValueSet has been pre-expanded. ValueSet "{0}" must be pre-expanded for this operation to work. @@ -6,8 +9,8 @@ ca.uhn.fhir.jpa.term.TermReadSvcImpl.valueSetExpandedUsingPreExpansion=ValueSet ca.uhn.fhir.jpa.term.TermReadSvcImpl.valueSetExpandedUsingInMemoryExpansion=ValueSet with URL "{0}" was expanded using an in-memory expansion ca.uhn.fhir.jpa.term.TermReadSvcImpl.validationPerformedAgainstPreExpansion=Code validation occurred using a ValueSet expansion that was pre-calculated at {0} ca.uhn.fhir.jpa.term.TermReadSvcImpl.valueSetNotFoundInTerminologyDatabase=ValueSet can not be found in terminology database: {0} -ca.uhn.fhir.jpa.term.TermReadSvcImpl.valueSetPreExpansionInvalidated=ValueSet with URL "{0}" precaluclated expansion with {1} concept(s) has been invalidated -ca.uhn.fhir.jpa.term.TermReadSvcImpl.valueSetCantInvalidateNotYetPrecalculated=ValueSet with URL "{0}" already has status: {1} +ca.uhn.fhir.jpa.term.TermReadSvcImpl.valueSetPreExpansionInvalidated=ValueSet with URL "{0}" precaluclated expansion with {1} concept(s) has been invalidated +ca.uhn.fhir.jpa.term.TermReadSvcImpl.valueSetCantInvalidateNotYetPrecalculated=ValueSet with URL "{0}" already has status: {1} ca.uhn.fhir.jpa.term.TermReadSvcImpl.unknownCodeInSystem=Unknown code "{0}#{1}" diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java index 3ace1fbb0727..e984fa2fa57d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java @@ -2055,7 +2055,7 @@ protected IValidationSupport.CodeValidationResult validateCodeIsInPreExpandedVal .findByResourcePid(valueSetResourcePid.getId()) .orElseThrow(IllegalStateException::new); String timingDescription = toHumanReadableExpansionTimestamp(valueSetEntity); - String msg = myContext + String preExpansionMessage = myContext .getLocalizer() .getMessage(TermReadSvcImpl.class, "validationPerformedAgainstPreExpansion", timingDescription); @@ -2068,14 +2068,13 @@ protected IValidationSupport.CodeValidationResult validateCodeIsInPreExpandedVal .setCode(concept.getCode()) .setDisplay(concept.getDisplay()) .setCodeSystemVersion(concept.getSystemVersion()) - .setMessage(msg); + .setSourceDetails(preExpansionMessage); } } String expectedDisplay = concepts.get(0).getDisplay(); - String append = createMessageAppendForDisplayMismatch(theSystem, theDisplay, expectedDisplay) + " - " + msg; - return createFailureCodeValidationResult(theSystem, theCode, systemVersion, append) - .setDisplay(expectedDisplay); + return InMemoryTerminologyServerValidationSupport.createWarningForDisplayMismatch( + myContext, theCode, theDisplay, expectedDisplay, systemVersion); } if (!concepts.isEmpty()) { @@ -2083,7 +2082,7 @@ protected IValidationSupport.CodeValidationResult validateCodeIsInPreExpandedVal .setCode(concepts.get(0).getCode()) .setDisplay(concepts.get(0).getDisplay()) .setCodeSystemVersion(concepts.get(0).getSystemVersion()) - .setMessage(msg); + .setMessage(preExpansionMessage); } // Ok, we failed @@ -2096,7 +2095,7 @@ protected IValidationSupport.CodeValidationResult validateCodeIsInPreExpandedVal String unknownCodeMessage = myContext .getLocalizer() .getMessage(TermReadSvcImpl.class, "unknownCodeInSystem", theSystem, theCode); - append = " - " + unknownCodeMessage + ". " + msg; + append = " - " + unknownCodeMessage + ". " + preExpansionMessage; } return createFailureCodeValidationResult(theSystem, theCode, null, append); @@ -2710,11 +2709,8 @@ public IValidationSupport.CodeValidationResult validateCode( || code.getDisplay().equals(theDisplay)) { return new CodeValidationResult().setCode(code.getCode()).setDisplay(code.getDisplay()); } else { - String messageAppend = - createMessageAppendForDisplayMismatch(theCodeSystemUrl, theDisplay, code.getDisplay()); - return createFailureCodeValidationResult( - theCodeSystemUrl, theCode, code.getSystemVersion(), messageAppend) - .setDisplay(code.getDisplay()); + return InMemoryTerminologyServerValidationSupport.createWarningForDisplayMismatch( + myContext, theCode, theDisplay, code.getDisplay(), code.getSystemVersion()); } } @@ -3182,13 +3178,6 @@ static boolean isValueSetDisplayLanguageMatch(ValueSetExpansionOptions theExpans return theExpansionOptions.getTheDisplayLanguage().equalsIgnoreCase(theStoredLang); } - @Nonnull - private static String createMessageAppendForDisplayMismatch( - String theCodeSystemUrl, String theDisplay, String theExpectedDisplay) { - return " - Concept Display \"" + theDisplay + "\" does not match expected \"" + theExpectedDisplay - + "\" for CodeSystem: " + theCodeSystemUrl; - } - @Nonnull private static String createMessageAppendForCodeNotFoundInCodeSystem(String theCodeSystemUrl) { return " - Code is not found in CodeSystem: " + theCodeSystemUrl; diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java index 1f5a4ca2dbf5..3d8d46840802 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java @@ -1396,8 +1396,8 @@ public void testValidateUsingExternallyDefinedCodeMisMatchDisplay_ShouldError() containsString("None of the codings provided are in the value set 'IdentifierType'")); assertThat(OperationOutcomeUtil.getFirstIssueDetails(myFhirContext, oo), containsString("a coding should come from this value set unless it has no suitable code (note that the validator cannot judge what is suitable) (codes = http://foo#bar)")); - assertEquals(OperationOutcome.IssueSeverity.ERROR, oo.getIssue().get(1).getSeverity()); - assertThat(oo.getIssue().get(1).getDiagnostics(), containsString("Unable to validate code http://foo#bar - Concept Display ")); + assertEquals(OperationOutcome.IssueSeverity.WARNING, oo.getIssue().get(1).getSeverity()); + assertEquals("Concept Display \"not bar code\" does not match expected \"Bar Code\" for 'http://foo#bar'", oo.getIssue().get(1).getDiagnostics()); } private OperationOutcome doTestValidateResourceContainingProfileDeclaration(String methodName, EncodingEnum enc) throws IOException { 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 f44af8e37029..35346f8725bf 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 @@ -517,22 +517,24 @@ private CodeValidationResult validateCodeInExpandedValueSet( .setCodeSystemName(codeSystemResourceName) .setCodeSystemVersion(csVersion); if (isNotBlank(theValueSetUrl)) { - codeValidationResult.setMessage( - "Code was validated against in-memory expansion of ValueSet: " + theValueSetUrl); + populateSourceDetailsForInMemoryExpansion(theValueSetUrl, codeValidationResult); } return codeValidationResult; } else { - String message = "Concept Display \"" + theDisplayToValidate + "\" does not match expected \"" - + nextExpansionCode.getDisplay() + "\""; + String messageAppend = ""; if (isNotBlank(theValueSetUrl)) { - message += " for in-memory expansion of ValueSet: " + theValueSetUrl; + messageAppend = " for in-memory expansion of ValueSet: " + theValueSetUrl; } - return new CodeValidationResult() - .setSeverity(IssueSeverity.ERROR) - .setDisplay(nextExpansionCode.getDisplay()) - .setMessage(message) - .setCodeSystemName(codeSystemResourceName) - .setCodeSystemVersion(csVersion); + CodeValidationResult codeValidationResult = createWarningForDisplayMismatch( + myCtx, + theCodeToValidate, theDisplayToValidate, + nextExpansionCode.getDisplay(), + codeSystemResourceVersion, + messageAppend); + if (isNotBlank(theValueSetUrl)) { + populateSourceDetailsForInMemoryExpansion(theValueSetUrl, codeValidationResult); + } + return codeValidationResult; } } } @@ -562,6 +564,12 @@ private CodeValidationResult validateCodeInExpandedValueSet( return new CodeValidationResult().setSeverityCode(severity.toCode()).setMessage(message); } + private static void populateSourceDetailsForInMemoryExpansion( + String theValueSetUrl, CodeValidationResult codeValidationResult) { + codeValidationResult.setSourceDetails( + "Code was validated against in-memory expansion of ValueSet: " + theValueSetUrl); + } + @Override public LookupCodeResult lookupCode( ValidationSupportContext theValidationSupportContext, @@ -1194,6 +1202,37 @@ private String optionallyPopulateVersionFromUrl(String theSystemUrl, String theV return theVersion; } + public static CodeValidationResult createWarningForDisplayMismatch( + FhirContext theFhirContext, + String theCode, String theDisplay, + String theExpectedDisplay, + String theCodeSystemVersion) { + return createWarningForDisplayMismatch(theFhirContext, + theCode, theDisplay, theExpectedDisplay, theCodeSystemVersion, ""); + } + + private static CodeValidationResult createWarningForDisplayMismatch( + FhirContext theFhirContext, + String theCode, String theDisplay, + String theExpectedDisplay, + String theCodeSystemVersion, + String theMessageAppend) { + + String message = theFhirContext + .getLocalizer() + .getMessage(InMemoryTerminologyServerValidationSupport.class, + "displayMismatch", + theDisplay, + theExpectedDisplay) + + theMessageAppend; + return new CodeValidationResult() + .setSeverity(IssueSeverity.WARNING) + .setMessage(message) + .setCode(theCode) + .setCodeSystemVersion(theCodeSystemVersion) + .setDisplay(theExpectedDisplay); + } + public enum FailureType { UNKNOWN_CODE_SYSTEM, OTHER From 9a965532ef23f6f33111388301c11172acf6d1f5 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Sat, 16 Sep 2023 14:22:46 -0400 Subject: [PATCH 04/10] Test fixes --- .../dao/r4/FhirResourceDaoR4ValidateTest.java | 79 +- .../uhn/fhir/rest/server/mail/MailSvcIT.java | 3 +- ...oryTerminologyServerValidationSupport.java | 26 +- .../VersionSpecificWorkerContextWrapper.java | 1408 ++++++++--------- ...erminologyServerValidationSupportTest.java | 22 +- 5 files changed, 812 insertions(+), 726 deletions(-) diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java index 3d8d46840802..7258d728f83d 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java @@ -49,6 +49,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.util.AopTestUtils; @@ -1364,7 +1366,7 @@ public void testValidateUsingExternallyDefinedCode() { } @Test - public void testValidateUsingExternallyDefinedCodeMisMatchDisplay_ShouldError() { + public void testValidateUsingExternallyDefinedCodeMisMatchDisplay_InMemory_ShouldLogWarning() { CodeSystem codeSystem = new CodeSystem(); codeSystem.setUrl("http://foo"); codeSystem.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT); @@ -1994,6 +1996,81 @@ public void testValidateUsingDifferentialProfile() throws IOException { assertThat(encoded, containsString("No issues detected")); } + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testValidateWrongDisplayOnRequiredBinding(boolean thePreCalculateExpansion) { + StructureDefinition sd = new StructureDefinition(); + sd.setUrl("http://profile"); + sd.setStatus(Enumerations.PublicationStatus.ACTIVE); + sd.setType("Observation"); + sd.setAbstract(false); + sd.setDerivation(StructureDefinition.TypeDerivationRule.CONSTRAINT); + sd.setBaseDefinition("http://hl7.org/fhir/StructureDefinition/Observation"); + ElementDefinition codeElement = sd.getDifferential().addElement(); + codeElement.setId("Observation.code"); + codeElement.setPath("Observation.code"); + codeElement.addType().setCode("CodeableConcept"); + codeElement.getBinding().setStrength(Enumerations.BindingStrength.REQUIRED); + codeElement.getBinding().setValueSet("http://vs"); + myStructureDefinitionDao.create(sd, new SystemRequestDetails()); + + CodeSystem cs = new CodeSystem(); + cs.setUrl("http://cs"); + cs.setStatus(Enumerations.PublicationStatus.ACTIVE); + cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE); + cs.addConcept() + .setCode("8302-2") + .setDisplay("Body Height"); + myCodeSystemDao.create(cs, new SystemRequestDetails()); + + ValueSet vs = new ValueSet(); + vs.setUrl("http://vs"); + vs.setStatus(Enumerations.PublicationStatus.ACTIVE); + vs.getCompose().addInclude().setSystem("http://cs"); + myValueSetDao.create(vs, new SystemRequestDetails()); + + if (thePreCalculateExpansion) { + myTermReadSvc.preExpandDeferredValueSetsToTerminologyTables(); + } + + Observation obs = new Observation(); + obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED); + obs.getText().setDivAsString("
hello
"); + obs.getMeta().addProfile("http://profile"); + obs.setStatus(Observation.ObservationStatus.FINAL); + obs.getCode().addCoding() + .setSystem("http://cs") + .setCode("8302-2") + .setDisplay("Body height2"); + obs.setEffective(DateTimeType.now()); + obs.addPerformer(new Reference("Practitioner/123")); + obs.setSubject(new Reference("Patient/123")); + obs.setValue(new Quantity(null, 123, "http://unitsofmeasure.org", "[in_i]", "in")); + + String encoded = myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs); + MethodOutcome outcome = myObservationDao.validate(obs, null, encoded, EncodingEnum.JSON, ValidationModeEnum.CREATE, null, new SystemRequestDetails()); + + OperationOutcome oo = (OperationOutcome) outcome.getOperationOutcome(); + ourLog.info("Outcome: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo).replace("\"resourceType\"", "\"resType\"")); + + assertEquals(1, oo.getIssue().size()); + assertThat(oo.getIssue().get(0).getDiagnostics(), + containsString("Concept Display \"Body height2\" does not match expected \"Body Height\"")); + if (!thePreCalculateExpansion) { + assertThat(oo.getIssue().get(0).getDiagnostics(), + containsString("for in-memory expansion of ValueSet: http://vs")); + } + assertEquals(11, ((IntegerType)oo.getIssue().get(0).getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-line").getValue()).getValue()); + assertEquals(4, ((IntegerType)oo.getIssue().get(0).getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-col").getValue()).getValue()); + assertEquals(0, oo.getIssue().get(0).getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-message-id").size()); + assertEquals(OperationOutcome.IssueType.PROCESSING, oo.getIssue().get(0).getCode()); + assertEquals(OperationOutcome.IssueSeverity.WARNING, oo.getIssue().get(0).getSeverity()); + assertEquals(2, oo.getIssue().get(0).getLocation().size()); + assertEquals("Observation.code", oo.getIssue().get(0).getLocation().get(0).getValue()); + assertEquals("Line[11] Col[4]", oo.getIssue().get(0).getLocation().get(1).getValue()); + + } + /** * See #1780 */ diff --git a/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/mail/MailSvcIT.java b/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/mail/MailSvcIT.java index c62c965f2505..87f039401cbc 100644 --- a/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/mail/MailSvcIT.java +++ b/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/mail/MailSvcIT.java @@ -42,7 +42,8 @@ public void testSendSingleMail() throws Exception { // execute fixture.sendMail(email); // validate - assertTrue(ourGreenMail.waitForIncomingEmail(5000, 1)); + boolean condition = ourGreenMail.waitForIncomingEmail(5000, 1); + assertTrue(condition); final MimeMessage[] receivedMessages = ourGreenMail.getReceivedMessages(); assertEquals(1, receivedMessages.length); assertEquals(SUBJECT, receivedMessages[0].getSubject()); 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 35346f8725bf..0e72a2c70fe3 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 @@ -527,7 +527,8 @@ private CodeValidationResult validateCodeInExpandedValueSet( } CodeValidationResult codeValidationResult = createWarningForDisplayMismatch( myCtx, - theCodeToValidate, theDisplayToValidate, + theCodeToValidate, + theDisplayToValidate, nextExpansionCode.getDisplay(), codeSystemResourceVersion, messageAppend); @@ -1204,27 +1205,30 @@ private String optionallyPopulateVersionFromUrl(String theSystemUrl, String theV public static CodeValidationResult createWarningForDisplayMismatch( FhirContext theFhirContext, - String theCode, String theDisplay, + String theCode, + String theDisplay, String theExpectedDisplay, String theCodeSystemVersion) { - return createWarningForDisplayMismatch(theFhirContext, - theCode, theDisplay, theExpectedDisplay, theCodeSystemVersion, ""); + return createWarningForDisplayMismatch( + theFhirContext, theCode, theDisplay, theExpectedDisplay, theCodeSystemVersion, ""); } private static CodeValidationResult createWarningForDisplayMismatch( FhirContext theFhirContext, - String theCode, String theDisplay, + String theCode, + String theDisplay, String theExpectedDisplay, String theCodeSystemVersion, String theMessageAppend) { String message = theFhirContext - .getLocalizer() - .getMessage(InMemoryTerminologyServerValidationSupport.class, - "displayMismatch", - theDisplay, - theExpectedDisplay) + - theMessageAppend; + .getLocalizer() + .getMessage( + InMemoryTerminologyServerValidationSupport.class, + "displayMismatch", + theDisplay, + theExpectedDisplay) + + theMessageAppend; return new CodeValidationResult() .setSeverity(IssueSeverity.WARNING) .setMessage(message) diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java index ec5303637ee0..2fdd8b91eb03 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java @@ -41,8 +41,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; @@ -50,713 +48,711 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWorkerContext { - private static final Logger ourLog = LoggerFactory.getLogger(VersionSpecificWorkerContextWrapper.class); - private final ValidationSupportContext myValidationSupportContext; - private final VersionCanonicalizer myVersionCanonicalizer; - private final LoadingCache myFetchResourceCache; - private volatile List myAllStructures; - private org.hl7.fhir.r5.model.Parameters myExpansionProfile; - - public VersionSpecificWorkerContextWrapper( - ValidationSupportContext theValidationSupportContext, VersionCanonicalizer theVersionCanonicalizer) { - myValidationSupportContext = theValidationSupportContext; - myVersionCanonicalizer = theVersionCanonicalizer; - - long timeoutMillis = HapiSystemProperties.getTestValidationResourceCachesMs(); - - myFetchResourceCache = CacheFactory.build(timeoutMillis, 10000, key -> { - String fetchResourceName = key.getResourceName(); - if (myValidationSupportContext - .getRootValidationSupport() - .getFhirContext() - .getVersion() - .getVersion() - == FhirVersionEnum.DSTU2) { - if ("CodeSystem".equals(fetchResourceName)) { - fetchResourceName = "ValueSet"; - } - } - - Class fetchResourceType; - if (fetchResourceName.equals("Resource")) { - fetchResourceType = null; - } else { - fetchResourceType = myValidationSupportContext - .getRootValidationSupport() - .getFhirContext() - .getResourceDefinition(fetchResourceName) - .getImplementingClass(); - } - - IBaseResource fetched = myValidationSupportContext - .getRootValidationSupport() - .fetchResource(fetchResourceType, key.getUri()); - - Resource canonical = myVersionCanonicalizer.resourceToValidatorCanonical(fetched); - - if (canonical instanceof StructureDefinition) { - StructureDefinition canonicalSd = (StructureDefinition) canonical; - if (canonicalSd.getSnapshot().isEmpty()) { - ourLog.info("Generating snapshot for StructureDefinition: {}", canonicalSd.getUrl()); - fetched = myValidationSupportContext - .getRootValidationSupport() - .generateSnapshot(theValidationSupportContext, fetched, "", null, ""); - Validate.isTrue( - fetched != null, - "StructureDefinition %s has no snapshot, and no snapshot generator is configured", - key.getUri()); - canonical = myVersionCanonicalizer.resourceToValidatorCanonical(fetched); - } - } - - return canonical; - }); - - setValidationMessageLanguage(getLocale()); - } - - @Override - public Set getBinaryKeysAsSet() { - throw new UnsupportedOperationException(Msg.code(2118)); - } - - @Override - public boolean hasBinaryKey(String s) { - return myValidationSupportContext.getRootValidationSupport().fetchBinary(s) != null; - } - - @Override - public byte[] getBinaryForKey(String s) { - return myValidationSupportContext.getRootValidationSupport().fetchBinary(s); - } - - @Override - public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader) throws FHIRException { - throw new UnsupportedOperationException(Msg.code(652)); - } - - @Override - public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader, List types) - throws FileNotFoundException, IOException, FHIRException { - throw new UnsupportedOperationException(Msg.code(653)); - } - - @Override - public int loadFromPackageAndDependencies(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm) - throws FHIRException { - throw new UnsupportedOperationException(Msg.code(654)); - } - - @Override - public boolean hasPackage(String id, String ver) { - throw new UnsupportedOperationException(Msg.code(655)); - } - - @Override - public boolean hasPackage(PackageInformation packageInformation) { - return false; - } - - @Override - public PackageInformation getPackage(String id, String ver) { - return null; - } - - @Override - public int getClientRetryCount() { - throw new UnsupportedOperationException(Msg.code(656)); - } - - @Override - public IWorkerContext setClientRetryCount(int value) { - throw new UnsupportedOperationException(Msg.code(657)); - } - - @Override - public TimeTracker clock() { - return null; - } - - @Override - public IWorkerContextManager.IPackageLoadingTracker getPackageTracker() { - throw new UnsupportedOperationException(Msg.code(2235)); - } - - @Override - public IWorkerContext setPackageTracker(IWorkerContextManager.IPackageLoadingTracker packageTracker) { - throw new UnsupportedOperationException(Msg.code(2266)); - } - - @Override - public String getSpecUrl() { - return ""; - } - - @Override - public PEBuilder getProfiledElementBuilder( - PEBuilder.PEElementPropertiesPolicy thePEElementPropertiesPolicy, boolean theB) { - throw new UnsupportedOperationException(Msg.code(2264)); - } - - @Override - public PackageInformation getPackageForUrl(String s) { - throw new UnsupportedOperationException(Msg.code(2236)); - } - - @Override - public org.hl7.fhir.r5.model.Parameters getExpansionParameters() { - return myExpansionProfile; - } - - @Override - public void setExpansionProfile(org.hl7.fhir.r5.model.Parameters expParameters) { - myExpansionProfile = expParameters; - } - - private List allStructures() { - - List retVal = myAllStructures; - if (retVal == null) { - retVal = new ArrayList<>(); - for (IBaseResource next : - myValidationSupportContext.getRootValidationSupport().fetchAllStructureDefinitions()) { - try { - StructureDefinition converted = myVersionCanonicalizer.structureDefinitionToCanonical(next); - retVal.add(converted); - } catch (FHIRException e) { - throw new InternalErrorException(Msg.code(659) + e); - } - } - myAllStructures = retVal; - } - - return retVal; - } - - @Override - public void cacheResource(Resource res) { - } - - @Override - public void cacheResourceFromPackage(Resource res, PackageInformation packageDetails) throws FHIRException { - } - - @Override - public void cachePackage(PackageInformation packageInformation) { - } - - @Nonnull - private ValidationResult convertValidationResult( - String theSystem, @Nullable IValidationSupport.CodeValidationResult theResult) { - ValidationResult retVal = null; - if (theResult != null) { - String code = theResult.getCode(); - String display = theResult.getDisplay(); - - String issueSeverityCode = theResult.getSeverityCode(); - String message = theResult.getMessage(); - ValidationMessage.IssueSeverity issueSeverity = null; - if (issueSeverityCode != null) { - issueSeverity = ValidationMessage.IssueSeverity.fromCode(issueSeverityCode); - } else if (isNotBlank(message)) { - issueSeverity = ValidationMessage.IssueSeverity.INFORMATION; - } - - CodeSystem.ConceptDefinitionComponent conceptDefinitionComponent = null; - if (code != null) { - conceptDefinitionComponent = new CodeSystem.ConceptDefinitionComponent() - .setCode(code) - .setDisplay(display); - } - - retVal = new ValidationResult( - issueSeverity, - message, - theSystem, - theResult.getCodeSystemVersion(), - conceptDefinitionComponent, - display, - null); - } - - if (retVal == null) { - retVal = new ValidationResult(ValidationMessage.IssueSeverity.ERROR, "Validation failed", null); - } - - return retVal; - } - - @Override - public ValueSetExpansionOutcome expandVS( - org.hl7.fhir.r5.model.ValueSet source, boolean cacheOk, boolean Hierarchical) { - IBaseResource convertedSource; - try { - convertedSource = myVersionCanonicalizer.valueSetFromValidatorCanonical(source); - } catch (FHIRException e) { - throw new InternalErrorException(Msg.code(661) + e); - } - IValidationSupport.ValueSetExpansionOutcome expanded = myValidationSupportContext - .getRootValidationSupport() - .expandValueSet(myValidationSupportContext, null, convertedSource); - - org.hl7.fhir.r5.model.ValueSet convertedResult = null; - if (expanded.getValueSet() != null) { - try { - convertedResult = myVersionCanonicalizer.valueSetToValidatorCanonical(expanded.getValueSet()); - } catch (FHIRException e) { - throw new InternalErrorException(Msg.code(662) + e); - } - } - - String error = expanded.getError(); - TerminologyServiceErrorClass result = null; - - return new ValueSetExpansionOutcome(convertedResult, error, result); - } - - @Override - public ValueSetExpansionOutcome expandVS( - Resource src, - org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent binding, - boolean cacheOk, - boolean Hierarchical) { - throw new UnsupportedOperationException(Msg.code(663)); - } - - @Override - public ValueSetExpansionOutcome expandVS(ValueSet.ConceptSetComponent inc, boolean hierarchical, boolean noInactive) - throws TerminologyServiceException { - throw new UnsupportedOperationException(Msg.code(664)); - } - - @Override - public Locale getLocale() { - return myValidationSupportContext - .getRootValidationSupport() - .getFhirContext() - .getLocalizer() - .getLocale(); - } - - @Override - public void setLocale(Locale locale) { - // ignore - } - - @Override - public org.hl7.fhir.r5.model.CodeSystem fetchCodeSystem(String system) { - IBaseResource fetched = - myValidationSupportContext.getRootValidationSupport().fetchCodeSystem(system); - if (fetched == null) { - return null; - } - try { - return (org.hl7.fhir.r5.model.CodeSystem) myVersionCanonicalizer.codeSystemToValidatorCanonical(fetched); - } catch (FHIRException e) { - throw new InternalErrorException(Msg.code(665) + e); - } - } - - @Override - public CodeSystem fetchCodeSystem(String system, String verison) { - IBaseResource fetched = - myValidationSupportContext.getRootValidationSupport().fetchCodeSystem(system); - if (fetched == null) { - return null; - } - try { - return (org.hl7.fhir.r5.model.CodeSystem) myVersionCanonicalizer.codeSystemToValidatorCanonical(fetched); - } catch (FHIRException e) { - throw new InternalErrorException(Msg.code(1992) + e); - } - } - - @Override - public CodeSystem fetchSupplementedCodeSystem(String system) { - return null; - } - - @Override - public CodeSystem fetchSupplementedCodeSystem(String system, String version) { - return null; - } - - @Override - public T fetchResourceRaw(Class class_, String uri) { - return fetchResource(class_, uri); - } - - @Override - public T fetchResource(Class class_, String uri) { - - if (isBlank(uri)) { - return null; - } - - ResourceKey key = new ResourceKey(class_.getSimpleName(), uri); - @SuppressWarnings("unchecked") - T retVal = (T) myFetchResourceCache.get(key); - - return retVal; - } - - @Override - public Resource fetchResourceById(String type, String uri) { - throw new UnsupportedOperationException(Msg.code(666)); - } - - @Override - public T fetchResourceWithException(Class class_, String uri) throws FHIRException { - T retVal = fetchResource(class_, uri); - if (retVal == null) { - throw new FHIRException( - Msg.code(667) + "Can not find resource of type " + class_.getSimpleName() + " with uri " + uri); - } - return retVal; - } - - @Override - public T fetchResource(Class class_, String uri, String version) { - return fetchResource(class_, uri + "|" + version); - } - - @Override - public T fetchResource(Class class_, String uri, Resource canonicalForSource) { - return fetchResource(class_, uri); - } - - @Override - public T fetchResourceWithException(Class class_, String uri, Resource sourceOfReference) - throws FHIRException { - throw new UnsupportedOperationException(Msg.code(2214)); - } - - @Override - public List getResourceNames() { - return new ArrayList<>(myValidationSupportContext - .getRootValidationSupport() - .getFhirContext() - .getResourceTypes()); - } - - @Override - public Set getResourceNamesAsSet() { - return myValidationSupportContext - .getRootValidationSupport() - .getFhirContext() - .getResourceTypes(); - } - - @Override - public StructureDefinition fetchTypeDefinition(String typeName) { - return fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + typeName); - } - - @Override - public List fetchTypeDefinitions(String typeName) { - List allStructures = new ArrayList<>(allStructures()); - allStructures.removeIf(sd -> !sd.hasType() || !sd.getType().equals(typeName)); - return allStructures; - } - - @Override - public UcumService getUcumService() { - throw new UnsupportedOperationException(Msg.code(676)); - } - - @Override - public void setUcumService(UcumService ucumService) { - throw new UnsupportedOperationException(Msg.code(677)); - } - - @Override - public String getVersion() { - return myValidationSupportContext - .getRootValidationSupport() - .getFhirContext() - .getVersion() - .getVersion() - .getFhirVersionString(); - } - - @Override - public boolean hasResource(Class class_, String uri) { - throw new UnsupportedOperationException(Msg.code(680)); - } - - @Override - public boolean isNoTerminologyServer() { - return false; - } - - @Override - public Set getCodeSystemsUsed() { - throw new UnsupportedOperationException(Msg.code(681)); - } - - @Override - public IResourceValidator newValidator() { - throw new UnsupportedOperationException(Msg.code(684)); - } - - @Override - public Map getNSUrlMap() { - throw new UnsupportedOperationException(Msg.code(2265)); - } - - @Override - public ILoggingService getLogger() { - return null; - } - - @Override - public void setLogger(ILoggingService logger) { - throw new UnsupportedOperationException(Msg.code(687)); - } - - @Override - public boolean supportsSystem(String system) { - return myValidationSupportContext - .getRootValidationSupport() - .isCodeSystemSupported(myValidationSupportContext, system); - } - - @Override - public TranslationServices translator() { - throw new UnsupportedOperationException(Msg.code(688)); - } - - @Override - public ValueSetExpansionOutcome expandVS( - ValueSet source, boolean cacheOk, boolean heiarchical, boolean incompleteOk) { - return null; - } - - @Override - public ValidationResult validateCode( - ValidationOptions theOptions, String system, String version, String code, String display) { - ConceptValidationOptions validationOptions = convertConceptValidationOptions(theOptions); - return doValidation(null, validationOptions, system, code, display); - } - - @Override - public ValidationResult validateCode( - ValidationOptions theOptions, - String theSystem, - String version, - String theCode, - String display, - ValueSet theValueSet) { - IBaseResource convertedVs = null; - - try { - if (theValueSet != null) { - convertedVs = myVersionCanonicalizer.valueSetFromValidatorCanonical(theValueSet); - } - } catch (FHIRException e) { - throw new InternalErrorException(Msg.code(689) + e); - } - - ConceptValidationOptions validationOptions = convertConceptValidationOptions(theOptions); - - return doValidation(convertedVs, validationOptions, theSystem, theCode, display); - } - - @Override - public ValidationResult validateCode( - ValidationOptions theOptions, String code, org.hl7.fhir.r5.model.ValueSet theValueSet) { - IBaseResource convertedVs = null; - try { - if (theValueSet != null) { - convertedVs = myVersionCanonicalizer.valueSetFromValidatorCanonical(theValueSet); - } - } catch (FHIRException e) { - throw new InternalErrorException(Msg.code(690) + e); - } - - ConceptValidationOptions validationOptions = - convertConceptValidationOptions(theOptions).setInferSystem(true); - - return doValidation(convertedVs, validationOptions, null, code, null); - } - - @Override - public ValidationResult validateCode( - ValidationOptions theOptions, - org.hl7.fhir.r5.model.Coding theCoding, - org.hl7.fhir.r5.model.ValueSet theValueSet) { - IBaseResource convertedVs = null; - - try { - if (theValueSet != null) { - convertedVs = myVersionCanonicalizer.valueSetFromValidatorCanonical(theValueSet); - } - } catch (FHIRException e) { - throw new InternalErrorException(Msg.code(691) + e); - } - - ConceptValidationOptions validationOptions = convertConceptValidationOptions(theOptions); - String system = theCoding.getSystem(); - String code = theCoding.getCode(); - String display = theCoding.getDisplay(); - - return doValidation(convertedVs, validationOptions, system, code, display); - } - - @Override - public ValidationResult validateCode( - ValidationOptions options, Coding code, ValueSet vs, ValidationContextCarrier ctxt) { - return validateCode(options, code, vs); - } - - @Override - public void validateCodeBatch( - ValidationOptions options, List codes, ValueSet vs) { - for (CodingValidationRequest next : codes) { - ValidationResult outcome = validateCode(options, next.getCoding(), vs); - next.setResult(outcome); - } - } - - - @Nonnull - private ValidationResult doValidation( - IBaseResource theValueSet, - ConceptValidationOptions theValidationOptions, - String theSystem, - String theCode, - String theDisplay) { - IValidationSupport.CodeValidationResult result; - if (theValueSet != null) { - result = myValidationSupportContext - .getRootValidationSupport() - .validateCodeInValueSet( - myValidationSupportContext, - theValidationOptions, - theSystem, - theCode, - theDisplay, - theValueSet); - } else { - result = myValidationSupportContext - .getRootValidationSupport() - .validateCode( - myValidationSupportContext, theValidationOptions, theSystem, theCode, theDisplay, null); - } - return convertValidationResult(theSystem, result); - } - - @Override - public ValidationResult validateCode( - ValidationOptions theOptions, - org.hl7.fhir.r5.model.CodeableConcept code, - org.hl7.fhir.r5.model.ValueSet theVs) { - List validationResultsOk = new ArrayList<>(); - for (Coding next : code.getCoding()) { - ValidationResult retVal = validateCode(theOptions, next, theVs); - if (retVal.isOk()) { - if (myValidationSupportContext.isEnabledValidationForCodingsLogicalAnd()) { - validationResultsOk.add(retVal); - } else { - return retVal; - } - } - } - - if (code.getCoding().size() > 0 - && validationResultsOk.size() == code.getCoding().size()) { - return validationResultsOk.get(0); - } - - return new ValidationResult(ValidationMessage.IssueSeverity.ERROR, null, null); - } - - public void invalidateCaches() { - myFetchResourceCache.invalidateAll(); - } - - @Override - public List fetchResourcesByType(Class theClass) { - if (theClass.equals(StructureDefinition.class)) { - return (List) allStructures(); - } - throw new UnsupportedOperationException(Msg.code(650) + "Unable to fetch resources of type: " + theClass); - } - - @Override - public boolean isForPublication() { - return false; - } - - @Override - public void setForPublication(boolean b) { - throw new UnsupportedOperationException(Msg.code(2351)); - } - - public static ConceptValidationOptions convertConceptValidationOptions(ValidationOptions theOptions) { - ConceptValidationOptions retVal = new ConceptValidationOptions(); - if (theOptions.isGuessSystem()) { - retVal = retVal.setInferSystem(true); - } - return retVal; - } - - @Nonnull - public static VersionSpecificWorkerContextWrapper newVersionSpecificWorkerContextWrapper( - IValidationSupport theValidationSupport) { - VersionCanonicalizer versionCanonicalizer = new VersionCanonicalizer(theValidationSupport.getFhirContext()); - return new VersionSpecificWorkerContextWrapper( - new ValidationSupportContext(theValidationSupport), versionCanonicalizer); - } - - private static class ResourceKey { - private final int myHashCode; - private final String myResourceName; - private final String myUri; - - private ResourceKey(String theResourceName, String theUri) { - myResourceName = theResourceName; - myUri = theUri; - myHashCode = new HashCodeBuilder(17, 37) - .append(myResourceName) - .append(myUri) - .toHashCode(); - } - - @Override - public boolean equals(Object theO) { - if (this == theO) { - return true; - } - - if (theO == null || getClass() != theO.getClass()) { - return false; - } - - ResourceKey that = (ResourceKey) theO; - - return new EqualsBuilder() - .append(myResourceName, that.myResourceName) - .append(myUri, that.myUri) - .isEquals(); - } - - public String getResourceName() { - return myResourceName; - } - - public String getUri() { - return myUri; - } - - @Override - public int hashCode() { - return myHashCode; - } - } + private static final Logger ourLog = LoggerFactory.getLogger(VersionSpecificWorkerContextWrapper.class); + private final ValidationSupportContext myValidationSupportContext; + private final VersionCanonicalizer myVersionCanonicalizer; + private final LoadingCache myFetchResourceCache; + private volatile List myAllStructures; + private org.hl7.fhir.r5.model.Parameters myExpansionProfile; + + public VersionSpecificWorkerContextWrapper( + ValidationSupportContext theValidationSupportContext, VersionCanonicalizer theVersionCanonicalizer) { + myValidationSupportContext = theValidationSupportContext; + myVersionCanonicalizer = theVersionCanonicalizer; + + long timeoutMillis = HapiSystemProperties.getTestValidationResourceCachesMs(); + + myFetchResourceCache = CacheFactory.build(timeoutMillis, 10000, key -> { + String fetchResourceName = key.getResourceName(); + if (myValidationSupportContext + .getRootValidationSupport() + .getFhirContext() + .getVersion() + .getVersion() + == FhirVersionEnum.DSTU2) { + if ("CodeSystem".equals(fetchResourceName)) { + fetchResourceName = "ValueSet"; + } + } + + Class fetchResourceType; + if (fetchResourceName.equals("Resource")) { + fetchResourceType = null; + } else { + fetchResourceType = myValidationSupportContext + .getRootValidationSupport() + .getFhirContext() + .getResourceDefinition(fetchResourceName) + .getImplementingClass(); + } + + IBaseResource fetched = myValidationSupportContext + .getRootValidationSupport() + .fetchResource(fetchResourceType, key.getUri()); + + Resource canonical = myVersionCanonicalizer.resourceToValidatorCanonical(fetched); + + if (canonical instanceof StructureDefinition) { + StructureDefinition canonicalSd = (StructureDefinition) canonical; + if (canonicalSd.getSnapshot().isEmpty()) { + ourLog.info("Generating snapshot for StructureDefinition: {}", canonicalSd.getUrl()); + fetched = myValidationSupportContext + .getRootValidationSupport() + .generateSnapshot(theValidationSupportContext, fetched, "", null, ""); + Validate.isTrue( + fetched != null, + "StructureDefinition %s has no snapshot, and no snapshot generator is configured", + key.getUri()); + canonical = myVersionCanonicalizer.resourceToValidatorCanonical(fetched); + } + } + + return canonical; + }); + + setValidationMessageLanguage(getLocale()); + } + + @Override + public Set getBinaryKeysAsSet() { + throw new UnsupportedOperationException(Msg.code(2118)); + } + + @Override + public boolean hasBinaryKey(String s) { + return myValidationSupportContext.getRootValidationSupport().fetchBinary(s) != null; + } + + @Override + public byte[] getBinaryForKey(String s) { + return myValidationSupportContext.getRootValidationSupport().fetchBinary(s); + } + + @Override + public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader) throws FHIRException { + throw new UnsupportedOperationException(Msg.code(652)); + } + + @Override + public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader, List types) + throws FileNotFoundException, IOException, FHIRException { + throw new UnsupportedOperationException(Msg.code(653)); + } + + @Override + public int loadFromPackageAndDependencies(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm) + throws FHIRException { + throw new UnsupportedOperationException(Msg.code(654)); + } + + @Override + public boolean hasPackage(String id, String ver) { + throw new UnsupportedOperationException(Msg.code(655)); + } + + @Override + public boolean hasPackage(PackageInformation packageInformation) { + return false; + } + + @Override + public PackageInformation getPackage(String id, String ver) { + return null; + } + + @Override + public int getClientRetryCount() { + throw new UnsupportedOperationException(Msg.code(656)); + } + + @Override + public IWorkerContext setClientRetryCount(int value) { + throw new UnsupportedOperationException(Msg.code(657)); + } + + @Override + public TimeTracker clock() { + return null; + } + + @Override + public IWorkerContextManager.IPackageLoadingTracker getPackageTracker() { + throw new UnsupportedOperationException(Msg.code(2235)); + } + + @Override + public IWorkerContext setPackageTracker(IWorkerContextManager.IPackageLoadingTracker packageTracker) { + throw new UnsupportedOperationException(Msg.code(2266)); + } + + @Override + public String getSpecUrl() { + return ""; + } + + @Override + public PEBuilder getProfiledElementBuilder( + PEBuilder.PEElementPropertiesPolicy thePEElementPropertiesPolicy, boolean theB) { + throw new UnsupportedOperationException(Msg.code(2264)); + } + + @Override + public PackageInformation getPackageForUrl(String s) { + throw new UnsupportedOperationException(Msg.code(2236)); + } + + @Override + public org.hl7.fhir.r5.model.Parameters getExpansionParameters() { + return myExpansionProfile; + } + + @Override + public void setExpansionProfile(org.hl7.fhir.r5.model.Parameters expParameters) { + myExpansionProfile = expParameters; + } + + private List allStructures() { + + List retVal = myAllStructures; + if (retVal == null) { + retVal = new ArrayList<>(); + for (IBaseResource next : + myValidationSupportContext.getRootValidationSupport().fetchAllStructureDefinitions()) { + try { + StructureDefinition converted = myVersionCanonicalizer.structureDefinitionToCanonical(next); + retVal.add(converted); + } catch (FHIRException e) { + throw new InternalErrorException(Msg.code(659) + e); + } + } + myAllStructures = retVal; + } + + return retVal; + } + + @Override + public void cacheResource(Resource res) {} + + @Override + public void cacheResourceFromPackage(Resource res, PackageInformation packageDetails) throws FHIRException {} + + @Override + public void cachePackage(PackageInformation packageInformation) {} + + @Nonnull + private ValidationResult convertValidationResult( + String theSystem, @Nullable IValidationSupport.CodeValidationResult theResult) { + ValidationResult retVal = null; + if (theResult != null) { + String code = theResult.getCode(); + String display = theResult.getDisplay(); + + String issueSeverityCode = theResult.getSeverityCode(); + String message = theResult.getMessage(); + ValidationMessage.IssueSeverity issueSeverity = null; + if (issueSeverityCode != null) { + issueSeverity = ValidationMessage.IssueSeverity.fromCode(issueSeverityCode); + } else if (isNotBlank(message)) { + issueSeverity = ValidationMessage.IssueSeverity.INFORMATION; + } + + CodeSystem.ConceptDefinitionComponent conceptDefinitionComponent = null; + if (code != null) { + conceptDefinitionComponent = new CodeSystem.ConceptDefinitionComponent() + .setCode(code) + .setDisplay(display); + } + + retVal = new ValidationResult( + issueSeverity, + message, + theSystem, + theResult.getCodeSystemVersion(), + conceptDefinitionComponent, + display, + null); + } + + if (retVal == null) { + retVal = new ValidationResult(ValidationMessage.IssueSeverity.ERROR, "Validation failed", null); + } + + return retVal; + } + + @Override + public ValueSetExpansionOutcome expandVS( + org.hl7.fhir.r5.model.ValueSet source, boolean cacheOk, boolean Hierarchical) { + IBaseResource convertedSource; + try { + convertedSource = myVersionCanonicalizer.valueSetFromValidatorCanonical(source); + } catch (FHIRException e) { + throw new InternalErrorException(Msg.code(661) + e); + } + IValidationSupport.ValueSetExpansionOutcome expanded = myValidationSupportContext + .getRootValidationSupport() + .expandValueSet(myValidationSupportContext, null, convertedSource); + + org.hl7.fhir.r5.model.ValueSet convertedResult = null; + if (expanded.getValueSet() != null) { + try { + convertedResult = myVersionCanonicalizer.valueSetToValidatorCanonical(expanded.getValueSet()); + } catch (FHIRException e) { + throw new InternalErrorException(Msg.code(662) + e); + } + } + + String error = expanded.getError(); + TerminologyServiceErrorClass result = null; + + return new ValueSetExpansionOutcome(convertedResult, error, result); + } + + @Override + public ValueSetExpansionOutcome expandVS( + Resource src, + org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent binding, + boolean cacheOk, + boolean Hierarchical) { + throw new UnsupportedOperationException(Msg.code(663)); + } + + @Override + public ValueSetExpansionOutcome expandVS(ValueSet.ConceptSetComponent inc, boolean hierarchical, boolean noInactive) + throws TerminologyServiceException { + throw new UnsupportedOperationException(Msg.code(664)); + } + + @Override + public Locale getLocale() { + return myValidationSupportContext + .getRootValidationSupport() + .getFhirContext() + .getLocalizer() + .getLocale(); + } + + @Override + public void setLocale(Locale locale) { + // ignore + } + + @Override + public org.hl7.fhir.r5.model.CodeSystem fetchCodeSystem(String system) { + IBaseResource fetched = + myValidationSupportContext.getRootValidationSupport().fetchCodeSystem(system); + if (fetched == null) { + return null; + } + try { + return (org.hl7.fhir.r5.model.CodeSystem) myVersionCanonicalizer.codeSystemToValidatorCanonical(fetched); + } catch (FHIRException e) { + throw new InternalErrorException(Msg.code(665) + e); + } + } + + @Override + public CodeSystem fetchCodeSystem(String system, String verison) { + IBaseResource fetched = + myValidationSupportContext.getRootValidationSupport().fetchCodeSystem(system); + if (fetched == null) { + return null; + } + try { + return (org.hl7.fhir.r5.model.CodeSystem) myVersionCanonicalizer.codeSystemToValidatorCanonical(fetched); + } catch (FHIRException e) { + throw new InternalErrorException(Msg.code(1992) + e); + } + } + + @Override + public CodeSystem fetchSupplementedCodeSystem(String system) { + return null; + } + + @Override + public CodeSystem fetchSupplementedCodeSystem(String system, String version) { + return null; + } + + @Override + public T fetchResourceRaw(Class class_, String uri) { + return fetchResource(class_, uri); + } + + @Override + public T fetchResource(Class class_, String uri) { + + if (isBlank(uri)) { + return null; + } + + ResourceKey key = new ResourceKey(class_.getSimpleName(), uri); + @SuppressWarnings("unchecked") + T retVal = (T) myFetchResourceCache.get(key); + + return retVal; + } + + @Override + public Resource fetchResourceById(String type, String uri) { + throw new UnsupportedOperationException(Msg.code(666)); + } + + @Override + public T fetchResourceWithException(Class class_, String uri) throws FHIRException { + T retVal = fetchResource(class_, uri); + if (retVal == null) { + throw new FHIRException( + Msg.code(667) + "Can not find resource of type " + class_.getSimpleName() + " with uri " + uri); + } + return retVal; + } + + @Override + public T fetchResource(Class class_, String uri, String version) { + return fetchResource(class_, uri + "|" + version); + } + + @Override + public T fetchResource(Class class_, String uri, Resource canonicalForSource) { + return fetchResource(class_, uri); + } + + @Override + public T fetchResourceWithException(Class class_, String uri, Resource sourceOfReference) + throws FHIRException { + throw new UnsupportedOperationException(Msg.code(2214)); + } + + @Override + public List getResourceNames() { + return new ArrayList<>(myValidationSupportContext + .getRootValidationSupport() + .getFhirContext() + .getResourceTypes()); + } + + @Override + public Set getResourceNamesAsSet() { + return myValidationSupportContext + .getRootValidationSupport() + .getFhirContext() + .getResourceTypes(); + } + + @Override + public StructureDefinition fetchTypeDefinition(String typeName) { + return fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + typeName); + } + + @Override + public List fetchTypeDefinitions(String typeName) { + List allStructures = new ArrayList<>(allStructures()); + allStructures.removeIf(sd -> !sd.hasType() || !sd.getType().equals(typeName)); + return allStructures; + } + + @Override + public UcumService getUcumService() { + throw new UnsupportedOperationException(Msg.code(676)); + } + + @Override + public void setUcumService(UcumService ucumService) { + throw new UnsupportedOperationException(Msg.code(677)); + } + + @Override + public String getVersion() { + return myValidationSupportContext + .getRootValidationSupport() + .getFhirContext() + .getVersion() + .getVersion() + .getFhirVersionString(); + } + + @Override + public boolean hasResource(Class class_, String uri) { + throw new UnsupportedOperationException(Msg.code(680)); + } + + @Override + public boolean isNoTerminologyServer() { + return false; + } + + @Override + public Set getCodeSystemsUsed() { + throw new UnsupportedOperationException(Msg.code(681)); + } + + @Override + public IResourceValidator newValidator() { + throw new UnsupportedOperationException(Msg.code(684)); + } + + @Override + public Map getNSUrlMap() { + throw new UnsupportedOperationException(Msg.code(2265)); + } + + @Override + public ILoggingService getLogger() { + return null; + } + + @Override + public void setLogger(ILoggingService logger) { + throw new UnsupportedOperationException(Msg.code(687)); + } + + @Override + public boolean supportsSystem(String system) { + return myValidationSupportContext + .getRootValidationSupport() + .isCodeSystemSupported(myValidationSupportContext, system); + } + + @Override + public TranslationServices translator() { + throw new UnsupportedOperationException(Msg.code(688)); + } + + @Override + public ValueSetExpansionOutcome expandVS( + ValueSet source, boolean cacheOk, boolean heiarchical, boolean incompleteOk) { + return null; + } + + @Override + public ValidationResult validateCode( + ValidationOptions theOptions, String system, String version, String code, String display) { + ConceptValidationOptions validationOptions = convertConceptValidationOptions(theOptions); + return doValidation(null, validationOptions, system, code, display); + } + + @Override + public ValidationResult validateCode( + ValidationOptions theOptions, + String theSystem, + String version, + String theCode, + String display, + ValueSet theValueSet) { + IBaseResource convertedVs = null; + + try { + if (theValueSet != null) { + convertedVs = myVersionCanonicalizer.valueSetFromValidatorCanonical(theValueSet); + } + } catch (FHIRException e) { + throw new InternalErrorException(Msg.code(689) + e); + } + + ConceptValidationOptions validationOptions = convertConceptValidationOptions(theOptions); + + return doValidation(convertedVs, validationOptions, theSystem, theCode, display); + } + + @Override + public ValidationResult validateCode( + ValidationOptions theOptions, String code, org.hl7.fhir.r5.model.ValueSet theValueSet) { + IBaseResource convertedVs = null; + try { + if (theValueSet != null) { + convertedVs = myVersionCanonicalizer.valueSetFromValidatorCanonical(theValueSet); + } + } catch (FHIRException e) { + throw new InternalErrorException(Msg.code(690) + e); + } + + ConceptValidationOptions validationOptions = + convertConceptValidationOptions(theOptions).setInferSystem(true); + + return doValidation(convertedVs, validationOptions, null, code, null); + } + + @Override + public ValidationResult validateCode( + ValidationOptions theOptions, + org.hl7.fhir.r5.model.Coding theCoding, + org.hl7.fhir.r5.model.ValueSet theValueSet) { + IBaseResource convertedVs = null; + + try { + if (theValueSet != null) { + convertedVs = myVersionCanonicalizer.valueSetFromValidatorCanonical(theValueSet); + } + } catch (FHIRException e) { + throw new InternalErrorException(Msg.code(691) + e); + } + + ConceptValidationOptions validationOptions = convertConceptValidationOptions(theOptions); + String system = theCoding.getSystem(); + String code = theCoding.getCode(); + String display = theCoding.getDisplay(); + + return doValidation(convertedVs, validationOptions, system, code, display); + } + + @Override + public ValidationResult validateCode( + ValidationOptions options, Coding code, ValueSet vs, ValidationContextCarrier ctxt) { + return validateCode(options, code, vs); + } + + @Override + public void validateCodeBatch( + ValidationOptions options, List codes, ValueSet vs) { + for (CodingValidationRequest next : codes) { + ValidationResult outcome = validateCode(options, next.getCoding(), vs); + next.setResult(outcome); + } + } + + @Nonnull + private ValidationResult doValidation( + IBaseResource theValueSet, + ConceptValidationOptions theValidationOptions, + String theSystem, + String theCode, + String theDisplay) { + IValidationSupport.CodeValidationResult result; + if (theValueSet != null) { + result = myValidationSupportContext + .getRootValidationSupport() + .validateCodeInValueSet( + myValidationSupportContext, + theValidationOptions, + theSystem, + theCode, + theDisplay, + theValueSet); + } else { + result = myValidationSupportContext + .getRootValidationSupport() + .validateCode( + myValidationSupportContext, theValidationOptions, theSystem, theCode, theDisplay, null); + } + return convertValidationResult(theSystem, result); + } + + @Override + public ValidationResult validateCode( + ValidationOptions theOptions, + org.hl7.fhir.r5.model.CodeableConcept code, + org.hl7.fhir.r5.model.ValueSet theVs) { + List validationResultsOk = new ArrayList<>(); + for (Coding next : code.getCoding()) { + ValidationResult retVal = validateCode(theOptions, next, theVs); + if (retVal.isOk()) { + if (myValidationSupportContext.isEnabledValidationForCodingsLogicalAnd()) { + validationResultsOk.add(retVal); + } else { + return retVal; + } + } + } + + if (code.getCoding().size() > 0 + && validationResultsOk.size() == code.getCoding().size()) { + return validationResultsOk.get(0); + } + + return new ValidationResult(ValidationMessage.IssueSeverity.ERROR, null, null); + } + + public void invalidateCaches() { + myFetchResourceCache.invalidateAll(); + } + + @Override + public List fetchResourcesByType(Class theClass) { + if (theClass.equals(StructureDefinition.class)) { + return (List) allStructures(); + } + throw new UnsupportedOperationException(Msg.code(650) + "Unable to fetch resources of type: " + theClass); + } + + @Override + public boolean isForPublication() { + return false; + } + + @Override + public void setForPublication(boolean b) { + throw new UnsupportedOperationException(Msg.code(2351)); + } + + public static ConceptValidationOptions convertConceptValidationOptions(ValidationOptions theOptions) { + ConceptValidationOptions retVal = new ConceptValidationOptions(); + if (theOptions.isGuessSystem()) { + retVal = retVal.setInferSystem(true); + } + return retVal; + } + + @Nonnull + public static VersionSpecificWorkerContextWrapper newVersionSpecificWorkerContextWrapper( + IValidationSupport theValidationSupport) { + VersionCanonicalizer versionCanonicalizer = new VersionCanonicalizer(theValidationSupport.getFhirContext()); + return new VersionSpecificWorkerContextWrapper( + new ValidationSupportContext(theValidationSupport), versionCanonicalizer); + } + + private static class ResourceKey { + private final int myHashCode; + private final String myResourceName; + private final String myUri; + + private ResourceKey(String theResourceName, String theUri) { + myResourceName = theResourceName; + myUri = theUri; + myHashCode = new HashCodeBuilder(17, 37) + .append(myResourceName) + .append(myUri) + .toHashCode(); + } + + @Override + public boolean equals(Object theO) { + if (this == theO) { + return true; + } + + if (theO == null || getClass() != theO.getClass()) { + return false; + } + + ResourceKey that = (ResourceKey) theO; + + return new EqualsBuilder() + .append(myResourceName, that.myResourceName) + .append(myUri, that.myUri) + .isEquals(); + } + + public String getResourceName() { + return myResourceName; + } + + public String getUri() { + return myUri; + } + + @Override + public int hashCode() { + return myHashCode; + } + } } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupportTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupportTest.java index 2953280fbf06..a33284774779 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupportTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupportTest.java @@ -22,6 +22,9 @@ import java.util.HashMap; import java.util.Map; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -62,7 +65,7 @@ public void testValidateCodeWithInferredSystem_CommonCodeSystemsCs_BuiltInVs() { // ValidateCode outcome = myChain.validateCode(valCtx, options, null, "txt", null, valueSetUrl); assertTrue(outcome.isOk()); - assertEquals("Code was validated against in-memory expansion of ValueSet: http://hl7.org/fhir/ValueSet/mimetypes", outcome.getMessage()); + assertEquals("Code was validated against in-memory expansion of ValueSet: http://hl7.org/fhir/ValueSet/mimetypes", outcome.getSourceDetails()); assertEquals("txt", outcome.getCode()); // ValidateCodeInValueSet @@ -70,7 +73,7 @@ public void testValidateCodeWithInferredSystem_CommonCodeSystemsCs_BuiltInVs() { assertNotNull(valueSet); outcome = myChain.validateCodeInValueSet(valCtx, options, null, "txt", null, valueSet); assertTrue(outcome.isOk()); - assertEquals("Code was validated against in-memory expansion of ValueSet: http://hl7.org/fhir/ValueSet/mimetypes", outcome.getMessage()); + assertEquals("Code was validated against in-memory expansion of ValueSet: http://hl7.org/fhir/ValueSet/mimetypes", outcome.getSourceDetails()); assertEquals("txt", outcome.getCode()); } @@ -91,7 +94,7 @@ public void testValidateCode_UnknownCodeSystem_EnumeratedValueSet() { IValidationSupport.CodeValidationResult outcome; outcome = myChain.validateCodeInValueSet(valCtx, options, "http://cs", "code1", null, vs); - assertEquals("Code was validated against in-memory expansion of ValueSet: http://vs", outcome.getMessage()); + assertEquals("Code was validated against in-memory expansion of ValueSet: http://vs", outcome.getSourceDetails()); assertTrue(outcome.isOk()); outcome = myChain.validateCodeInValueSet(valCtx, options, "http://cs", "code99", null, vs); @@ -127,7 +130,9 @@ public void testValidateCode_UnknownCodeSystem_EnumeratedValueSet_MultipleInclud IValidationSupport.CodeValidationResult outcome; outcome = myChain.validateCodeInValueSet(valCtx, options, "http://cs", "code1", null, vs); - assertEquals("Code was validated against in-memory expansion of ValueSet: http://vs", outcome.getMessage()); + assertNull(outcome.getMessage()); + assertNull(outcome.getSeverityCode()); + assertEquals("Code was validated against in-memory expansion of ValueSet: http://vs", outcome.getSourceDetails()); assertTrue(outcome.isOk()); outcome = myChain.validateCodeInValueSet(valCtx, options, "http://cs", "code99", null, vs); @@ -243,10 +248,13 @@ public void testExpandValueSet_VsIsEnumeratedWithVersionedSystem_CsOnlyDifferent code = "28571000087109"; display = "BLAH"; outcome = mySvc.validateCode(valCtx, options, codeSystemUrl, code, display, valueSetUrl); - assertFalse(outcome.isOk()); - assertEquals(null, outcome.getCode()); + assertTrue(outcome.isOk()); + assertEquals("28571000087109", outcome.getCode()); assertEquals("MODERNA COVID-19 mRNA-1273", outcome.getDisplay()); - assertEquals("0.17", outcome.getCodeSystemVersion()); + assertEquals("http://snomed.info/sct/20611000087101/version/20210331", outcome.getCodeSystemVersion()); + assertThat(outcome.getMessage(), containsString("Concept Display \"BLAH\" does not match expected \"MODERNA COVID-19 mRNA-1273\"")); + assertEquals("warning", outcome.getSeverityCode()); + assertThat(outcome.getSourceDetails(), startsWith("Code was validated against in-memory expansion")); // Validate code - good code, good display codeSystemUrl = "http://snomed.info/sct"; From 8dcfd851dfbb8e67c8781739ef2c86d834d97f90 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Sat, 16 Sep 2023 15:45:03 -0400 Subject: [PATCH 05/10] Tests all seem to be working --- .../provider/ValueSetOperationProvider.java | 26 ++++++++++++------- .../extractor/SearchParamExtractorR4.java | 2 +- .../extractor/SearchParamExtractorR4B.java | 2 +- .../extractor/SearchParamExtractorR5.java | 2 +- .../FhirResourceDaoValueSetDstu2Test.java | 3 ++- .../FhirResourceDaoDstu3ValueSetTest.java | 2 +- .../ResourceProviderDstu3ValueSetTest.java | 22 +++++++++------- .../dao/r4/FhirResourceDaoR4ValueSetTest.java | 10 +++---- .../r4/ResourceProviderR4CodeSystemTest.java | 4 +-- ...rceProviderR4ValueSetNoVerCSNoVerTest.java | 11 ++++---- .../jpa/term/ValueSetExpansionR4Test.java | 13 +++++----- .../dao/r5/FhirResourceDaoR5ValueSetTest.java | 2 +- .../r5/ResourceProviderR5ValueSetTest.java | 11 ++++---- ...oryTerminologyServerValidationSupport.java | 2 +- .../FhirInstanceValidatorDstu3Test.java | 2 +- 15 files changed, 64 insertions(+), 50 deletions(-) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/ValueSetOperationProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/ValueSetOperationProvider.java index 816334e17ac4..b022da51caf9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/ValueSetOperationProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/ValueSetOperationProvider.java @@ -61,6 +61,10 @@ public class ValueSetOperationProvider extends BaseJpaProvider { private static final Logger ourLog = LoggerFactory.getLogger(ValueSetOperationProvider.class); + public static final String SOURCE_DETAILS = "sourceDetails"; + public static final String RESULT = "result"; + public static final String MESSAGE = "message"; + public static final String DISPLAY = "display"; @Autowired protected IValidationSupport myValidationSupport; @@ -145,9 +149,10 @@ protected IFhirResourceDaoValueSet getDao() { idempotent = true, typeName = "ValueSet", returnParameters = { - @OperationParam(name = "result", typeName = "boolean", min = 1), - @OperationParam(name = "message", typeName = "string"), - @OperationParam(name = "display", typeName = "string") + @OperationParam(name = RESULT, typeName = "boolean", min = 1), + @OperationParam(name = MESSAGE, typeName = "string"), + @OperationParam(name = DISPLAY, typeName = "string"), + @OperationParam(name = SOURCE_DETAILS, typeName = "string") }) public IBaseParameters validateCode( HttpServletRequest theServletRequest, @@ -159,7 +164,7 @@ public IBaseParameters validateCode( @OperationParam(name = "system", min = 0, max = 1, typeName = "uri") IPrimitiveType theSystem, @OperationParam(name = "systemVersion", min = 0, max = 1, typeName = "string") IPrimitiveType theSystemVersion, - @OperationParam(name = "display", min = 0, max = 1, typeName = "string") IPrimitiveType theDisplay, + @OperationParam(name = DISPLAY, min = 0, max = 1, typeName = "string") IPrimitiveType theDisplay, @OperationParam(name = "coding", min = 0, max = 1, typeName = "Coding") IBaseCoding theCoding, @OperationParam(name = "codeableConcept", min = 0, max = 1, typeName = "CodeableConcept") ICompositeType theCodeableConcept, @@ -251,7 +256,7 @@ private Supplier supplyUnableToValidateResult( name = ProviderConstants.OPERATION_INVALIDATE_EXPANSION, idempotent = false, typeName = "ValueSet", - returnParameters = {@OperationParam(name = "message", typeName = "string", min = 1, max = 1)}) + returnParameters = {@OperationParam(name = MESSAGE, typeName = "string", min = 1, max = 1)}) public IBaseParameters invalidateValueSetExpansion( @IdParam IIdType theValueSetId, RequestDetails theRequestDetails, HttpServletRequest theServletRequest) { startRequest(theServletRequest); @@ -260,7 +265,7 @@ public IBaseParameters invalidateValueSetExpansion( String outcome = myTermReadSvc.invalidatePreCalculatedExpansion(theValueSetId, theRequestDetails); IBaseParameters retVal = ParametersUtil.newInstance(getContext()); - ParametersUtil.addParameterToParametersString(getContext(), retVal, "message", outcome); + ParametersUtil.addParameterToParametersString(getContext(), retVal, MESSAGE, outcome); return retVal; } finally { @@ -325,12 +330,15 @@ public static ValueSetExpansionOptions createValueSetExpansionOptions( public static IBaseParameters toValidateCodeResult(FhirContext theContext, CodeValidationResult theResult) { IBaseParameters retVal = ParametersUtil.newInstance(theContext); - ParametersUtil.addParameterToParametersBoolean(theContext, retVal, "result", theResult.isOk()); + ParametersUtil.addParameterToParametersBoolean(theContext, retVal, RESULT, theResult.isOk()); if (isNotBlank(theResult.getMessage())) { - ParametersUtil.addParameterToParametersString(theContext, retVal, "message", theResult.getMessage()); + ParametersUtil.addParameterToParametersString(theContext, retVal, MESSAGE, theResult.getMessage()); } if (isNotBlank(theResult.getDisplay())) { - ParametersUtil.addParameterToParametersString(theContext, retVal, "display", theResult.getDisplay()); + ParametersUtil.addParameterToParametersString(theContext, retVal, DISPLAY, theResult.getDisplay()); + } + if (isNotBlank(theResult.getSourceDetails())) { + ParametersUtil.addParameterToParametersString(theContext, retVal, SOURCE_DETAILS, theResult.getSourceDetails()); } return retVal; diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java index 486c7d157d96..32a22686730c 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java @@ -40,12 +40,12 @@ import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.utils.FHIRPathEngine; -import javax.annotation.PostConstruct; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import javax.annotation.PostConstruct; import static org.apache.commons.lang3.StringUtils.isNotBlank; diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4B.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4B.java index 2b34730dc48e..749df636d955 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4B.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4B.java @@ -40,12 +40,12 @@ import org.hl7.fhir.r4b.model.ValueSet; import org.hl7.fhir.r4b.utils.FHIRPathEngine; -import javax.annotation.PostConstruct; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import javax.annotation.PostConstruct; import static org.apache.commons.lang3.StringUtils.isNotBlank; diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java index 18acbd862721..142432415f1b 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java @@ -39,12 +39,12 @@ import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.utils.FHIRPathEngine; -import javax.annotation.PostConstruct; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import javax.annotation.PostConstruct; import static org.apache.commons.lang3.StringUtils.isNotBlank; diff --git a/hapi-fhir-jpaserver-test-dstu2/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoValueSetDstu2Test.java b/hapi-fhir-jpaserver-test-dstu2/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoValueSetDstu2Test.java index 1700d1adc54a..6551761c69d7 100644 --- a/hapi-fhir-jpaserver-test-dstu2/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoValueSetDstu2Test.java +++ b/hapi-fhir-jpaserver-test-dstu2/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoValueSetDstu2Test.java @@ -103,9 +103,10 @@ public void testValidateCodeOperationByIdentifierAndCodeAndSystemAndBadDisplay() CodingDt coding = null; CodeableConceptDt codeableConcept = null; IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); - assertFalse(result.isOk()); + assertTrue(result.isOk()); assertEquals("Concept Display \"Systolic blood pressure at First encounterXXXX\" does not match expected \"Systolic blood pressure at First encounter\" for in-memory expansion of ValueSet: http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2", result.getMessage()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); + assertEquals(IValidationSupport.IssueSeverity.WARNING, result.getSeverity()); } @Test diff --git a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ValueSetTest.java b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ValueSetTest.java index 38b22ee02912..73d3d7afeab9 100644 --- a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ValueSetTest.java +++ b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ValueSetTest.java @@ -226,7 +226,7 @@ public void testValidateCodeOperationByIdentifierAndCodeAndSystemAndBadDisplay() Coding coding = null; CodeableConcept codeableConcept = null; IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); - assertFalse(result.isOk()); + assertTrue(result.isOk()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } diff --git a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java index 5a1465e79dee..09ce4621a132 100644 --- a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java +++ b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java @@ -9,6 +9,7 @@ import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; import ca.uhn.fhir.jpa.model.dao.JpaPid; import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.provider.ValueSetOperationProvider; import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.util.CircularQueueCaptureQueriesListener; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -789,14 +790,14 @@ public void testValidateCodeAgainstBuiltInSystem() throws Exception { String resp = myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); ourLog.info(resp); - assertEquals("result", respParam.getParameter().get(0).getName()); + assertEquals(ValueSetOperationProvider.RESULT, respParam.getParameter().get(0).getName()); assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue()); - assertEquals("message", respParam.getParameter().get(1).getName()); - assertEquals("Code was validated against in-memory expansion of ValueSet: http://hl7.org/fhir/ValueSet/administrative-gender", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); + assertEquals(ValueSetOperationProvider.DISPLAY, respParam.getParameter().get(1).getName()); + assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); - assertEquals("display", respParam.getParameter().get(2).getName()); - assertEquals("Male", ((StringType) respParam.getParameter().get(2).getValue()).getValue()); + assertEquals(ValueSetOperationProvider.SOURCE_DETAILS, respParam.getParameter().get(2).getName()); + assertEquals("Code was validated against in-memory expansion of ValueSet: http://hl7.org/fhir/ValueSet/administrative-gender", ((StringType) respParam.getParameter().get(2).getValue()).getValue()); } /** @@ -819,14 +820,15 @@ public void testValidateCodeAgainstBuiltInSystemByUrl() throws Exception { String resp = myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); ourLog.info(resp); - assertEquals("result", respParam.getParameter().get(0).getName()); + assertEquals(ValueSetOperationProvider.RESULT, respParam.getParameter().get(0).getName()); assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue()); - assertEquals("message", respParam.getParameter().get(1).getName()); - assertEquals("Code was validated against in-memory expansion of ValueSet: http://hl7.org/fhir/ValueSet/administrative-gender", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); + assertEquals(ValueSetOperationProvider.DISPLAY, respParam.getParameter().get(1).getName()); + assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); + + assertEquals(ValueSetOperationProvider.SOURCE_DETAILS, respParam.getParameter().get(2).getName()); + assertEquals("Code was validated against in-memory expansion of ValueSet: http://hl7.org/fhir/ValueSet/administrative-gender", ((StringType) respParam.getParameter().get(2).getValue()).getValue()); - assertEquals("display", respParam.getParameter().get(2).getName()); - assertEquals("Male", ((StringType) respParam.getParameter().get(2).getValue()).getValue()); } @Test diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValueSetTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValueSetTest.java index a95aa057d153..7faf5984eea7 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValueSetTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValueSetTest.java @@ -148,7 +148,7 @@ public void testValidateCodeInValueSet_HierarchicalAndEnumeratedValueset() { outcome = myValidationSupport.validateCode(ctx, options, "http://cs", "child10", null, "http://vs"); assertNotNull(outcome); assertTrue(outcome.isOk()); - assertEquals("Code was validated against in-memory expansion of ValueSet: http://vs", outcome.getMessage()); + assertEquals("Code was validated against in-memory expansion of ValueSet: http://vs", outcome.getSourceDetails()); outcome = myValidationSupport.validateCode(ctx, options, "http://cs", "childX", null, "http://vs"); assertNotNull(outcome); @@ -160,7 +160,7 @@ public void testValidateCodeInValueSet_HierarchicalAndEnumeratedValueset() { outcome = myValidationSupport.validateCode(ctx, options, "http://cs-np", "code1", null, "http://vs"); assertNotNull(outcome); assertTrue(outcome.isOk()); - assertEquals("Code was validated against in-memory expansion of ValueSet: http://vs", outcome.getMessage()); + assertEquals("Code was validated against in-memory expansion of ValueSet: http://vs", outcome.getSourceDetails()); outcome = myValidationSupport.validateCode(ctx, options, "http://cs-np", "codeX", null, "http://vs"); assertNotNull(outcome); @@ -251,7 +251,7 @@ public void testValidateCodeInValueSet_HierarchicalAndEnumeratedValueset_Hiberna outcome = myValidationSupport.validateCode(ctx, options, "http://cs", "child10", null, "http://vs"); assertNotNull(outcome); assertTrue(outcome.isOk()); - assertEquals("Code was validated against in-memory expansion of ValueSet: http://vs", outcome.getMessage()); + assertEquals("Code was validated against in-memory expansion of ValueSet: http://vs", outcome.getSourceDetails()); outcome = myValidationSupport.validateCode(ctx, options, "http://cs", "childX", null, "http://vs"); assertNotNull(outcome); @@ -263,7 +263,7 @@ public void testValidateCodeInValueSet_HierarchicalAndEnumeratedValueset_Hiberna outcome = myValidationSupport.validateCode(ctx, options, "http://cs-np", "code1", null, "http://vs"); assertNotNull(outcome); assertTrue(outcome.isOk()); - assertEquals("Code was validated against in-memory expansion of ValueSet: http://vs", outcome.getMessage()); + assertEquals("Code was validated against in-memory expansion of ValueSet: http://vs", outcome.getSourceDetails()); outcome = myValidationSupport.validateCode(ctx, options, "http://cs-np", "codeX", null, "http://vs"); assertNotNull(outcome); @@ -344,7 +344,7 @@ public void testValidateCodeOperationByIdentifierAndCodeAndSystemAndBadDisplay() Coding coding = null; CodeableConcept codeableConcept = null; IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); - assertFalse(result.isOk()); + assertTrue(result.isOk()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Concept Display \"Systolic blood pressure at First encounterXXXX\" does not match expected \"Systolic blood pressure at First encounter\" for in-memory expansion of ValueSet: http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2", result.getMessage()); } diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemTest.java index d5f1e93d872e..24958f4153b8 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemTest.java @@ -529,8 +529,8 @@ public void testValidateCodeFoundByCodeNotMatchDisplay() { String resp = myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); ourLog.info(resp); - assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); - assertEquals("Unable to validate code http://acme.org#8452-5 - Concept Display \"Old Systolic blood pressure.inspiration - expiration\" does not match expected \"Systolic blood pressure.inspiration - expiration\" for CodeSystem: http://acme.org", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString()); + assertTrue(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); + assertEquals("Concept Display \"Old Systolic blood pressure.inspiration - expiration\" does not match expected \"Systolic blood pressure.inspiration - expiration\"", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString()); } @Test diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetNoVerCSNoVerTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetNoVerCSNoVerTest.java index 76e917ae3f42..3ac4d4ed784e 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetNoVerCSNoVerTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetNoVerCSNoVerTest.java @@ -14,6 +14,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test; +import ca.uhn.fhir.jpa.provider.ValueSetOperationProvider; import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; @@ -1294,14 +1295,14 @@ public void testValidateCodeAgainstBuiltInSystem() { String resp = myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); ourLog.info(resp); - assertEquals("result", respParam.getParameter().get(0).getName()); + assertEquals(ValueSetOperationProvider.RESULT, respParam.getParameter().get(0).getName()); assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue()); - assertEquals("message", respParam.getParameter().get(1).getName()); - assertEquals("Code was validated against in-memory expansion of ValueSet: http://hl7.org/fhir/ValueSet/administrative-gender", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); + assertEquals(ValueSetOperationProvider.DISPLAY, respParam.getParameter().get(1).getName()); + assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); - assertEquals("display", respParam.getParameter().get(2).getName()); - assertEquals("Male", ((StringType) respParam.getParameter().get(2).getValue()).getValue()); + assertEquals(ValueSetOperationProvider.SOURCE_DETAILS, respParam.getParameter().get(2).getName()); + assertEquals("Code was validated against in-memory expansion of ValueSet: http://hl7.org/fhir/ValueSet/administrative-gender", ((StringType) respParam.getParameter().get(2).getValue()).getValue()); } @Test diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/term/ValueSetExpansionR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/term/ValueSetExpansionR4Test.java index fe4f2a22ae56..bc22114e25f1 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/term/ValueSetExpansionR4Test.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/term/ValueSetExpansionR4Test.java @@ -466,7 +466,7 @@ public void testExpandNonPersistedValueSet() { String code = "male"; IValidationSupport.CodeValidationResult outcome = myValueSetDao.validateCode(new CodeType(valueSetUrl), null, new CodeType(code), new CodeType(codeSystemUrl), null, null, null, mySrd); assertTrue(outcome.isOk()); - assertEquals("Code was validated against in-memory expansion of ValueSet: http://hl7.org/fhir/ValueSet/administrative-gender", outcome.getMessage()); + assertEquals("Code was validated against in-memory expansion of ValueSet: http://hl7.org/fhir/ValueSet/administrative-gender", outcome.getSourceDetails()); // Validate Code - Bad code = "AAA"; @@ -1635,10 +1635,11 @@ public void testExpandValueSet_VsIsEnumeratedWithVersionedSystem_CsOnlyDifferent code = "28571000087109"; display = "BLAH"; outcome = myValueSetDao.validateCode(null, vsId, new CodeType(code), new UriType(codeSystemUrl), new StringType(display), null, null, mySrd); - assertFalse(outcome.isOk()); - assertEquals(null, outcome.getCode()); + assertTrue(outcome.isOk()); + assertEquals("28571000087109", outcome.getCode()); assertEquals("MODERNA COVID-19 mRNA-1273", outcome.getDisplay()); assertEquals("Concept Display \"BLAH\" does not match expected \"MODERNA COVID-19 mRNA-1273\" for in-memory expansion of ValueSet: http://ehealthontario.ca/fhir/ValueSet/vaccinecode", outcome.getMessage()); + assertEquals("Code was validated against in-memory expansion of ValueSet: http://ehealthontario.ca/fhir/ValueSet/vaccinecode", outcome.getSourceDetails()); assertEquals("0.17", outcome.getCodeSystemVersion()); // Validate code - good code, good display @@ -1680,11 +1681,11 @@ public void testExpandValueSet_VsIsEnumeratedWithVersionedSystem_CsOnlyDifferent code = "28571000087109"; display = "BLAH"; outcome = myValueSetDao.validateCode(null, vsId, new CodeType(code), new UriType(codeSystemUrl), new StringType(display), null, null, mySrd); - assertFalse(outcome.isOk()); - assertEquals(null, outcome.getCode()); + assertTrue(outcome.isOk()); + assertEquals("28571000087109", outcome.getCode()); assertEquals("MODERNA COVID-19 mRNA-1273", outcome.getDisplay()); assertEquals("0.17", outcome.getCodeSystemVersion()); - assertThat(outcome.getMessage(), containsString("Unable to validate code http://snomed.info/sct#28571000087109 - Concept Display \"BLAH\" does not match expected \"MODERNA COVID-19 mRNA-1273\" for CodeSystem: http://snomed.info/sct - Code validation occurred using a ValueSet expansion that was pre-calculated at")); + assertEquals("Concept Display \"BLAH\" does not match expected \"MODERNA COVID-19 mRNA-1273\"", outcome.getMessage()); // Validate code - good code, good display codeSystemUrl = "http://snomed.info/sct"; diff --git a/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoR5ValueSetTest.java b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoR5ValueSetTest.java index ac5d9acc2830..d27a8972181f 100644 --- a/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoR5ValueSetTest.java +++ b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoR5ValueSetTest.java @@ -117,7 +117,7 @@ public void testValidateCodeOperationByIdentifierAndCodeAndSystemAndBadDisplay() Coding coding = null; CodeableConcept codeableConcept = null; IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); - assertFalse(result.isOk()); + assertTrue(result.isOk()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Concept Display \"Systolic blood pressure at First encounterXXXX\" does not match expected \"Systolic blood pressure at First encounter\" for in-memory expansion of ValueSet: http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2", result.getMessage()); } diff --git a/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java index 37829a81f711..6d9b8a299c79 100644 --- a/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java +++ b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java @@ -12,6 +12,7 @@ import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum; import ca.uhn.fhir.jpa.model.dao.JpaPid; import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.provider.ValueSetOperationProvider; import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -1228,14 +1229,14 @@ public void testValidateCodeAgainstBuiltInSystem() { String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); ourLog.info(resp); - assertEquals("result", respParam.getParameter().get(0).getName()); + assertEquals(ValueSetOperationProvider.RESULT, respParam.getParameter().get(0).getName()); assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue()); - assertEquals("message", respParam.getParameter().get(1).getName()); - assertEquals("Code was validated against in-memory expansion of ValueSet: http://hl7.org/fhir/ValueSet/administrative-gender", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); + assertEquals(ValueSetOperationProvider.DISPLAY, respParam.getParameter().get(1).getName()); + assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); - assertEquals("display", respParam.getParameter().get(2).getName()); - assertEquals("Male", ((StringType) respParam.getParameter().get(2).getValue()).getValue()); + assertEquals(ValueSetOperationProvider.SOURCE_DETAILS, respParam.getParameter().get(2).getName()); + assertEquals("Code was validated against in-memory expansion of ValueSet: http://hl7.org/fhir/ValueSet/administrative-gender", ((StringType) respParam.getParameter().get(2).getValue()).getValue()); } // Good code and system, but not in specified valueset 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 0e72a2c70fe3..35877fe4e0c2 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 @@ -530,7 +530,7 @@ private CodeValidationResult validateCodeInExpandedValueSet( theCodeToValidate, theDisplayToValidate, nextExpansionCode.getDisplay(), - codeSystemResourceVersion, + csVersion, messageAppend); if (isNotBlank(theValueSetUrl)) { populateSourceDetailsForInMemoryExpansion(theValueSetUrl, codeValidationResult); diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java index 5b939bbab29d..5722d23c1199 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java @@ -790,7 +790,7 @@ public void testValidateUsingDifferentialProfile() throws IOException { Patient resource = loadResource("/dstu3/nl/nl-core-patient-01.json", Patient.class); ValidationResult results = myVal.validateWithResult(resource); List outcome = logResultsAndReturnNonInformationalOnes(results); - assertThat(outcome.toString(), containsString("Could not confirm that the codes provided are in the value set 'LandGBACodelijst'")); + assertThat(outcome.toString(), containsString("The Coding provided (urn:oid:2.16.840.1.113883.2.4.4.16.34#6030) is not in the value set 'LandGBACodelijst'")); } private void loadNL() throws IOException { From 37c222ede788ee7c6175c3b81c18714beeb57443 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Sat, 16 Sep 2023 16:39:57 -0400 Subject: [PATCH 06/10] Make display validation level configurable --- .../jpa/config/ValidationSupportConfig.java | 9 + .../ca/uhn/fhir/jpa/term/TermReadSvcImpl.java | 15 +- .../validation/JpaValidationSupportChain.java | 5 +- .../extractor/SearchParamExtractorR4.java | 1 + .../dao/r4/FhirResourceDaoR4ValidateTest.java | 70 +- .../stresstest/GiantTransactionPerfTest.java | 3 - .../jpa/api/config/JpaStorageSettings.java | 41 + ...oryTerminologyServerValidationSupport.java | 2571 +++++++++-------- 8 files changed, 1424 insertions(+), 1291 deletions(-) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/ValidationSupportConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/ValidationSupportConfig.java index 3cbd8bc5ddf2..4d8cef8db752 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/ValidationSupportConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/ValidationSupportConfig.java @@ -23,6 +23,7 @@ import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.dao.JpaPersistedResourceValidationSupport; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain; @@ -30,6 +31,7 @@ import ca.uhn.fhir.jpa.validation.ValidatorResourceFetcher; import ca.uhn.fhir.validation.IInstanceValidatorModule; import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport; import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.common.hapi.validation.validator.HapiToHl7OrgDstu2ValidatingSupportWrapper; @@ -45,6 +47,13 @@ public DefaultProfileValidationSupport defaultProfileValidationSupport(FhirConte return new DefaultProfileValidationSupport(theFhirContext); } + @Bean + public InMemoryTerminologyServerValidationSupport inMemoryTerminologyServerValidationSupport(FhirContext theFhirContext, JpaStorageSettings theStorageSettings) { + InMemoryTerminologyServerValidationSupport retVal = new InMemoryTerminologyServerValidationSupport(theFhirContext); + retVal.setIssueSeverityForCodeDisplayMismatch(theStorageSettings.getIssueSeverityForCodeDisplayMismatch()); + return retVal; + } + @Bean(name = JpaConfig.JPA_VALIDATION_SUPPORT_CHAIN) public JpaValidationSupportChain jpaValidationSupportChain(FhirContext theFhirContext) { return new JpaValidationSupportChain(theFhirContext); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java index e984fa2fa57d..ec4f3c9d4110 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java @@ -295,6 +295,9 @@ public class TermReadSvcImpl implements ITermReadSvc, IHasScheduledJobs { @Autowired private IJpaStorageResourceParser myJpaStorageResourceParser; + @Autowired + private InMemoryTerminologyServerValidationSupport myInMemoryTerminologyServerValidationSupport; + @Override public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) { TermCodeSystemVersionDetails cs = getCurrentCodeSystemVersion(theSystem); @@ -1025,7 +1028,7 @@ private void expandValueSetHandleIncludeOrExclude( new VersionConvertor_40_50(new BaseAdvisor_40_50()), "ValueSet"); org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent includeOrExclude = ValueSet40_50.convertConceptSetComponent(theIncludeOrExclude); - new InMemoryTerminologyServerValidationSupport(myContext) + myInMemoryTerminologyServerValidationSupport .expandValueSetIncludeOrExclude( new ValidationSupportContext(provideValidationSupport()), consumer, @@ -2073,8 +2076,8 @@ protected IValidationSupport.CodeValidationResult validateCodeIsInPreExpandedVal } String expectedDisplay = concepts.get(0).getDisplay(); - return InMemoryTerminologyServerValidationSupport.createWarningForDisplayMismatch( - myContext, theCode, theDisplay, expectedDisplay, systemVersion); + return InMemoryTerminologyServerValidationSupport.createResultForDisplayMismatch( + myContext, theCode, theDisplay, expectedDisplay, systemVersion, myStorageSettings.getIssueSeverityForCodeDisplayMismatch()); } if (!concepts.isEmpty()) { @@ -2709,8 +2712,8 @@ public IValidationSupport.CodeValidationResult validateCode( || code.getDisplay().equals(theDisplay)) { return new CodeValidationResult().setCode(code.getCode()).setDisplay(code.getDisplay()); } else { - return InMemoryTerminologyServerValidationSupport.createWarningForDisplayMismatch( - myContext, theCode, theDisplay, code.getDisplay(), code.getSystemVersion()); + return InMemoryTerminologyServerValidationSupport.createResultForDisplayMismatch( + myContext, theCode, theDisplay, code.getDisplay(), code.getSystemVersion(), myStorageSettings.getIssueSeverityForCodeDisplayMismatch()); } } @@ -2748,7 +2751,7 @@ IValidationSupport.CodeValidationResult validateCodeInValueSet( if (retVal == null) { if (valueSet != null) { - retVal = new InMemoryTerminologyServerValidationSupport(myContext) + retVal = myInMemoryTerminologyServerValidationSupport .validateCodeInValueSet( theValidationSupportContext, theValidationOptions, diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChain.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChain.java index 9a42f1d58738..8f88c3a88ecf 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChain.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaValidationSupportChain.java @@ -59,6 +59,9 @@ public class JpaValidationSupportChain extends ValidationSupportChain { @Autowired private UnknownCodeSystemWarningValidationSupport myUnknownCodeSystemWarningValidationSupport; + @Autowired + private InMemoryTerminologyServerValidationSupport myInMemoryTerminologyServerValidationSupport; + /** * Constructor */ @@ -82,7 +85,7 @@ public void postConstruct() { addValidationSupport(myJpaValidationSupport); addValidationSupport(myTerminologyService); addValidationSupport(new SnapshotGeneratingValidationSupport(myFhirContext)); - addValidationSupport(new InMemoryTerminologyServerValidationSupport(myFhirContext)); + addValidationSupport(myInMemoryTerminologyServerValidationSupport); addValidationSupport(myNpmJpaValidationSupport); addValidationSupport(new CommonCodeSystemsTerminologyService(myFhirContext)); addValidationSupport(myConceptMappingSvc); diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java index 32a22686730c..262bfeecb98e 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.searchparam.extractor; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.StorageSettings; import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java index 7258d728f83d..8bdf7f95a9b7 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java @@ -34,6 +34,7 @@ import ca.uhn.fhir.validation.ResultSeverityEnum; import ca.uhn.fhir.validation.ValidationResult; import org.apache.commons.io.IOUtils; +import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport; import org.hl7.fhir.common.hapi.validation.support.UnknownCodeSystemWarningValidationSupport; import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; @@ -50,6 +51,7 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.util.AopTestUtils; @@ -87,15 +89,21 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test { private ValidationSettings myValidationSettings; @Autowired private UnknownCodeSystemWarningValidationSupport myUnknownCodeSystemWarningValidationSupport; + @Autowired + private InMemoryTerminologyServerValidationSupport myInMemoryTerminologyServerValidationSupport; @AfterEach public void after() { FhirInstanceValidator val = AopTestUtils.getTargetObject(myValidatorModule); val.setBestPracticeWarningLevel(BestPracticeWarningLevel.Warning); - myStorageSettings.setAllowExternalReferences(new JpaStorageSettings().isAllowExternalReferences()); - myStorageSettings.setMaximumExpansionSize(JpaStorageSettings.DEFAULT_MAX_EXPANSION_SIZE); - myStorageSettings.setPreExpandValueSets(new JpaStorageSettings().isPreExpandValueSets()); + JpaStorageSettings defaults = new JpaStorageSettings(); + myStorageSettings.setAllowExternalReferences(defaults.isAllowExternalReferences()); + myStorageSettings.setMaximumExpansionSize(defaults.getMaximumExpansionSize()); + myStorageSettings.setPreExpandValueSets(defaults.isPreExpandValueSets()); + myStorageSettings.setIssueSeverityForCodeDisplayMismatch(defaults.getIssueSeverityForCodeDisplayMismatch()); + + myInMemoryTerminologyServerValidationSupport.setIssueSeverityForCodeDisplayMismatch(defaults.getIssueSeverityForCodeDisplayMismatch()); TermReadSvcImpl.setInvokeOnNextCallForUnitTest(null); @@ -1997,8 +2005,18 @@ public void testValidateUsingDifferentialProfile() throws IOException { } @ParameterizedTest - @ValueSource(booleans = {false, true}) - public void testValidateWrongDisplayOnRequiredBinding(boolean thePreCalculateExpansion) { + @CsvSource(value = { + "INFORMATION, false", + "INFORMATION, true", + "WARNING, false", + "WARNING, true", + "ERROR, false", + "ERROR, true", + }) + public void testValidateWrongDisplayOnRequiredBinding(IValidationSupport.IssueSeverity theDisplayCodeMismatchIssueSeverity, boolean thePreCalculateExpansion) { + myStorageSettings.setIssueSeverityForCodeDisplayMismatch(theDisplayCodeMismatchIssueSeverity); + myInMemoryTerminologyServerValidationSupport.setIssueSeverityForCodeDisplayMismatch(theDisplayCodeMismatchIssueSeverity); + StructureDefinition sd = new StructureDefinition(); sd.setUrl("http://profile"); sd.setStatus(Enumerations.PublicationStatus.ACTIVE); @@ -2053,21 +2071,35 @@ public void testValidateWrongDisplayOnRequiredBinding(boolean thePreCalculateExp OperationOutcome oo = (OperationOutcome) outcome.getOperationOutcome(); ourLog.info("Outcome: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo).replace("\"resourceType\"", "\"resType\"")); - assertEquals(1, oo.getIssue().size()); - assertThat(oo.getIssue().get(0).getDiagnostics(), - containsString("Concept Display \"Body height2\" does not match expected \"Body Height\"")); - if (!thePreCalculateExpansion) { - assertThat(oo.getIssue().get(0).getDiagnostics(), - containsString("for in-memory expansion of ValueSet: http://vs")); + OperationOutcome.OperationOutcomeIssueComponent badDisplayIssue; + if (theDisplayCodeMismatchIssueSeverity == IValidationSupport.IssueSeverity.ERROR) { + + assertEquals(2, oo.getIssue().size()); + badDisplayIssue = oo.getIssue().get(1); + + OperationOutcome.OperationOutcomeIssueComponent noGoodCodings = oo.getIssue().get(0); + assertEquals("error", noGoodCodings.getSeverity().toCode()); + assertEquals("None of the codings provided are in the value set 'ValueSet[http://vs]' (http://vs), and a coding from this value set is required) (codes = http://cs#8302-2)", noGoodCodings.getDiagnostics()); + + } else if (theDisplayCodeMismatchIssueSeverity == IValidationSupport.IssueSeverity.WARNING) { + + assertEquals(1, oo.getIssue().size()); + badDisplayIssue = oo.getIssue().get(0); + assertThat(badDisplayIssue.getDiagnostics(), + containsString("Concept Display \"Body height2\" does not match expected \"Body Height\"")); + assertEquals(OperationOutcome.IssueType.PROCESSING, badDisplayIssue.getCode()); + assertEquals(theDisplayCodeMismatchIssueSeverity.name().toLowerCase(), badDisplayIssue.getSeverity().toCode()); + + } else { + + assertEquals(1, oo.getIssue().size()); + badDisplayIssue = oo.getIssue().get(0); + assertThat(badDisplayIssue.getDiagnostics(), + containsString("No issues detected during validation")); + assertEquals(OperationOutcome.IssueType.INFORMATIONAL, badDisplayIssue.getCode()); + assertEquals(theDisplayCodeMismatchIssueSeverity.name().toLowerCase(), badDisplayIssue.getSeverity().toCode()); + } - assertEquals(11, ((IntegerType)oo.getIssue().get(0).getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-line").getValue()).getValue()); - assertEquals(4, ((IntegerType)oo.getIssue().get(0).getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-col").getValue()).getValue()); - assertEquals(0, oo.getIssue().get(0).getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-message-id").size()); - assertEquals(OperationOutcome.IssueType.PROCESSING, oo.getIssue().get(0).getCode()); - assertEquals(OperationOutcome.IssueSeverity.WARNING, oo.getIssue().get(0).getSeverity()); - assertEquals(2, oo.getIssue().get(0).getLocation().size()); - assertEquals("Observation.code", oo.getIssue().get(0).getLocation().get(0).getValue()); - assertEquals("Line[11] Col[4]", oo.getIssue().get(0).getLocation().get(1).getValue()); } diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/stresstest/GiantTransactionPerfTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/stresstest/GiantTransactionPerfTest.java index a83494de830a..ef4e4b36e2b7 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/stresstest/GiantTransactionPerfTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/stresstest/GiantTransactionPerfTest.java @@ -30,8 +30,6 @@ import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.model.sched.ISchedulerService; -import ca.uhn.fhir.jpa.model.sched.ScheduledJobDefinition; import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR4; @@ -62,7 +60,6 @@ import org.mockito.Answers; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.quartz.JobKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/config/JpaStorageSettings.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/config/JpaStorageSettings.java index 1101e08db96c..6ee1677fbcfa 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/config/JpaStorageSettings.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/config/JpaStorageSettings.java @@ -19,6 +19,7 @@ */ package ca.uhn.fhir.jpa.api.config; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.jpa.api.model.HistoryCountModeEnum; import ca.uhn.fhir.jpa.api.model.WarmCacheEntry; import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum; @@ -327,6 +328,13 @@ public class JpaStorageSettings extends StorageSettings { */ private boolean myNonResourceDbHistoryEnabled = true; + /** + * @since 7.0.0 + */ + @Nonnull + private IValidationSupport.IssueSeverity myIssueSeverityForCodeDisplayMismatch = IValidationSupport.IssueSeverity.WARNING; + + /** * Constructor */ @@ -2315,6 +2323,39 @@ public boolean isNonResourceDbHistoryEnabled() { return myNonResourceDbHistoryEnabled; } + /** + * This setting controls the validation issue severity to report when a code validation + * finds that the code is present in the given CodeSystem, but the display name being + * validated doesn't match the expected value(s). Defaults to + * {@link ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity#WARNING}. Set this + * value to {@link ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity#INFORMATION} + * if you don't want to see display name validation issues at all in resource validation + * outcomes. + * + * @since 7.0.0 + */ + @Nonnull + public IValidationSupport.IssueSeverity getIssueSeverityForCodeDisplayMismatch() { + return myIssueSeverityForCodeDisplayMismatch; + } + + /** + * This setting controls the validation issue severity to report when a code validation + * finds that the code is present in the given CodeSystem, but the display name being + * validated doesn't match the expected value(s). Defaults to + * {@link ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity#WARNING}. Set this + * value to {@link ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity#INFORMATION} + * if you don't want to see display name validation issues at all in resource validation + * outcomes. + * + * @param theIssueSeverityForCodeDisplayMismatch The severity. Must not be {@literal null}. + * @since 7.0.0 + */ + public void setIssueSeverityForCodeDisplayMismatch(@Nonnull IValidationSupport.IssueSeverity theIssueSeverityForCodeDisplayMismatch) { + Validate.notNull(theIssueSeverityForCodeDisplayMismatch, "theIssueSeverityForCodeDisplayMismatch must not be null"); + myIssueSeverityForCodeDisplayMismatch = theIssueSeverityForCodeDisplayMismatch; + } + public void setNonResourceDbHistoryEnabled(boolean theNonResourceDbHistoryEnabled) { myNonResourceDbHistoryEnabled = theNonResourceDbHistoryEnabled; } 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 35877fe4e0c2..721fa0fbcef0 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 @@ -27,6 +27,8 @@ import org.hl7.fhir.r5.model.Enumerations; import org.hl7.fhir.utilities.validation.ValidationMessage; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -37,8 +39,6 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import static org.apache.commons.lang3.StringUtils.contains; import static org.apache.commons.lang3.StringUtils.defaultString; @@ -56,1264 +56,1311 @@ */ @SuppressWarnings("EnhancedSwitchMigration") public class InMemoryTerminologyServerValidationSupport implements IValidationSupport { - private static final String OUR_PIPE_CHARACTER = "|"; - - private final FhirContext myCtx; - private final VersionCanonicalizer myVersionCanonicalizer; - - public InMemoryTerminologyServerValidationSupport(FhirContext theCtx) { - Validate.notNull(theCtx, "theCtx must not be null"); - myCtx = theCtx; - myVersionCanonicalizer = new VersionCanonicalizer(theCtx); - } - - @Override - public FhirContext getFhirContext() { - return myCtx; - } - - @Override - public ValueSetExpansionOutcome expandValueSet( - ValidationSupportContext theValidationSupportContext, - ValueSetExpansionOptions theExpansionOptions, - @Nonnull IBaseResource theValueSetToExpand) { - return expandValueSet(theValidationSupportContext, theValueSetToExpand, null, null); - } - - private ValueSetExpansionOutcome expandValueSet( - ValidationSupportContext theValidationSupportContext, - IBaseResource theValueSetToExpand, - String theWantSystemAndVersion, - String theWantCode) { - org.hl7.fhir.r5.model.ValueSet expansionR5; - try { - expansionR5 = expandValueSetToCanonical( - theValidationSupportContext, theValueSetToExpand, theWantSystemAndVersion, theWantCode); - } catch (ExpansionCouldNotBeCompletedInternallyException e) { - return new ValueSetExpansionOutcome(e.getMessage()); - } - if (expansionR5 == null) { - return null; - } - - IBaseResource expansion; - switch (myCtx.getVersion().getVersion()) { - case DSTU2: { - org.hl7.fhir.r4.model.ValueSet expansionR4 = (org.hl7.fhir.r4.model.ValueSet) - VersionConvertorFactory_40_50.convertResource(expansionR5, new BaseAdvisor_40_50(false)); - expansion = myVersionCanonicalizer.valueSetFromCanonical(expansionR4); - break; - } - case DSTU2_HL7ORG: { - expansion = VersionConvertorFactory_10_50.convertResource(expansionR5, new BaseAdvisor_10_50(false)); - break; - } - case DSTU3: { - expansion = VersionConvertorFactory_30_50.convertResource(expansionR5, new BaseAdvisor_30_50(false)); - break; - } - case R4: { - expansion = VersionConvertorFactory_40_50.convertResource(expansionR5, new BaseAdvisor_40_50(false)); - break; - } - case R4B: { - expansion = VersionConvertorFactory_43_50.convertResource(expansionR5, new BaseAdvisor_43_50(false)); - break; - } - case R5: { - expansion = expansionR5; - break; - } - case DSTU2_1: - default: - throw new IllegalArgumentException(Msg.code(697) + "Can not handle version: " - + myCtx.getVersion().getVersion()); - } - - return new ValueSetExpansionOutcome(expansion); - } - - private org.hl7.fhir.r5.model.ValueSet expandValueSetToCanonical( - ValidationSupportContext theValidationSupportContext, - IBaseResource theValueSetToExpand, - @Nullable String theWantSystemUrlAndVersion, - @Nullable String theWantCode) - throws ExpansionCouldNotBeCompletedInternallyException { - org.hl7.fhir.r5.model.ValueSet expansionR5; - switch (getFhirVersionEnum( - theValidationSupportContext.getRootValidationSupport().getFhirContext(), theValueSetToExpand)) { - case DSTU2: { - expansionR5 = expandValueSetDstu2( - theValidationSupportContext, - (ca.uhn.fhir.model.dstu2.resource.ValueSet) theValueSetToExpand, - theWantSystemUrlAndVersion, - theWantCode); - break; - } - case DSTU2_HL7ORG: { - expansionR5 = expandValueSetDstu2Hl7Org( - theValidationSupportContext, - (ValueSet) theValueSetToExpand, - theWantSystemUrlAndVersion, - theWantCode); - break; - } - case DSTU3: { - expansionR5 = expandValueSetDstu3( - theValidationSupportContext, - (org.hl7.fhir.dstu3.model.ValueSet) theValueSetToExpand, - theWantSystemUrlAndVersion, - theWantCode); - break; - } - case R4: { - expansionR5 = expandValueSetR4( - theValidationSupportContext, - (org.hl7.fhir.r4.model.ValueSet) theValueSetToExpand, - theWantSystemUrlAndVersion, - theWantCode); - break; - } - case R4B: { - expansionR5 = expandValueSetR4B( - theValidationSupportContext, - (org.hl7.fhir.r4b.model.ValueSet) theValueSetToExpand, - theWantSystemUrlAndVersion, - theWantCode); - break; - } - case R5: { - expansionR5 = expandValueSetR5( - theValidationSupportContext, - (org.hl7.fhir.r5.model.ValueSet) theValueSetToExpand, - theWantSystemUrlAndVersion, - theWantCode); - break; - } - case DSTU2_1: - default: - throw new IllegalArgumentException(Msg.code(698) + "Can not handle version: " - + myCtx.getVersion().getVersion()); - } - - return expansionR5; - } - - @Override - public CodeValidationResult validateCodeInValueSet( - ValidationSupportContext theValidationSupportContext, - ConceptValidationOptions theOptions, - String theCodeSystemUrlAndVersion, - String theCode, - String theDisplay, - @Nonnull IBaseResource theValueSet) { - org.hl7.fhir.r5.model.ValueSet expansion; - String vsUrl = CommonCodeSystemsTerminologyService.getValueSetUrl(getFhirContext(), theValueSet); - try { - expansion = expandValueSetToCanonical( - theValidationSupportContext, theValueSet, theCodeSystemUrlAndVersion, theCode); - } catch (ExpansionCouldNotBeCompletedInternallyException e) { - CodeValidationResult codeValidationResult = new CodeValidationResult(); - codeValidationResult.setSeverityCode("error"); - - String msg = "Failed to expand ValueSet '" + vsUrl + "' (in-memory). Could not validate code " - + theCodeSystemUrlAndVersion + "#" + theCode; - if (e.getMessage() != null) { - msg += ". Error was: " + e.getMessage(); - } - - codeValidationResult.setMessage(msg); - return codeValidationResult; - } - - if (expansion == null) { - return null; - } - - return validateCodeInExpandedValueSet( - theValidationSupportContext, - theOptions, - theCodeSystemUrlAndVersion, - theCode, - theDisplay, - expansion, - vsUrl); - } - - @Override - @Nullable - public CodeValidationResult validateCode( - @Nonnull ValidationSupportContext theValidationSupportContext, - @Nonnull ConceptValidationOptions theOptions, - String theCodeSystem, - String theCode, - String theDisplay, - String theValueSetUrl) { - IBaseResource vs; - if (isNotBlank(theValueSetUrl)) { - vs = theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrl); - if (vs == null) { - 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: - 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))); - break; - case DSTU3: - 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: - 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 R4B: - if (codeSystemVersion != null) { - vs = new org.hl7.fhir.r4b.model.ValueSet() - .setCompose(new org.hl7.fhir.r4b.model.ValueSet.ValueSetComposeComponent() - .addInclude(new org.hl7.fhir.r4b.model.ValueSet.ConceptSetComponent() - .setSystem(codeSystemUrl) - .setVersion(codeSystemVersion))); - } else { - vs = new org.hl7.fhir.r4b.model.ValueSet() - .setCompose(new org.hl7.fhir.r4b.model.ValueSet.ValueSetComposeComponent() - .addInclude(new org.hl7.fhir.r4b.model.ValueSet.ConceptSetComponent() - .setSystem(theCodeSystem))); - } - break; - case R5: - 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_1: - default: - throw new IllegalArgumentException(Msg.code(699) + "Can not handle version: " - + myCtx.getVersion().getVersion()); - } - } - - ValueSetExpansionOutcome valueSetExpansionOutcome = - expandValueSet(theValidationSupportContext, vs, theCodeSystem, theCode); - if (valueSetExpansionOutcome == null) { - return null; - } - - if (valueSetExpansionOutcome.getError() != null) { - return new CodeValidationResult() - .setSeverity(IssueSeverity.ERROR) - .setMessage(valueSetExpansionOutcome.getError()); - } - - IBaseResource expansion = valueSetExpansionOutcome.getValueSet(); - return validateCodeInExpandedValueSet( - theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, expansion, theValueSetUrl); - } - - private CodeValidationResult validateCodeInExpandedValueSet( - ValidationSupportContext theValidationSupportContext, - ConceptValidationOptions theOptions, - String theCodeSystemUrlAndVersionToValidate, - String theCodeToValidate, - String theDisplayToValidate, - IBaseResource theExpansion, - String theValueSetUrl) { - assert theExpansion != null; - - boolean caseSensitive = true; - IBaseResource codeSystemToValidateResource = null; - if (!theOptions.isInferSystem() && isNotBlank(theCodeSystemUrlAndVersionToValidate)) { - codeSystemToValidateResource = theValidationSupportContext - .getRootValidationSupport() - .fetchCodeSystem(theCodeSystemUrlAndVersionToValidate); - } - - List codes = new ArrayList<>(); - switch (getFhirVersionEnum( - theValidationSupportContext.getRootValidationSupport().getFhirContext(), theExpansion)) { - case DSTU2: { - ca.uhn.fhir.model.dstu2.resource.ValueSet expansionVs = - (ca.uhn.fhir.model.dstu2.resource.ValueSet) theExpansion; - List contains = - expansionVs.getExpansion().getContains(); - flattenAndConvertCodesDstu2(contains, codes); - break; - } - case DSTU2_HL7ORG: { - ValueSet expansionVs = (ValueSet) theExpansion; - List contains = - expansionVs.getExpansion().getContains(); - flattenAndConvertCodesDstu2Hl7Org(contains, codes); - 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); - 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); - break; - } - case R4B: { - org.hl7.fhir.r4b.model.ValueSet expansionVs = (org.hl7.fhir.r4b.model.ValueSet) theExpansion; - List contains = - expansionVs.getExpansion().getContains(); - flattenAndConvertCodesR4B(contains, codes); - 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); - break; - } - case DSTU2_1: - default: - throw new IllegalArgumentException(Msg.code(700) + "Can not handle version: " - + myCtx.getVersion().getVersion()); - } - - String codeSystemResourceName = null; - String codeSystemResourceVersion = null; - String codeSystemResourceContentMode = null; - if (codeSystemToValidateResource != null) { - switch (getFhirVersionEnum( - theValidationSupportContext.getRootValidationSupport().getFhirContext(), - codeSystemToValidateResource)) { - case DSTU2: - case DSTU2_HL7ORG: { - caseSensitive = true; - break; - } - case DSTU3: { - org.hl7.fhir.dstu3.model.CodeSystem systemDstu3 = - (org.hl7.fhir.dstu3.model.CodeSystem) codeSystemToValidateResource; - caseSensitive = systemDstu3.getCaseSensitive(); - 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) codeSystemToValidateResource; - caseSensitive = systemR4.getCaseSensitive(); - codeSystemResourceName = systemR4.getName(); - codeSystemResourceVersion = systemR4.getVersion(); - codeSystemResourceContentMode = systemR4.getContentElement().getValueAsString(); - break; - } - case R4B: { - org.hl7.fhir.r4b.model.CodeSystem systemR4B = - (org.hl7.fhir.r4b.model.CodeSystem) codeSystemToValidateResource; - caseSensitive = systemR4B.getCaseSensitive(); - codeSystemResourceName = systemR4B.getName(); - codeSystemResourceVersion = systemR4B.getVersion(); - codeSystemResourceContentMode = - systemR4B.getContentElement().getValueAsString(); - break; - } - case R5: { - CodeSystem systemR5 = (CodeSystem) codeSystemToValidateResource; - caseSensitive = systemR5.getCaseSensitive(); - codeSystemResourceName = systemR5.getName(); - codeSystemResourceVersion = systemR5.getVersion(); - codeSystemResourceContentMode = systemR5.getContentElement().getValueAsString(); - break; - } - case DSTU2_1: - default: - throw new IllegalArgumentException(Msg.code(701) + "Can not handle version: " - + myCtx.getVersion().getVersion()); - } - } - - String codeSystemUrlToValidate = null; - String codeSystemVersionToValidate = null; - if (theCodeSystemUrlAndVersionToValidate != null) { - int versionIndex = theCodeSystemUrlAndVersionToValidate.indexOf("|"); - if (versionIndex > -1) { - codeSystemUrlToValidate = theCodeSystemUrlAndVersionToValidate.substring(0, versionIndex); - codeSystemVersionToValidate = theCodeSystemUrlAndVersionToValidate.substring(versionIndex + 1); - } else { - codeSystemUrlToValidate = theCodeSystemUrlAndVersionToValidate; - } - } - for (FhirVersionIndependentConcept nextExpansionCode : codes) { - - boolean codeMatches; - if (caseSensitive) { - codeMatches = defaultString(theCodeToValidate).equals(nextExpansionCode.getCode()); - } else { - codeMatches = defaultString(theCodeToValidate).equalsIgnoreCase(nextExpansionCode.getCode()); - } - if (codeMatches) { - if (theOptions.isInferSystem() - || (nextExpansionCode.getSystem().equals(codeSystemUrlToValidate) - && (codeSystemVersionToValidate == null - || codeSystemVersionToValidate.equals(nextExpansionCode.getSystemVersion())))) { - String csVersion = codeSystemResourceVersion; - if (isNotBlank(nextExpansionCode.getSystemVersion())) { - csVersion = nextExpansionCode.getSystemVersion(); - } - if (!theOptions.isValidateDisplay() - || (isBlank(nextExpansionCode.getDisplay()) - || isBlank(theDisplayToValidate) - || nextExpansionCode.getDisplay().equals(theDisplayToValidate))) { - CodeValidationResult codeValidationResult = new CodeValidationResult() - .setCode(theCodeToValidate) - .setDisplay(nextExpansionCode.getDisplay()) - .setCodeSystemName(codeSystemResourceName) - .setCodeSystemVersion(csVersion); - if (isNotBlank(theValueSetUrl)) { - populateSourceDetailsForInMemoryExpansion(theValueSetUrl, codeValidationResult); - } - return codeValidationResult; - } else { - String messageAppend = ""; - if (isNotBlank(theValueSetUrl)) { - messageAppend = " for in-memory expansion of ValueSet: " + theValueSetUrl; - } - CodeValidationResult codeValidationResult = createWarningForDisplayMismatch( - myCtx, - theCodeToValidate, - theDisplayToValidate, - nextExpansionCode.getDisplay(), - csVersion, - messageAppend); - if (isNotBlank(theValueSetUrl)) { - populateSourceDetailsForInMemoryExpansion(theValueSetUrl, codeValidationResult); - } - return codeValidationResult; - } - } - } - } - - ValidationMessage.IssueSeverity severity; - String message; - if ("fragment".equals(codeSystemResourceContentMode)) { - severity = ValidationMessage.IssueSeverity.WARNING; - message = "Unknown code in fragment CodeSystem '" - + (isNotBlank(theCodeSystemUrlAndVersionToValidate) - ? theCodeSystemUrlAndVersionToValidate + "#" - : "") - + theCodeToValidate + "'"; - } else { - severity = ValidationMessage.IssueSeverity.ERROR; - message = "Unknown code '" - + (isNotBlank(theCodeSystemUrlAndVersionToValidate) - ? theCodeSystemUrlAndVersionToValidate + "#" - : "") - + theCodeToValidate + "'"; - } - if (isNotBlank(theValueSetUrl)) { - message += " for in-memory expansion of ValueSet '" + theValueSetUrl + "'"; - } - - return new CodeValidationResult().setSeverityCode(severity.toCode()).setMessage(message); - } - - private static void populateSourceDetailsForInMemoryExpansion( - String theValueSetUrl, CodeValidationResult codeValidationResult) { - codeValidationResult.setSourceDetails( - "Code was validated against in-memory expansion of ValueSet: " + theValueSetUrl); - } - - @Override - public LookupCodeResult lookupCode( - ValidationSupportContext theValidationSupportContext, - String theSystem, - String theCode, - String theDisplayLanguage) { - CodeValidationResult codeValidationResult = validateCode( - theValidationSupportContext, new ConceptValidationOptions(), theSystem, theCode, null, null); - if (codeValidationResult == null) { - return null; - } - return codeValidationResult.asLookupCodeResult(theSystem, theCode); - } - - @Nullable - private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2Hl7Org( - ValidationSupportContext theValidationSupportContext, - ValueSet theInput, - @Nullable String theWantSystemUrlAndVersion, - @Nullable String theWantCode) - throws ExpansionCouldNotBeCompletedInternallyException { - org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) - VersionConvertorFactory_10_50.convertResource(theInput, new BaseAdvisor_10_50(false)); - return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode)); - } - - @Nullable - private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2( - ValidationSupportContext theValidationSupportContext, - ca.uhn.fhir.model.dstu2.resource.ValueSet theInput, - @Nullable String theWantSystemUrlAndVersion, - @Nullable String theWantCode) - throws ExpansionCouldNotBeCompletedInternallyException { - IParser parserRi = FhirContext.forCached(FhirVersionEnum.DSTU2_HL7ORG).newJsonParser(); - IParser parserHapi = FhirContext.forDstu2Cached().newJsonParser(); - - 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 = (org.hl7.fhir.r5.model.ValueSet) - VersionConvertorFactory_10_50.convertResource(valueSetRi, new BaseAdvisor_10_50(false)); - return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode)); - } - - @Override - public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) { - if (isBlank(theSystem)) { - return false; - } - - IBaseResource cs = - theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(theSystem); - - if (!myCtx.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU2_1)) { - return cs != null; - } - - if (cs != null) { - IPrimitiveType content = - getFhirContext().newTerser().getSingleValueOrNull(cs, "content", IPrimitiveType.class); - return !"not-present".equals(content.getValueAsString()); - } - - return false; - } - - @Override - public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) { - return isNotBlank(theValueSetUrl) - && theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrl) != null; - } - - private void addCodesDstu2Hl7Org( - List theSourceList, - List theTargetList) { - for (ValueSet.ConceptDefinitionComponent nextSource : theSourceList) { - CodeSystem.ConceptDefinitionComponent targetConcept = new CodeSystem.ConceptDefinitionComponent() - .setCode(nextSource.getCode()) - .setDisplay(nextSource.getDisplay()); - theTargetList.add(targetConcept); - addCodesDstu2Hl7Org(nextSource.getConcept(), targetConcept.getConcept()); - } - } - - private void addCodesDstu2( - List theSourceList, - List theTargetList) { - for (ca.uhn.fhir.model.dstu2.resource.ValueSet.CodeSystemConcept nextSource : theSourceList) { - CodeSystem.ConceptDefinitionComponent targetConcept = new CodeSystem.ConceptDefinitionComponent() - .setCode(nextSource.getCode()) - .setDisplay(nextSource.getDisplay()); - theTargetList.add(targetConcept); - addCodesDstu2(nextSource.getConcept(), targetConcept.getConcept()); - } - } - - @Nullable - private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu3( - ValidationSupportContext theValidationSupportContext, - org.hl7.fhir.dstu3.model.ValueSet theInput, - @Nullable String theWantSystemUrlAndVersion, - @Nullable String theWantCode) - throws ExpansionCouldNotBeCompletedInternallyException { - org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) - VersionConvertorFactory_30_50.convertResource(theInput, new BaseAdvisor_30_50(false)); - return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode)); - } - - @Nullable - private org.hl7.fhir.r5.model.ValueSet expandValueSetR4( - ValidationSupportContext theValidationSupportContext, - org.hl7.fhir.r4.model.ValueSet theInput, - @Nullable String theWantSystemUrlAndVersion, - @Nullable String theWantCode) - throws ExpansionCouldNotBeCompletedInternallyException { - org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) - VersionConvertorFactory_40_50.convertResource(theInput, new BaseAdvisor_40_50(false)); - return expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode); - } - - @Nullable - private org.hl7.fhir.r5.model.ValueSet expandValueSetR4B( - ValidationSupportContext theValidationSupportContext, - org.hl7.fhir.r4b.model.ValueSet theInput, - @Nullable String theWantSystemUrlAndVersion, - @Nullable String theWantCode) - throws ExpansionCouldNotBeCompletedInternallyException { - org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) - VersionConvertorFactory_43_50.convertResource(theInput, new BaseAdvisor_43_50(false)); - return expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode); - } - - @Nullable - private org.hl7.fhir.r5.model.ValueSet expandValueSetR5( - ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r5.model.ValueSet theInput) - throws ExpansionCouldNotBeCompletedInternallyException { - return expandValueSetR5(theValidationSupportContext, theInput, null, null); - } - - @Nullable - private org.hl7.fhir.r5.model.ValueSet expandValueSetR5( - ValidationSupportContext theValidationSupportContext, - org.hl7.fhir.r5.model.ValueSet theInput, - @Nullable String theWantSystemUrlAndVersion, - @Nullable String theWantCode) - throws ExpansionCouldNotBeCompletedInternallyException { - Set concepts = new HashSet<>(); - - expandValueSetR5IncludeOrExcludes( - theValidationSupportContext, - concepts, - theInput.getCompose().getInclude(), - true, - theWantSystemUrlAndVersion, - theWantCode); - expandValueSetR5IncludeOrExcludes( - theValidationSupportContext, - concepts, - theInput.getCompose().getExclude(), - false, - theWantSystemUrlAndVersion, - theWantCode); - - org.hl7.fhir.r5.model.ValueSet retVal = new org.hl7.fhir.r5.model.ValueSet(); - for (FhirVersionIndependentConcept next : concepts) { - org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent contains = - retVal.getExpansion().addContains(); - contains.setSystem(next.getSystem()); - contains.setCode(next.getCode()); - contains.setDisplay(next.getDisplay()); - contains.setVersion(next.getSystemVersion()); - } - - return retVal; - } - - /** - * Use with caution - this is not a stable API - * - * @since 5.6.0 - */ - public void expandValueSetIncludeOrExclude( - ValidationSupportContext theValidationSupportContext, - Consumer theConsumer, - org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent theIncludeOrExclude) - throws ExpansionCouldNotBeCompletedInternallyException { - expandValueSetR5IncludeOrExclude(theValidationSupportContext, theConsumer, null, null, theIncludeOrExclude); - } - - private void expandValueSetR5IncludeOrExcludes( - ValidationSupportContext theValidationSupportContext, - Set theConcepts, - List theComposeList, - boolean theComposeListIsInclude, - @Nullable String theWantSystemUrlAndVersion, - @Nullable String theWantCode) - throws ExpansionCouldNotBeCompletedInternallyException { - Consumer consumer = c -> { - if (theComposeListIsInclude) { - theConcepts.add(c); - } else { - theConcepts.remove(c); - } - }; - expandValueSetR5IncludeOrExcludes( - theValidationSupportContext, consumer, theComposeList, theWantSystemUrlAndVersion, theWantCode); - } - - private void expandValueSetR5IncludeOrExcludes( - ValidationSupportContext theValidationSupportContext, - Consumer theConsumer, - List theComposeList, - @Nullable String theWantSystemUrlAndVersion, - @Nullable String theWantCode) - throws ExpansionCouldNotBeCompletedInternallyException { - ExpansionCouldNotBeCompletedInternallyException caughtException = null; - for (org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent nextInclude : theComposeList) { - try { - boolean outcome = expandValueSetR5IncludeOrExclude( - theValidationSupportContext, theConsumer, theWantSystemUrlAndVersion, theWantCode, nextInclude); - if (isNotBlank(theWantCode)) { - if (outcome) { - return; - } - } - } catch (ExpansionCouldNotBeCompletedInternallyException e) { - if (isBlank(theWantCode)) { - throw e; - } else { - caughtException = e; - } - } - } - if (caughtException != null) { - throw caughtException; - } - } - - /** - * Returns true if at least one code was added - */ - private boolean expandValueSetR5IncludeOrExclude( - ValidationSupportContext theValidationSupportContext, - Consumer theConsumer, - @Nullable String theWantSystemUrlAndVersion, - @Nullable String theWantCode, - org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent theInclude) - throws ExpansionCouldNotBeCompletedInternallyException { - - String wantSystemUrl = null; - String wantSystemVersion = null; - - if (theWantSystemUrlAndVersion != null) { - int versionIndex = theWantSystemUrlAndVersion.indexOf(OUR_PIPE_CHARACTER); - if (versionIndex > -1) { - wantSystemUrl = theWantSystemUrlAndVersion.substring(0, versionIndex); - wantSystemVersion = theWantSystemUrlAndVersion.substring(versionIndex + 1); - } else { - wantSystemUrl = theWantSystemUrlAndVersion; - } - } - - String includeOrExcludeConceptSystemUrl = theInclude.getSystem(); - String includeOrExcludeConceptSystemVersion = theInclude.getVersion(); - - Function codeSystemLoader = newCodeSystemLoader(theValidationSupportContext); - Function valueSetLoader = - newValueSetLoader(theValidationSupportContext); - - List nextCodeList = new ArrayList<>(); - CodeSystem includeOrExcludeSystemResource = null; - - if (isNotBlank(includeOrExcludeConceptSystemUrl)) { - - includeOrExcludeConceptSystemVersion = optionallyPopulateVersionFromUrl( - includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion); - includeOrExcludeConceptSystemUrl = substringBefore(includeOrExcludeConceptSystemUrl, OUR_PIPE_CHARACTER); - - if (wantSystemUrl != null && !wantSystemUrl.equals(includeOrExcludeConceptSystemUrl)) { - return false; - } - - if (wantSystemVersion != null && !wantSystemVersion.equals(includeOrExcludeConceptSystemVersion)) { - return false; - } - - String loadedCodeSystemUrl; - if (includeOrExcludeConceptSystemVersion != null) { - loadedCodeSystemUrl = - includeOrExcludeConceptSystemUrl + OUR_PIPE_CHARACTER + includeOrExcludeConceptSystemVersion; - } else { - loadedCodeSystemUrl = includeOrExcludeConceptSystemUrl; - } - - includeOrExcludeSystemResource = codeSystemLoader.apply(loadedCodeSystemUrl); - - Set wantCodes; - if (theInclude.getConcept().isEmpty()) { - wantCodes = null; - } else { - wantCodes = - theInclude.getConcept().stream().map(t -> t.getCode()).collect(Collectors.toSet()); - } - - boolean ableToHandleCode = false; - String failureMessage = null; - FailureType failureType = FailureType.OTHER; - - if (includeOrExcludeSystemResource == null - || includeOrExcludeSystemResource.getContent() == Enumerations.CodeSystemContentMode.NOTPRESENT) { - - if (theWantCode != null) { - if (theValidationSupportContext - .getRootValidationSupport() - .isCodeSystemSupported(theValidationSupportContext, includeOrExcludeConceptSystemUrl)) { - LookupCodeResult lookup = theValidationSupportContext - .getRootValidationSupport() - .lookupCode( - theValidationSupportContext, - includeOrExcludeConceptSystemUrl, - theWantCode, - null); - if (lookup != null) { - ableToHandleCode = true; - if (lookup.isFound()) { - CodeSystem.ConceptDefinitionComponent conceptDefinition = - new CodeSystem.ConceptDefinitionComponent() - .addConcept() - .setCode(theWantCode) - .setDisplay(lookup.getCodeDisplay()); - List codesList = - Collections.singletonList(conceptDefinition); - addCodes( - includeOrExcludeConceptSystemUrl, - includeOrExcludeConceptSystemVersion, - codesList, - nextCodeList, - wantCodes); - } - } - } else { - - /* - * If we're doing an expansion specifically looking for a single code, that means we're validating that code. - * In the case where we have a ValueSet that explicitly enumerates a collection of codes - * (via ValueSet.compose.include.code) in a code system that is unknown we'll assume the code is valid - * even if we can't find the CodeSystem. This is a compromise obviously, since it would be ideal for - * CodeSystems to always be known, but realistically there are always going to be CodeSystems that - * can't be supplied because of copyright issues, or because they are grammar based. Allowing a VS to - * enumerate a set of good codes for them is a nice compromise there. - */ - if (Objects.equals(theInclude.getSystem(), theWantSystemUrlAndVersion)) { - Optional - matchingEnumeratedConcept = theInclude.getConcept().stream() - .filter(t -> Objects.equals(t.getCode(), theWantCode)) - .findFirst(); - - // If the ValueSet.compose.include has no individual concepts in it, and - // we can't find the actual referenced CodeSystem, we have no choice - // but to fail - if (!theInclude.getConcept().isEmpty()) { - ableToHandleCode = true; - } else { - failureMessage = getFailureMessageForMissingOrUnusableCodeSystem( - includeOrExcludeSystemResource, loadedCodeSystemUrl); - } - - if (matchingEnumeratedConcept.isPresent()) { - CodeSystem.ConceptDefinitionComponent conceptDefinition = - new CodeSystem.ConceptDefinitionComponent() - .addConcept() - .setCode(theWantCode) - .setDisplay(matchingEnumeratedConcept - .get() - .getDisplay()); - List codesList = - Collections.singletonList(conceptDefinition); - addCodes( - includeOrExcludeConceptSystemUrl, - includeOrExcludeConceptSystemVersion, - codesList, - nextCodeList, - wantCodes); - } - } - } - } else { - if (isNotBlank(theInclude.getSystem()) - && !theInclude.getConcept().isEmpty() - && theInclude.getFilter().isEmpty() - && theInclude.getValueSet().isEmpty()) { - theInclude.getConcept().stream() - .map(t -> new FhirVersionIndependentConcept( - theInclude.getSystem(), t.getCode(), t.getDisplay(), theInclude.getVersion())) - .forEach(t -> nextCodeList.add(t)); - ableToHandleCode = true; - } - - if (!ableToHandleCode) { - failureMessage = getFailureMessageForMissingOrUnusableCodeSystem( - includeOrExcludeSystemResource, loadedCodeSystemUrl); - } - } - - } else { - ableToHandleCode = true; - } - - if (!ableToHandleCode) { - if (includeOrExcludeSystemResource == null && failureMessage == null) { - failureMessage = getFailureMessageForMissingOrUnusableCodeSystem( - includeOrExcludeSystemResource, loadedCodeSystemUrl); - } - - if (includeOrExcludeSystemResource == null) { - failureType = FailureType.UNKNOWN_CODE_SYSTEM; - } - - throw new ExpansionCouldNotBeCompletedInternallyException(Msg.code(702) + failureMessage, failureType); - } - - if (includeOrExcludeSystemResource != null - && includeOrExcludeSystemResource.getContent() != Enumerations.CodeSystemContentMode.NOTPRESENT) { - addCodes( - includeOrExcludeConceptSystemUrl, - includeOrExcludeConceptSystemVersion, - includeOrExcludeSystemResource.getConcept(), - nextCodeList, - wantCodes); - } - } - - for (CanonicalType nextValueSetInclude : theInclude.getValueSet()) { - org.hl7.fhir.r5.model.ValueSet vs = valueSetLoader.apply(nextValueSetInclude.getValueAsString()); - if (vs != null) { - org.hl7.fhir.r5.model.ValueSet subExpansion = - expandValueSetR5(theValidationSupportContext, vs, theWantSystemUrlAndVersion, theWantCode); - if (subExpansion == null) { - throw new ExpansionCouldNotBeCompletedInternallyException( - Msg.code(703) + "Failed to expand ValueSet: " + nextValueSetInclude.getValueAsString(), - FailureType.OTHER); - } - for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : - subExpansion.getExpansion().getContains()) { - nextCodeList.add(new FhirVersionIndependentConcept( - next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); - } - } - } - - boolean retVal = false; - - for (FhirVersionIndependentConcept next : nextCodeList) { - if (includeOrExcludeSystemResource != null && theWantCode != null) { - boolean matches; - if (includeOrExcludeSystemResource.getCaseSensitive()) { - matches = theWantCode.equals(next.getCode()); - } else { - matches = theWantCode.equalsIgnoreCase(next.getCode()); - } - if (!matches) { - continue; - } - } - - theConsumer.accept(next); - retVal = true; - } - - return retVal; - } - - private Function newValueSetLoader( - ValidationSupportContext theValidationSupportContext) { - switch (myCtx.getVersion().getVersion()) { - case DSTU2: - case DSTU2_HL7ORG: - return t -> { - IBaseResource vs = theValidationSupportContext - .getRootValidationSupport() - .fetchValueSet(t); - if (vs instanceof ca.uhn.fhir.model.dstu2.resource.ValueSet) { - IParser parserRi = FhirContext.forCached(FhirVersionEnum.DSTU2_HL7ORG) - .newJsonParser(); - IParser parserHapi = FhirContext.forDstu2Cached().newJsonParser(); - ca.uhn.fhir.model.dstu2.resource.ValueSet valueSet = - (ca.uhn.fhir.model.dstu2.resource.ValueSet) vs; - org.hl7.fhir.dstu2.model.ValueSet valueSetRi = parserRi.parseResource( - org.hl7.fhir.dstu2.model.ValueSet.class, parserHapi.encodeResourceToString(valueSet)); - return (org.hl7.fhir.r5.model.ValueSet) - VersionConvertorFactory_10_50.convertResource(valueSetRi, new BaseAdvisor_10_50(false)); - } else { - org.hl7.fhir.dstu2.model.ValueSet valueSet = - (org.hl7.fhir.dstu2.model.ValueSet) theValidationSupportContext - .getRootValidationSupport() - .fetchValueSet(t); - return (org.hl7.fhir.r5.model.ValueSet) - VersionConvertorFactory_10_50.convertResource(valueSet, new BaseAdvisor_10_50(false)); - } - }; - case DSTU3: - return t -> { - org.hl7.fhir.dstu3.model.ValueSet valueSet = - (org.hl7.fhir.dstu3.model.ValueSet) theValidationSupportContext - .getRootValidationSupport() - .fetchValueSet(t); - return (org.hl7.fhir.r5.model.ValueSet) - VersionConvertorFactory_30_50.convertResource(valueSet, new BaseAdvisor_30_50(false)); - }; - case R4: - return t -> { - org.hl7.fhir.r4.model.ValueSet valueSet = - (org.hl7.fhir.r4.model.ValueSet) theValidationSupportContext - .getRootValidationSupport() - .fetchValueSet(t); - return (org.hl7.fhir.r5.model.ValueSet) - VersionConvertorFactory_40_50.convertResource(valueSet, new BaseAdvisor_40_50(false)); - }; - case R4B: - return t -> { - org.hl7.fhir.r4b.model.ValueSet valueSet = - (org.hl7.fhir.r4b.model.ValueSet) theValidationSupportContext - .getRootValidationSupport() - .fetchValueSet(t); - return (org.hl7.fhir.r5.model.ValueSet) - VersionConvertorFactory_43_50.convertResource(valueSet, new BaseAdvisor_43_50(false)); - }; - default: - case DSTU2_1: - case R5: - return t -> (org.hl7.fhir.r5.model.ValueSet) - theValidationSupportContext.getRootValidationSupport().fetchValueSet(t); - } - } - - private Function newCodeSystemLoader(ValidationSupportContext theValidationSupportContext) { - switch (myCtx.getVersion().getVersion()) { - case DSTU2: - case DSTU2_HL7ORG: - return t -> { - IBaseResource codeSystem = theValidationSupportContext - .getRootValidationSupport() - .fetchCodeSystem(t); - CodeSystem retVal = null; - if (codeSystem != null) { - retVal = new CodeSystem(); - if (codeSystem instanceof ca.uhn.fhir.model.dstu2.resource.ValueSet) { - ca.uhn.fhir.model.dstu2.resource.ValueSet codeSystemCasted = - (ca.uhn.fhir.model.dstu2.resource.ValueSet) codeSystem; - retVal.setUrl(codeSystemCasted.getUrl()); - addCodesDstu2(codeSystemCasted.getCodeSystem().getConcept(), retVal.getConcept()); - } else { - org.hl7.fhir.dstu2.model.ValueSet codeSystemCasted = - (org.hl7.fhir.dstu2.model.ValueSet) codeSystem; - retVal.setUrl(codeSystemCasted.getUrl()); - addCodesDstu2Hl7Org(codeSystemCasted.getCodeSystem().getConcept(), retVal.getConcept()); - } - } - return retVal; - }; - case DSTU3: - return t -> { - org.hl7.fhir.dstu3.model.CodeSystem codeSystem = - (org.hl7.fhir.dstu3.model.CodeSystem) theValidationSupportContext - .getRootValidationSupport() - .fetchCodeSystem(t); - return (CodeSystem) - VersionConvertorFactory_30_50.convertResource(codeSystem, new BaseAdvisor_30_50(false)); - }; - case R4: - return t -> { - org.hl7.fhir.r4.model.CodeSystem codeSystem = - (org.hl7.fhir.r4.model.CodeSystem) theValidationSupportContext - .getRootValidationSupport() - .fetchCodeSystem(t); - return (CodeSystem) - VersionConvertorFactory_40_50.convertResource(codeSystem, new BaseAdvisor_40_50(false)); - }; - case R4B: - return t -> { - org.hl7.fhir.r4b.model.CodeSystem codeSystem = - (org.hl7.fhir.r4b.model.CodeSystem) theValidationSupportContext - .getRootValidationSupport() - .fetchCodeSystem(t); - return (CodeSystem) - VersionConvertorFactory_43_50.convertResource(codeSystem, new BaseAdvisor_43_50(false)); - }; - case DSTU2_1: - case R5: - default: - return t -> (org.hl7.fhir.r5.model.CodeSystem) - theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); - } - } - - private String getFailureMessageForMissingOrUnusableCodeSystem( - CodeSystem includeOrExcludeSystemResource, String loadedCodeSystemUrl) { - String failureMessage; - if (includeOrExcludeSystemResource == null) { - failureMessage = "Unable to expand ValueSet because CodeSystem could not be found: " + loadedCodeSystemUrl; - } else { - assert includeOrExcludeSystemResource.getContent() == Enumerations.CodeSystemContentMode.NOTPRESENT; - failureMessage = - "Unable to expand ValueSet because CodeSystem has CodeSystem.content=not-present but contents were not found: " - + loadedCodeSystemUrl; - } - return failureMessage; - } - - 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 FhirVersionIndependentConcept( - theCodeSystemUrl, next.getCode(), next.getDisplay(), theCodeSystemVersion)); - } - } - addCodes(theCodeSystemUrl, theCodeSystemVersion, next.getConcept(), theTarget, theCodeFilter); - } - } - - private String optionallyPopulateVersionFromUrl(String theSystemUrl, String theVersion) { - if (contains(theSystemUrl, OUR_PIPE_CHARACTER) && isBlank(theVersion)) { - theVersion = substringAfter(theSystemUrl, OUR_PIPE_CHARACTER); - } - return theVersion; - } - - public static CodeValidationResult createWarningForDisplayMismatch( - FhirContext theFhirContext, - String theCode, - String theDisplay, - String theExpectedDisplay, - String theCodeSystemVersion) { - return createWarningForDisplayMismatch( - theFhirContext, theCode, theDisplay, theExpectedDisplay, theCodeSystemVersion, ""); - } - - private static CodeValidationResult createWarningForDisplayMismatch( - FhirContext theFhirContext, - String theCode, - String theDisplay, - String theExpectedDisplay, - String theCodeSystemVersion, - String theMessageAppend) { - - String message = theFhirContext - .getLocalizer() - .getMessage( - InMemoryTerminologyServerValidationSupport.class, - "displayMismatch", - theDisplay, - theExpectedDisplay) - + theMessageAppend; - return new CodeValidationResult() - .setSeverity(IssueSeverity.WARNING) - .setMessage(message) - .setCode(theCode) - .setCodeSystemVersion(theCodeSystemVersion) - .setDisplay(theExpectedDisplay); - } - - public enum FailureType { - UNKNOWN_CODE_SYSTEM, - OTHER - } - - public static class ExpansionCouldNotBeCompletedInternallyException extends Exception { - - private static final long serialVersionUID = -2226561628771483085L; - private final FailureType myFailureType; - - public ExpansionCouldNotBeCompletedInternallyException(String theMessage, FailureType theFailureType) { - super(theMessage); - myFailureType = theFailureType; - } - - public FailureType getFailureType() { - return myFailureType; - } - } - - private static void flattenAndConvertCodesDstu2( - List theInput, - List theFhirVersionIndependentConcepts) { - for (ca.uhn.fhir.model.dstu2.resource.ValueSet.ExpansionContains next : theInput) { - theFhirVersionIndependentConcepts.add( - new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); - flattenAndConvertCodesDstu2(next.getContains(), theFhirVersionIndependentConcepts); - } - } - - private static void flattenAndConvertCodesDstu2Hl7Org( - List theInput, - List theFhirVersionIndependentConcepts) { - for (org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { - theFhirVersionIndependentConcepts.add( - new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); - flattenAndConvertCodesDstu2Hl7Org(next.getContains(), theFhirVersionIndependentConcepts); - } - } - - private static void flattenAndConvertCodesDstu3( - List theInput, - List theFhirVersionIndependentConcepts) { - for (org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { - theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept( - next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); - flattenAndConvertCodesDstu3(next.getContains(), theFhirVersionIndependentConcepts); - } - } - - private static void flattenAndConvertCodesR4( - List theInput, - List theFhirVersionIndependentConcepts) { - for (org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { - theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept( - next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); - flattenAndConvertCodesR4(next.getContains(), theFhirVersionIndependentConcepts); - } - } - - private static void flattenAndConvertCodesR4B( - List theInput, - List theFhirVersionIndependentConcepts) { - for (org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { - theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept( - next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); - flattenAndConvertCodesR4B(next.getContains(), theFhirVersionIndependentConcepts); - } - } - - private static void flattenAndConvertCodesR5( - List theInput, - List theFhirVersionIndependentConcepts) { - for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { - theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept( - next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); - flattenAndConvertCodesR5(next.getContains(), theFhirVersionIndependentConcepts); - } - } + private static final String OUR_PIPE_CHARACTER = "|"; + private final FhirContext myCtx; + private final VersionCanonicalizer myVersionCanonicalizer; + private IssueSeverity myIssueSeverityForCodeDisplayMismatch = IssueSeverity.WARNING; + + /** + * Constructor + * + * @param theCtx A FhirContext for the FHIR version being validated + */ + public InMemoryTerminologyServerValidationSupport(FhirContext theCtx) { + Validate.notNull(theCtx, "theCtx must not be null"); + myCtx = theCtx; + myVersionCanonicalizer = new VersionCanonicalizer(theCtx); + } + + /** + * This setting controls the validation issue severity to report when a code validation + * finds that the code is present in the given CodeSystem, but the display name being + * validated doesn't match the expected value(s). Defaults to + * {@link ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity#WARNING}. Set this + * value to {@link ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity#INFORMATION} + * if you don't want to see display name validation issues at all in resource validation + * outcomes. + * + * @since 7.0.0 + */ + public IssueSeverity getIssueSeverityForCodeDisplayMismatch() { + return myIssueSeverityForCodeDisplayMismatch; + } + + /** + * This setting controls the validation issue severity to report when a code validation + * finds that the code is present in the given CodeSystem, but the display name being + * validated doesn't match the expected value(s). Defaults to + * {@link ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity#WARNING}. Set this + * value to {@link ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity#INFORMATION} + * if you don't want to see display name validation issues at all in resource validation + * outcomes. + * + * @param theIssueSeverityForCodeDisplayMismatch The severity. Must not be {@literal null}. + * @since 7.0.0 + */ + public void setIssueSeverityForCodeDisplayMismatch(@Nonnull IssueSeverity theIssueSeverityForCodeDisplayMismatch) { + Validate.notNull(theIssueSeverityForCodeDisplayMismatch, "theIssueSeverityForCodeDisplayMismatch must not be null"); + myIssueSeverityForCodeDisplayMismatch = theIssueSeverityForCodeDisplayMismatch; + } + + @Override + public FhirContext getFhirContext() { + return myCtx; + } + + @Override + public ValueSetExpansionOutcome expandValueSet( + ValidationSupportContext theValidationSupportContext, + ValueSetExpansionOptions theExpansionOptions, + @Nonnull IBaseResource theValueSetToExpand) { + return expandValueSet(theValidationSupportContext, theValueSetToExpand, null, null); + } + + private ValueSetExpansionOutcome expandValueSet( + ValidationSupportContext theValidationSupportContext, + IBaseResource theValueSetToExpand, + String theWantSystemAndVersion, + String theWantCode) { + org.hl7.fhir.r5.model.ValueSet expansionR5; + try { + expansionR5 = expandValueSetToCanonical( + theValidationSupportContext, theValueSetToExpand, theWantSystemAndVersion, theWantCode); + } catch (ExpansionCouldNotBeCompletedInternallyException e) { + return new ValueSetExpansionOutcome(e.getMessage()); + } + if (expansionR5 == null) { + return null; + } + + IBaseResource expansion; + switch (myCtx.getVersion().getVersion()) { + case DSTU2: { + org.hl7.fhir.r4.model.ValueSet expansionR4 = (org.hl7.fhir.r4.model.ValueSet) + VersionConvertorFactory_40_50.convertResource(expansionR5, new BaseAdvisor_40_50(false)); + expansion = myVersionCanonicalizer.valueSetFromCanonical(expansionR4); + break; + } + case DSTU2_HL7ORG: { + expansion = VersionConvertorFactory_10_50.convertResource(expansionR5, new BaseAdvisor_10_50(false)); + break; + } + case DSTU3: { + expansion = VersionConvertorFactory_30_50.convertResource(expansionR5, new BaseAdvisor_30_50(false)); + break; + } + case R4: { + expansion = VersionConvertorFactory_40_50.convertResource(expansionR5, new BaseAdvisor_40_50(false)); + break; + } + case R4B: { + expansion = VersionConvertorFactory_43_50.convertResource(expansionR5, new BaseAdvisor_43_50(false)); + break; + } + case R5: { + expansion = expansionR5; + break; + } + case DSTU2_1: + default: + throw new IllegalArgumentException(Msg.code(697) + "Can not handle version: " + + myCtx.getVersion().getVersion()); + } + + return new ValueSetExpansionOutcome(expansion); + } + + private org.hl7.fhir.r5.model.ValueSet expandValueSetToCanonical( + ValidationSupportContext theValidationSupportContext, + IBaseResource theValueSetToExpand, + @Nullable String theWantSystemUrlAndVersion, + @Nullable String theWantCode) + throws ExpansionCouldNotBeCompletedInternallyException { + org.hl7.fhir.r5.model.ValueSet expansionR5; + switch (getFhirVersionEnum( + theValidationSupportContext.getRootValidationSupport().getFhirContext(), theValueSetToExpand)) { + case DSTU2: { + expansionR5 = expandValueSetDstu2( + theValidationSupportContext, + (ca.uhn.fhir.model.dstu2.resource.ValueSet) theValueSetToExpand, + theWantSystemUrlAndVersion, + theWantCode); + break; + } + case DSTU2_HL7ORG: { + expansionR5 = expandValueSetDstu2Hl7Org( + theValidationSupportContext, + (ValueSet) theValueSetToExpand, + theWantSystemUrlAndVersion, + theWantCode); + break; + } + case DSTU3: { + expansionR5 = expandValueSetDstu3( + theValidationSupportContext, + (org.hl7.fhir.dstu3.model.ValueSet) theValueSetToExpand, + theWantSystemUrlAndVersion, + theWantCode); + break; + } + case R4: { + expansionR5 = expandValueSetR4( + theValidationSupportContext, + (org.hl7.fhir.r4.model.ValueSet) theValueSetToExpand, + theWantSystemUrlAndVersion, + theWantCode); + break; + } + case R4B: { + expansionR5 = expandValueSetR4B( + theValidationSupportContext, + (org.hl7.fhir.r4b.model.ValueSet) theValueSetToExpand, + theWantSystemUrlAndVersion, + theWantCode); + break; + } + case R5: { + expansionR5 = expandValueSetR5( + theValidationSupportContext, + (org.hl7.fhir.r5.model.ValueSet) theValueSetToExpand, + theWantSystemUrlAndVersion, + theWantCode); + break; + } + case DSTU2_1: + default: + throw new IllegalArgumentException(Msg.code(698) + "Can not handle version: " + + myCtx.getVersion().getVersion()); + } + + return expansionR5; + } + + @Override + public CodeValidationResult validateCodeInValueSet( + ValidationSupportContext theValidationSupportContext, + ConceptValidationOptions theOptions, + String theCodeSystemUrlAndVersion, + String theCode, + String theDisplay, + @Nonnull IBaseResource theValueSet) { + org.hl7.fhir.r5.model.ValueSet expansion; + String vsUrl = CommonCodeSystemsTerminologyService.getValueSetUrl(getFhirContext(), theValueSet); + try { + expansion = expandValueSetToCanonical( + theValidationSupportContext, theValueSet, theCodeSystemUrlAndVersion, theCode); + } catch (ExpansionCouldNotBeCompletedInternallyException e) { + CodeValidationResult codeValidationResult = new CodeValidationResult(); + codeValidationResult.setSeverityCode("error"); + + String msg = "Failed to expand ValueSet '" + vsUrl + "' (in-memory). Could not validate code " + + theCodeSystemUrlAndVersion + "#" + theCode; + if (e.getMessage() != null) { + msg += ". Error was: " + e.getMessage(); + } + + codeValidationResult.setMessage(msg); + return codeValidationResult; + } + + if (expansion == null) { + return null; + } + + return validateCodeInExpandedValueSet( + theValidationSupportContext, + theOptions, + theCodeSystemUrlAndVersion, + theCode, + theDisplay, + expansion, + vsUrl); + } + + @Override + @Nullable + public CodeValidationResult validateCode( + @Nonnull ValidationSupportContext theValidationSupportContext, + @Nonnull ConceptValidationOptions theOptions, + String theCodeSystem, + String theCode, + String theDisplay, + String theValueSetUrl) { + IBaseResource vs; + if (isNotBlank(theValueSetUrl)) { + vs = theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrl); + if (vs == null) { + 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: + 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))); + break; + case DSTU3: + 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: + 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 R4B: + if (codeSystemVersion != null) { + vs = new org.hl7.fhir.r4b.model.ValueSet() + .setCompose(new org.hl7.fhir.r4b.model.ValueSet.ValueSetComposeComponent() + .addInclude(new org.hl7.fhir.r4b.model.ValueSet.ConceptSetComponent() + .setSystem(codeSystemUrl) + .setVersion(codeSystemVersion))); + } else { + vs = new org.hl7.fhir.r4b.model.ValueSet() + .setCompose(new org.hl7.fhir.r4b.model.ValueSet.ValueSetComposeComponent() + .addInclude(new org.hl7.fhir.r4b.model.ValueSet.ConceptSetComponent() + .setSystem(theCodeSystem))); + } + break; + case R5: + 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_1: + default: + throw new IllegalArgumentException(Msg.code(699) + "Can not handle version: " + + myCtx.getVersion().getVersion()); + } + } + + ValueSetExpansionOutcome valueSetExpansionOutcome = + expandValueSet(theValidationSupportContext, vs, theCodeSystem, theCode); + if (valueSetExpansionOutcome == null) { + return null; + } + + if (valueSetExpansionOutcome.getError() != null) { + return new CodeValidationResult() + .setSeverity(IssueSeverity.ERROR) + .setMessage(valueSetExpansionOutcome.getError()); + } + + IBaseResource expansion = valueSetExpansionOutcome.getValueSet(); + return validateCodeInExpandedValueSet( + theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, expansion, theValueSetUrl); + } + + private CodeValidationResult validateCodeInExpandedValueSet( + ValidationSupportContext theValidationSupportContext, + ConceptValidationOptions theOptions, + String theCodeSystemUrlAndVersionToValidate, + String theCodeToValidate, + String theDisplayToValidate, + IBaseResource theExpansion, + String theValueSetUrl) { + assert theExpansion != null; + + boolean caseSensitive = true; + IBaseResource codeSystemToValidateResource = null; + if (!theOptions.isInferSystem() && isNotBlank(theCodeSystemUrlAndVersionToValidate)) { + codeSystemToValidateResource = theValidationSupportContext + .getRootValidationSupport() + .fetchCodeSystem(theCodeSystemUrlAndVersionToValidate); + } + + List codes = new ArrayList<>(); + switch (getFhirVersionEnum( + theValidationSupportContext.getRootValidationSupport().getFhirContext(), theExpansion)) { + case DSTU2: { + ca.uhn.fhir.model.dstu2.resource.ValueSet expansionVs = + (ca.uhn.fhir.model.dstu2.resource.ValueSet) theExpansion; + List contains = + expansionVs.getExpansion().getContains(); + flattenAndConvertCodesDstu2(contains, codes); + break; + } + case DSTU2_HL7ORG: { + ValueSet expansionVs = (ValueSet) theExpansion; + List contains = + expansionVs.getExpansion().getContains(); + flattenAndConvertCodesDstu2Hl7Org(contains, codes); + 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); + 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); + break; + } + case R4B: { + org.hl7.fhir.r4b.model.ValueSet expansionVs = (org.hl7.fhir.r4b.model.ValueSet) theExpansion; + List contains = + expansionVs.getExpansion().getContains(); + flattenAndConvertCodesR4B(contains, codes); + 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); + break; + } + case DSTU2_1: + default: + throw new IllegalArgumentException(Msg.code(700) + "Can not handle version: " + + myCtx.getVersion().getVersion()); + } + + String codeSystemResourceName = null; + String codeSystemResourceVersion = null; + String codeSystemResourceContentMode = null; + if (codeSystemToValidateResource != null) { + switch (getFhirVersionEnum( + theValidationSupportContext.getRootValidationSupport().getFhirContext(), + codeSystemToValidateResource)) { + case DSTU2: + case DSTU2_HL7ORG: { + caseSensitive = true; + break; + } + case DSTU3: { + org.hl7.fhir.dstu3.model.CodeSystem systemDstu3 = + (org.hl7.fhir.dstu3.model.CodeSystem) codeSystemToValidateResource; + caseSensitive = systemDstu3.getCaseSensitive(); + 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) codeSystemToValidateResource; + caseSensitive = systemR4.getCaseSensitive(); + codeSystemResourceName = systemR4.getName(); + codeSystemResourceVersion = systemR4.getVersion(); + codeSystemResourceContentMode = systemR4.getContentElement().getValueAsString(); + break; + } + case R4B: { + org.hl7.fhir.r4b.model.CodeSystem systemR4B = + (org.hl7.fhir.r4b.model.CodeSystem) codeSystemToValidateResource; + caseSensitive = systemR4B.getCaseSensitive(); + codeSystemResourceName = systemR4B.getName(); + codeSystemResourceVersion = systemR4B.getVersion(); + codeSystemResourceContentMode = + systemR4B.getContentElement().getValueAsString(); + break; + } + case R5: { + CodeSystem systemR5 = (CodeSystem) codeSystemToValidateResource; + caseSensitive = systemR5.getCaseSensitive(); + codeSystemResourceName = systemR5.getName(); + codeSystemResourceVersion = systemR5.getVersion(); + codeSystemResourceContentMode = systemR5.getContentElement().getValueAsString(); + break; + } + case DSTU2_1: + default: + throw new IllegalArgumentException(Msg.code(701) + "Can not handle version: " + + myCtx.getVersion().getVersion()); + } + } + + String codeSystemUrlToValidate = null; + String codeSystemVersionToValidate = null; + if (theCodeSystemUrlAndVersionToValidate != null) { + int versionIndex = theCodeSystemUrlAndVersionToValidate.indexOf("|"); + if (versionIndex > -1) { + codeSystemUrlToValidate = theCodeSystemUrlAndVersionToValidate.substring(0, versionIndex); + codeSystemVersionToValidate = theCodeSystemUrlAndVersionToValidate.substring(versionIndex + 1); + } else { + codeSystemUrlToValidate = theCodeSystemUrlAndVersionToValidate; + } + } + for (FhirVersionIndependentConcept nextExpansionCode : codes) { + + boolean codeMatches; + if (caseSensitive) { + codeMatches = defaultString(theCodeToValidate).equals(nextExpansionCode.getCode()); + } else { + codeMatches = defaultString(theCodeToValidate).equalsIgnoreCase(nextExpansionCode.getCode()); + } + if (codeMatches) { + if (theOptions.isInferSystem() + || (nextExpansionCode.getSystem().equals(codeSystemUrlToValidate) + && (codeSystemVersionToValidate == null + || codeSystemVersionToValidate.equals(nextExpansionCode.getSystemVersion())))) { + String csVersion = codeSystemResourceVersion; + if (isNotBlank(nextExpansionCode.getSystemVersion())) { + csVersion = nextExpansionCode.getSystemVersion(); + } + if (!theOptions.isValidateDisplay() + || (isBlank(nextExpansionCode.getDisplay()) + || isBlank(theDisplayToValidate) + || nextExpansionCode.getDisplay().equals(theDisplayToValidate))) { + CodeValidationResult codeValidationResult = new CodeValidationResult() + .setCode(theCodeToValidate) + .setDisplay(nextExpansionCode.getDisplay()) + .setCodeSystemName(codeSystemResourceName) + .setCodeSystemVersion(csVersion); + if (isNotBlank(theValueSetUrl)) { + populateSourceDetailsForInMemoryExpansion(theValueSetUrl, codeValidationResult); + } + return codeValidationResult; + } else { + String messageAppend = ""; + if (isNotBlank(theValueSetUrl)) { + messageAppend = " for in-memory expansion of ValueSet: " + theValueSetUrl; + } + CodeValidationResult codeValidationResult = createResultForDisplayMismatch( + myCtx, + theCodeToValidate, + theDisplayToValidate, + nextExpansionCode.getDisplay(), + csVersion, + messageAppend, + getIssueSeverityForCodeDisplayMismatch()); + if (isNotBlank(theValueSetUrl)) { + populateSourceDetailsForInMemoryExpansion(theValueSetUrl, codeValidationResult); + } + return codeValidationResult; + } + } + } + } + + ValidationMessage.IssueSeverity severity; + String message; + if ("fragment".equals(codeSystemResourceContentMode)) { + severity = ValidationMessage.IssueSeverity.WARNING; + message = "Unknown code in fragment CodeSystem '" + + (isNotBlank(theCodeSystemUrlAndVersionToValidate) + ? theCodeSystemUrlAndVersionToValidate + "#" + : "") + + theCodeToValidate + "'"; + } else { + severity = ValidationMessage.IssueSeverity.ERROR; + message = "Unknown code '" + + (isNotBlank(theCodeSystemUrlAndVersionToValidate) + ? theCodeSystemUrlAndVersionToValidate + "#" + : "") + + theCodeToValidate + "'"; + } + if (isNotBlank(theValueSetUrl)) { + message += " for in-memory expansion of ValueSet '" + theValueSetUrl + "'"; + } + + return new CodeValidationResult().setSeverityCode(severity.toCode()).setMessage(message); + } + + @Override + public LookupCodeResult lookupCode( + ValidationSupportContext theValidationSupportContext, + String theSystem, + String theCode, + String theDisplayLanguage) { + CodeValidationResult codeValidationResult = validateCode( + theValidationSupportContext, new ConceptValidationOptions(), theSystem, theCode, null, null); + if (codeValidationResult == null) { + return null; + } + return codeValidationResult.asLookupCodeResult(theSystem, theCode); + } + + @Nullable + private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2Hl7Org( + ValidationSupportContext theValidationSupportContext, + ValueSet theInput, + @Nullable String theWantSystemUrlAndVersion, + @Nullable String theWantCode) + throws ExpansionCouldNotBeCompletedInternallyException { + org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) + VersionConvertorFactory_10_50.convertResource(theInput, new BaseAdvisor_10_50(false)); + return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode)); + } + + @Nullable + private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2( + ValidationSupportContext theValidationSupportContext, + ca.uhn.fhir.model.dstu2.resource.ValueSet theInput, + @Nullable String theWantSystemUrlAndVersion, + @Nullable String theWantCode) + throws ExpansionCouldNotBeCompletedInternallyException { + IParser parserRi = FhirContext.forCached(FhirVersionEnum.DSTU2_HL7ORG).newJsonParser(); + IParser parserHapi = FhirContext.forDstu2Cached().newJsonParser(); + + 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 = (org.hl7.fhir.r5.model.ValueSet) + VersionConvertorFactory_10_50.convertResource(valueSetRi, new BaseAdvisor_10_50(false)); + return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode)); + } + + @Override + public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) { + if (isBlank(theSystem)) { + return false; + } + + IBaseResource cs = + theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(theSystem); + + if (!myCtx.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU2_1)) { + return cs != null; + } + + if (cs != null) { + IPrimitiveType content = + getFhirContext().newTerser().getSingleValueOrNull(cs, "content", IPrimitiveType.class); + return !"not-present".equals(content.getValueAsString()); + } + + return false; + } + + @Override + public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) { + return isNotBlank(theValueSetUrl) + && theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrl) != null; + } + + private void addCodesDstu2Hl7Org( + List theSourceList, + List theTargetList) { + for (ValueSet.ConceptDefinitionComponent nextSource : theSourceList) { + CodeSystem.ConceptDefinitionComponent targetConcept = new CodeSystem.ConceptDefinitionComponent() + .setCode(nextSource.getCode()) + .setDisplay(nextSource.getDisplay()); + theTargetList.add(targetConcept); + addCodesDstu2Hl7Org(nextSource.getConcept(), targetConcept.getConcept()); + } + } + + private void addCodesDstu2( + List theSourceList, + List theTargetList) { + for (ca.uhn.fhir.model.dstu2.resource.ValueSet.CodeSystemConcept nextSource : theSourceList) { + CodeSystem.ConceptDefinitionComponent targetConcept = new CodeSystem.ConceptDefinitionComponent() + .setCode(nextSource.getCode()) + .setDisplay(nextSource.getDisplay()); + theTargetList.add(targetConcept); + addCodesDstu2(nextSource.getConcept(), targetConcept.getConcept()); + } + } + + @Nullable + private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu3( + ValidationSupportContext theValidationSupportContext, + org.hl7.fhir.dstu3.model.ValueSet theInput, + @Nullable String theWantSystemUrlAndVersion, + @Nullable String theWantCode) + throws ExpansionCouldNotBeCompletedInternallyException { + org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) + VersionConvertorFactory_30_50.convertResource(theInput, new BaseAdvisor_30_50(false)); + return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode)); + } + + @Nullable + private org.hl7.fhir.r5.model.ValueSet expandValueSetR4( + ValidationSupportContext theValidationSupportContext, + org.hl7.fhir.r4.model.ValueSet theInput, + @Nullable String theWantSystemUrlAndVersion, + @Nullable String theWantCode) + throws ExpansionCouldNotBeCompletedInternallyException { + org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) + VersionConvertorFactory_40_50.convertResource(theInput, new BaseAdvisor_40_50(false)); + return expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode); + } + + @Nullable + private org.hl7.fhir.r5.model.ValueSet expandValueSetR4B( + ValidationSupportContext theValidationSupportContext, + org.hl7.fhir.r4b.model.ValueSet theInput, + @Nullable String theWantSystemUrlAndVersion, + @Nullable String theWantCode) + throws ExpansionCouldNotBeCompletedInternallyException { + org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) + VersionConvertorFactory_43_50.convertResource(theInput, new BaseAdvisor_43_50(false)); + return expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode); + } + + @Nullable + private org.hl7.fhir.r5.model.ValueSet expandValueSetR5( + ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r5.model.ValueSet theInput) + throws ExpansionCouldNotBeCompletedInternallyException { + return expandValueSetR5(theValidationSupportContext, theInput, null, null); + } + + @Nullable + private org.hl7.fhir.r5.model.ValueSet expandValueSetR5( + ValidationSupportContext theValidationSupportContext, + org.hl7.fhir.r5.model.ValueSet theInput, + @Nullable String theWantSystemUrlAndVersion, + @Nullable String theWantCode) + throws ExpansionCouldNotBeCompletedInternallyException { + Set concepts = new HashSet<>(); + + expandValueSetR5IncludeOrExcludes( + theValidationSupportContext, + concepts, + theInput.getCompose().getInclude(), + true, + theWantSystemUrlAndVersion, + theWantCode); + expandValueSetR5IncludeOrExcludes( + theValidationSupportContext, + concepts, + theInput.getCompose().getExclude(), + false, + theWantSystemUrlAndVersion, + theWantCode); + + org.hl7.fhir.r5.model.ValueSet retVal = new org.hl7.fhir.r5.model.ValueSet(); + for (FhirVersionIndependentConcept next : concepts) { + org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent contains = + retVal.getExpansion().addContains(); + contains.setSystem(next.getSystem()); + contains.setCode(next.getCode()); + contains.setDisplay(next.getDisplay()); + contains.setVersion(next.getSystemVersion()); + } + + return retVal; + } + + /** + * Use with caution - this is not a stable API + * + * @since 5.6.0 + */ + public void expandValueSetIncludeOrExclude( + ValidationSupportContext theValidationSupportContext, + Consumer theConsumer, + org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent theIncludeOrExclude) + throws ExpansionCouldNotBeCompletedInternallyException { + expandValueSetR5IncludeOrExclude(theValidationSupportContext, theConsumer, null, null, theIncludeOrExclude); + } + + private void expandValueSetR5IncludeOrExcludes( + ValidationSupportContext theValidationSupportContext, + Set theConcepts, + List theComposeList, + boolean theComposeListIsInclude, + @Nullable String theWantSystemUrlAndVersion, + @Nullable String theWantCode) + throws ExpansionCouldNotBeCompletedInternallyException { + Consumer consumer = c -> { + if (theComposeListIsInclude) { + theConcepts.add(c); + } else { + theConcepts.remove(c); + } + }; + expandValueSetR5IncludeOrExcludes( + theValidationSupportContext, consumer, theComposeList, theWantSystemUrlAndVersion, theWantCode); + } + + private void expandValueSetR5IncludeOrExcludes( + ValidationSupportContext theValidationSupportContext, + Consumer theConsumer, + List theComposeList, + @Nullable String theWantSystemUrlAndVersion, + @Nullable String theWantCode) + throws ExpansionCouldNotBeCompletedInternallyException { + ExpansionCouldNotBeCompletedInternallyException caughtException = null; + for (org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent nextInclude : theComposeList) { + try { + boolean outcome = expandValueSetR5IncludeOrExclude( + theValidationSupportContext, theConsumer, theWantSystemUrlAndVersion, theWantCode, nextInclude); + if (isNotBlank(theWantCode)) { + if (outcome) { + return; + } + } + } catch (ExpansionCouldNotBeCompletedInternallyException e) { + if (isBlank(theWantCode)) { + throw e; + } else { + caughtException = e; + } + } + } + if (caughtException != null) { + throw caughtException; + } + } + + /** + * Returns true if at least one code was added + */ + private boolean expandValueSetR5IncludeOrExclude( + ValidationSupportContext theValidationSupportContext, + Consumer theConsumer, + @Nullable String theWantSystemUrlAndVersion, + @Nullable String theWantCode, + org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent theInclude) + throws ExpansionCouldNotBeCompletedInternallyException { + + String wantSystemUrl = null; + String wantSystemVersion = null; + + if (theWantSystemUrlAndVersion != null) { + int versionIndex = theWantSystemUrlAndVersion.indexOf(OUR_PIPE_CHARACTER); + if (versionIndex > -1) { + wantSystemUrl = theWantSystemUrlAndVersion.substring(0, versionIndex); + wantSystemVersion = theWantSystemUrlAndVersion.substring(versionIndex + 1); + } else { + wantSystemUrl = theWantSystemUrlAndVersion; + } + } + + String includeOrExcludeConceptSystemUrl = theInclude.getSystem(); + String includeOrExcludeConceptSystemVersion = theInclude.getVersion(); + + Function codeSystemLoader = newCodeSystemLoader(theValidationSupportContext); + Function valueSetLoader = + newValueSetLoader(theValidationSupportContext); + + List nextCodeList = new ArrayList<>(); + CodeSystem includeOrExcludeSystemResource = null; + + if (isNotBlank(includeOrExcludeConceptSystemUrl)) { + + includeOrExcludeConceptSystemVersion = optionallyPopulateVersionFromUrl( + includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion); + includeOrExcludeConceptSystemUrl = substringBefore(includeOrExcludeConceptSystemUrl, OUR_PIPE_CHARACTER); + + if (wantSystemUrl != null && !wantSystemUrl.equals(includeOrExcludeConceptSystemUrl)) { + return false; + } + + if (wantSystemVersion != null && !wantSystemVersion.equals(includeOrExcludeConceptSystemVersion)) { + return false; + } + + String loadedCodeSystemUrl; + if (includeOrExcludeConceptSystemVersion != null) { + loadedCodeSystemUrl = + includeOrExcludeConceptSystemUrl + OUR_PIPE_CHARACTER + includeOrExcludeConceptSystemVersion; + } else { + loadedCodeSystemUrl = includeOrExcludeConceptSystemUrl; + } + + includeOrExcludeSystemResource = codeSystemLoader.apply(loadedCodeSystemUrl); + + Set wantCodes; + if (theInclude.getConcept().isEmpty()) { + wantCodes = null; + } else { + wantCodes = + theInclude.getConcept().stream().map(t -> t.getCode()).collect(Collectors.toSet()); + } + + boolean ableToHandleCode = false; + String failureMessage = null; + FailureType failureType = FailureType.OTHER; + + if (includeOrExcludeSystemResource == null + || includeOrExcludeSystemResource.getContent() == Enumerations.CodeSystemContentMode.NOTPRESENT) { + + if (theWantCode != null) { + if (theValidationSupportContext + .getRootValidationSupport() + .isCodeSystemSupported(theValidationSupportContext, includeOrExcludeConceptSystemUrl)) { + LookupCodeResult lookup = theValidationSupportContext + .getRootValidationSupport() + .lookupCode( + theValidationSupportContext, + includeOrExcludeConceptSystemUrl, + theWantCode, + null); + if (lookup != null) { + ableToHandleCode = true; + if (lookup.isFound()) { + CodeSystem.ConceptDefinitionComponent conceptDefinition = + new CodeSystem.ConceptDefinitionComponent() + .addConcept() + .setCode(theWantCode) + .setDisplay(lookup.getCodeDisplay()); + List codesList = + Collections.singletonList(conceptDefinition); + addCodes( + includeOrExcludeConceptSystemUrl, + includeOrExcludeConceptSystemVersion, + codesList, + nextCodeList, + wantCodes); + } + } + } else { + + /* + * If we're doing an expansion specifically looking for a single code, that means we're validating that code. + * In the case where we have a ValueSet that explicitly enumerates a collection of codes + * (via ValueSet.compose.include.code) in a code system that is unknown we'll assume the code is valid + * even if we can't find the CodeSystem. This is a compromise obviously, since it would be ideal for + * CodeSystems to always be known, but realistically there are always going to be CodeSystems that + * can't be supplied because of copyright issues, or because they are grammar based. Allowing a VS to + * enumerate a set of good codes for them is a nice compromise there. + */ + if (Objects.equals(theInclude.getSystem(), theWantSystemUrlAndVersion)) { + Optional + matchingEnumeratedConcept = theInclude.getConcept().stream() + .filter(t -> Objects.equals(t.getCode(), theWantCode)) + .findFirst(); + + // If the ValueSet.compose.include has no individual concepts in it, and + // we can't find the actual referenced CodeSystem, we have no choice + // but to fail + if (!theInclude.getConcept().isEmpty()) { + ableToHandleCode = true; + } else { + failureMessage = getFailureMessageForMissingOrUnusableCodeSystem( + includeOrExcludeSystemResource, loadedCodeSystemUrl); + } + + if (matchingEnumeratedConcept.isPresent()) { + CodeSystem.ConceptDefinitionComponent conceptDefinition = + new CodeSystem.ConceptDefinitionComponent() + .addConcept() + .setCode(theWantCode) + .setDisplay(matchingEnumeratedConcept + .get() + .getDisplay()); + List codesList = + Collections.singletonList(conceptDefinition); + addCodes( + includeOrExcludeConceptSystemUrl, + includeOrExcludeConceptSystemVersion, + codesList, + nextCodeList, + wantCodes); + } + } + } + } else { + if (isNotBlank(theInclude.getSystem()) + && !theInclude.getConcept().isEmpty() + && theInclude.getFilter().isEmpty() + && theInclude.getValueSet().isEmpty()) { + theInclude.getConcept().stream() + .map(t -> new FhirVersionIndependentConcept( + theInclude.getSystem(), t.getCode(), t.getDisplay(), theInclude.getVersion())) + .forEach(t -> nextCodeList.add(t)); + ableToHandleCode = true; + } + + if (!ableToHandleCode) { + failureMessage = getFailureMessageForMissingOrUnusableCodeSystem( + includeOrExcludeSystemResource, loadedCodeSystemUrl); + } + } + + } else { + ableToHandleCode = true; + } + + if (!ableToHandleCode) { + if (includeOrExcludeSystemResource == null && failureMessage == null) { + failureMessage = getFailureMessageForMissingOrUnusableCodeSystem( + includeOrExcludeSystemResource, loadedCodeSystemUrl); + } + + if (includeOrExcludeSystemResource == null) { + failureType = FailureType.UNKNOWN_CODE_SYSTEM; + } + + throw new ExpansionCouldNotBeCompletedInternallyException(Msg.code(702) + failureMessage, failureType); + } + + if (includeOrExcludeSystemResource != null + && includeOrExcludeSystemResource.getContent() != Enumerations.CodeSystemContentMode.NOTPRESENT) { + addCodes( + includeOrExcludeConceptSystemUrl, + includeOrExcludeConceptSystemVersion, + includeOrExcludeSystemResource.getConcept(), + nextCodeList, + wantCodes); + } + } + + for (CanonicalType nextValueSetInclude : theInclude.getValueSet()) { + org.hl7.fhir.r5.model.ValueSet vs = valueSetLoader.apply(nextValueSetInclude.getValueAsString()); + if (vs != null) { + org.hl7.fhir.r5.model.ValueSet subExpansion = + expandValueSetR5(theValidationSupportContext, vs, theWantSystemUrlAndVersion, theWantCode); + if (subExpansion == null) { + throw new ExpansionCouldNotBeCompletedInternallyException( + Msg.code(703) + "Failed to expand ValueSet: " + nextValueSetInclude.getValueAsString(), + FailureType.OTHER); + } + for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : + subExpansion.getExpansion().getContains()) { + nextCodeList.add(new FhirVersionIndependentConcept( + next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); + } + } + } + + boolean retVal = false; + + for (FhirVersionIndependentConcept next : nextCodeList) { + if (includeOrExcludeSystemResource != null && theWantCode != null) { + boolean matches; + if (includeOrExcludeSystemResource.getCaseSensitive()) { + matches = theWantCode.equals(next.getCode()); + } else { + matches = theWantCode.equalsIgnoreCase(next.getCode()); + } + if (!matches) { + continue; + } + } + + theConsumer.accept(next); + retVal = true; + } + + return retVal; + } + + private Function newValueSetLoader( + ValidationSupportContext theValidationSupportContext) { + switch (myCtx.getVersion().getVersion()) { + case DSTU2: + case DSTU2_HL7ORG: + return t -> { + IBaseResource vs = theValidationSupportContext + .getRootValidationSupport() + .fetchValueSet(t); + if (vs instanceof ca.uhn.fhir.model.dstu2.resource.ValueSet) { + IParser parserRi = FhirContext.forCached(FhirVersionEnum.DSTU2_HL7ORG) + .newJsonParser(); + IParser parserHapi = FhirContext.forDstu2Cached().newJsonParser(); + ca.uhn.fhir.model.dstu2.resource.ValueSet valueSet = + (ca.uhn.fhir.model.dstu2.resource.ValueSet) vs; + org.hl7.fhir.dstu2.model.ValueSet valueSetRi = parserRi.parseResource( + org.hl7.fhir.dstu2.model.ValueSet.class, parserHapi.encodeResourceToString(valueSet)); + return (org.hl7.fhir.r5.model.ValueSet) + VersionConvertorFactory_10_50.convertResource(valueSetRi, new BaseAdvisor_10_50(false)); + } else { + org.hl7.fhir.dstu2.model.ValueSet valueSet = + (org.hl7.fhir.dstu2.model.ValueSet) theValidationSupportContext + .getRootValidationSupport() + .fetchValueSet(t); + return (org.hl7.fhir.r5.model.ValueSet) + VersionConvertorFactory_10_50.convertResource(valueSet, new BaseAdvisor_10_50(false)); + } + }; + case DSTU3: + return t -> { + org.hl7.fhir.dstu3.model.ValueSet valueSet = + (org.hl7.fhir.dstu3.model.ValueSet) theValidationSupportContext + .getRootValidationSupport() + .fetchValueSet(t); + return (org.hl7.fhir.r5.model.ValueSet) + VersionConvertorFactory_30_50.convertResource(valueSet, new BaseAdvisor_30_50(false)); + }; + case R4: + return t -> { + org.hl7.fhir.r4.model.ValueSet valueSet = + (org.hl7.fhir.r4.model.ValueSet) theValidationSupportContext + .getRootValidationSupport() + .fetchValueSet(t); + return (org.hl7.fhir.r5.model.ValueSet) + VersionConvertorFactory_40_50.convertResource(valueSet, new BaseAdvisor_40_50(false)); + }; + case R4B: + return t -> { + org.hl7.fhir.r4b.model.ValueSet valueSet = + (org.hl7.fhir.r4b.model.ValueSet) theValidationSupportContext + .getRootValidationSupport() + .fetchValueSet(t); + return (org.hl7.fhir.r5.model.ValueSet) + VersionConvertorFactory_43_50.convertResource(valueSet, new BaseAdvisor_43_50(false)); + }; + default: + case DSTU2_1: + case R5: + return t -> (org.hl7.fhir.r5.model.ValueSet) + theValidationSupportContext.getRootValidationSupport().fetchValueSet(t); + } + } + + private Function newCodeSystemLoader(ValidationSupportContext theValidationSupportContext) { + switch (myCtx.getVersion().getVersion()) { + case DSTU2: + case DSTU2_HL7ORG: + return t -> { + IBaseResource codeSystem = theValidationSupportContext + .getRootValidationSupport() + .fetchCodeSystem(t); + CodeSystem retVal = null; + if (codeSystem != null) { + retVal = new CodeSystem(); + if (codeSystem instanceof ca.uhn.fhir.model.dstu2.resource.ValueSet) { + ca.uhn.fhir.model.dstu2.resource.ValueSet codeSystemCasted = + (ca.uhn.fhir.model.dstu2.resource.ValueSet) codeSystem; + retVal.setUrl(codeSystemCasted.getUrl()); + addCodesDstu2(codeSystemCasted.getCodeSystem().getConcept(), retVal.getConcept()); + } else { + org.hl7.fhir.dstu2.model.ValueSet codeSystemCasted = + (org.hl7.fhir.dstu2.model.ValueSet) codeSystem; + retVal.setUrl(codeSystemCasted.getUrl()); + addCodesDstu2Hl7Org(codeSystemCasted.getCodeSystem().getConcept(), retVal.getConcept()); + } + } + return retVal; + }; + case DSTU3: + return t -> { + org.hl7.fhir.dstu3.model.CodeSystem codeSystem = + (org.hl7.fhir.dstu3.model.CodeSystem) theValidationSupportContext + .getRootValidationSupport() + .fetchCodeSystem(t); + return (CodeSystem) + VersionConvertorFactory_30_50.convertResource(codeSystem, new BaseAdvisor_30_50(false)); + }; + case R4: + return t -> { + org.hl7.fhir.r4.model.CodeSystem codeSystem = + (org.hl7.fhir.r4.model.CodeSystem) theValidationSupportContext + .getRootValidationSupport() + .fetchCodeSystem(t); + return (CodeSystem) + VersionConvertorFactory_40_50.convertResource(codeSystem, new BaseAdvisor_40_50(false)); + }; + case R4B: + return t -> { + org.hl7.fhir.r4b.model.CodeSystem codeSystem = + (org.hl7.fhir.r4b.model.CodeSystem) theValidationSupportContext + .getRootValidationSupport() + .fetchCodeSystem(t); + return (CodeSystem) + VersionConvertorFactory_43_50.convertResource(codeSystem, new BaseAdvisor_43_50(false)); + }; + case DSTU2_1: + case R5: + default: + return t -> (org.hl7.fhir.r5.model.CodeSystem) + theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); + } + } + + private String getFailureMessageForMissingOrUnusableCodeSystem( + CodeSystem includeOrExcludeSystemResource, String loadedCodeSystemUrl) { + String failureMessage; + if (includeOrExcludeSystemResource == null) { + failureMessage = "Unable to expand ValueSet because CodeSystem could not be found: " + loadedCodeSystemUrl; + } else { + assert includeOrExcludeSystemResource.getContent() == Enumerations.CodeSystemContentMode.NOTPRESENT; + failureMessage = + "Unable to expand ValueSet because CodeSystem has CodeSystem.content=not-present but contents were not found: " + + loadedCodeSystemUrl; + } + return failureMessage; + } + + 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 FhirVersionIndependentConcept( + theCodeSystemUrl, next.getCode(), next.getDisplay(), theCodeSystemVersion)); + } + } + addCodes(theCodeSystemUrl, theCodeSystemVersion, next.getConcept(), theTarget, theCodeFilter); + } + } + + private String optionallyPopulateVersionFromUrl(String theSystemUrl, String theVersion) { + if (contains(theSystemUrl, OUR_PIPE_CHARACTER) && isBlank(theVersion)) { + theVersion = substringAfter(theSystemUrl, OUR_PIPE_CHARACTER); + } + return theVersion; + } + + private static void populateSourceDetailsForInMemoryExpansion( + String theValueSetUrl, CodeValidationResult codeValidationResult) { + codeValidationResult.setSourceDetails( + "Code was validated against in-memory expansion of ValueSet: " + theValueSetUrl); + } + + public static CodeValidationResult createResultForDisplayMismatch( + FhirContext theFhirContext, + String theCode, + String theDisplay, + String theExpectedDisplay, + String theCodeSystemVersion, + IssueSeverity theIssueSeverityForCodeDisplayMismatch) { + return createResultForDisplayMismatch( + theFhirContext, theCode, theDisplay, theExpectedDisplay, theCodeSystemVersion, "", theIssueSeverityForCodeDisplayMismatch); + } + + private static CodeValidationResult createResultForDisplayMismatch( + FhirContext theFhirContext, + String theCode, + String theDisplay, + String theExpectedDisplay, + String theCodeSystemVersion, + String theMessageAppend, + IssueSeverity theIssueSeverityForCodeDisplayMismatch) { + + String message; + IssueSeverity issueSeverity = theIssueSeverityForCodeDisplayMismatch; + if (issueSeverity == IssueSeverity.INFORMATION) { + message = null; + issueSeverity = null; + } else { + message = theFhirContext + .getLocalizer() + .getMessage( + InMemoryTerminologyServerValidationSupport.class, + "displayMismatch", + theDisplay, + theExpectedDisplay) + + theMessageAppend; + } + return new CodeValidationResult() + .setSeverity(issueSeverity) + .setMessage(message) + .setCode(theCode) + .setCodeSystemVersion(theCodeSystemVersion) + .setDisplay(theExpectedDisplay); + } + + private static void flattenAndConvertCodesDstu2( + List theInput, + List theFhirVersionIndependentConcepts) { + for (ca.uhn.fhir.model.dstu2.resource.ValueSet.ExpansionContains next : theInput) { + theFhirVersionIndependentConcepts.add( + new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); + flattenAndConvertCodesDstu2(next.getContains(), theFhirVersionIndependentConcepts); + } + } + + private static void flattenAndConvertCodesDstu2Hl7Org( + List theInput, + List theFhirVersionIndependentConcepts) { + for (org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { + theFhirVersionIndependentConcepts.add( + new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); + flattenAndConvertCodesDstu2Hl7Org(next.getContains(), theFhirVersionIndependentConcepts); + } + } + + private static void flattenAndConvertCodesDstu3( + List theInput, + List theFhirVersionIndependentConcepts) { + for (org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { + theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept( + next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); + flattenAndConvertCodesDstu3(next.getContains(), theFhirVersionIndependentConcepts); + } + } + + private static void flattenAndConvertCodesR4( + List theInput, + List theFhirVersionIndependentConcepts) { + for (org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { + theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept( + next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); + flattenAndConvertCodesR4(next.getContains(), theFhirVersionIndependentConcepts); + } + } + + private static void flattenAndConvertCodesR4B( + List theInput, + List theFhirVersionIndependentConcepts) { + for (org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { + theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept( + next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); + flattenAndConvertCodesR4B(next.getContains(), theFhirVersionIndependentConcepts); + } + } + + private static void flattenAndConvertCodesR5( + List theInput, + List theFhirVersionIndependentConcepts) { + for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { + theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept( + next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); + flattenAndConvertCodesR5(next.getContains(), theFhirVersionIndependentConcepts); + } + } + + public enum FailureType { + UNKNOWN_CODE_SYSTEM, + OTHER + } + + public static class ExpansionCouldNotBeCompletedInternallyException extends Exception { + + private static final long serialVersionUID = -2226561628771483085L; + private final FailureType myFailureType; + + public ExpansionCouldNotBeCompletedInternallyException(String theMessage, FailureType theFailureType) { + super(theMessage); + myFailureType = theFailureType; + } + + public FailureType getFailureType() { + return myFailureType; + } + } } From 25a9fa7cdc834d290140636e05fe0aefb78e1c05 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Sun, 17 Sep 2023 11:20:18 +0800 Subject: [PATCH 07/10] Should be all working --- ...e-display-validation-now-configurable.yaml | 6 + .../ca/uhn/fhir/jpa/config/MdmJpaConfig.java | 19 + .../jpa/config/ValidationSupportConfig.java | 6 +- .../provider/ValueSetOperationProvider.java | 5 +- .../ca/uhn/fhir/jpa/term/TermReadSvcImpl.java | 36 +- .../jpa/mdm/config/MdmSurvivorshipConfig.java | 19 + .../extractor/SearchParamExtractorR4.java | 1 - .../fhir/mdm/svc/MdmSurvivorshipSvcImpl.java | 2 +- .../jpa/api/config/JpaStorageSettings.java | 10 +- .../java/ca/uhn/fhir/models/TestResource.java | 19 + ...oryTerminologyServerValidationSupport.java | 2625 +++++++++-------- 11 files changed, 1414 insertions(+), 1334 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/FIXME-code-display-validation-now-configurable.yaml diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/FIXME-code-display-validation-now-configurable.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/FIXME-code-display-validation-now-configurable.yaml new file mode 100644 index 000000000000..650f443feeb5 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/FIXME-code-display-validation-now-configurable.yaml @@ -0,0 +1,6 @@ +--- +type: add +issue: FIXME +title: "It is now possible to configure the strictness of concept display name validation + using a new flag on the InMemoryTerminologyServerValidationSupport (for non-JPA validation) + and JpaStorageSettings (for JPA validation)." diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/MdmJpaConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/MdmJpaConfig.java index 5d1178a17541..c97278451145 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/MdmJpaConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/MdmJpaConfig.java @@ -1,3 +1,22 @@ +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2023 Smile CDR, Inc. + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ package ca.uhn.fhir.jpa.config; import ca.uhn.fhir.jpa.api.svc.IDeleteExpungeSvc; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/ValidationSupportConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/ValidationSupportConfig.java index 4d8cef8db752..5fffa5d73c65 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/ValidationSupportConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/ValidationSupportConfig.java @@ -48,8 +48,10 @@ public DefaultProfileValidationSupport defaultProfileValidationSupport(FhirConte } @Bean - public InMemoryTerminologyServerValidationSupport inMemoryTerminologyServerValidationSupport(FhirContext theFhirContext, JpaStorageSettings theStorageSettings) { - InMemoryTerminologyServerValidationSupport retVal = new InMemoryTerminologyServerValidationSupport(theFhirContext); + public InMemoryTerminologyServerValidationSupport inMemoryTerminologyServerValidationSupport( + FhirContext theFhirContext, JpaStorageSettings theStorageSettings) { + InMemoryTerminologyServerValidationSupport retVal = + new InMemoryTerminologyServerValidationSupport(theFhirContext); retVal.setIssueSeverityForCodeDisplayMismatch(theStorageSettings.getIssueSeverityForCodeDisplayMismatch()); return retVal; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/ValueSetOperationProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/ValueSetOperationProvider.java index b022da51caf9..99189c921546 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/ValueSetOperationProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/ValueSetOperationProvider.java @@ -152,7 +152,7 @@ protected IFhirResourceDaoValueSet getDao() { @OperationParam(name = RESULT, typeName = "boolean", min = 1), @OperationParam(name = MESSAGE, typeName = "string"), @OperationParam(name = DISPLAY, typeName = "string"), - @OperationParam(name = SOURCE_DETAILS, typeName = "string") + @OperationParam(name = SOURCE_DETAILS, typeName = "string") }) public IBaseParameters validateCode( HttpServletRequest theServletRequest, @@ -338,7 +338,8 @@ public static IBaseParameters toValidateCodeResult(FhirContext theContext, CodeV ParametersUtil.addParameterToParametersString(theContext, retVal, DISPLAY, theResult.getDisplay()); } if (isNotBlank(theResult.getSourceDetails())) { - ParametersUtil.addParameterToParametersString(theContext, retVal, SOURCE_DETAILS, theResult.getSourceDetails()); + ParametersUtil.addParameterToParametersString( + theContext, retVal, SOURCE_DETAILS, theResult.getSourceDetails()); } return retVal; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java index ec4f3c9d4110..615c6fe57df1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java @@ -1028,11 +1028,8 @@ private void expandValueSetHandleIncludeOrExclude( new VersionConvertor_40_50(new BaseAdvisor_40_50()), "ValueSet"); org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent includeOrExclude = ValueSet40_50.convertConceptSetComponent(theIncludeOrExclude); - myInMemoryTerminologyServerValidationSupport - .expandValueSetIncludeOrExclude( - new ValidationSupportContext(provideValidationSupport()), - consumer, - includeOrExclude); + myInMemoryTerminologyServerValidationSupport.expandValueSetIncludeOrExclude( + new ValidationSupportContext(provideValidationSupport()), consumer, includeOrExclude); } catch (InMemoryTerminologyServerValidationSupport.ExpansionCouldNotBeCompletedInternallyException e) { if (theExpansionOptions != null && !theExpansionOptions.isFailOnMissingCodeSystem() @@ -2077,7 +2074,12 @@ protected IValidationSupport.CodeValidationResult validateCodeIsInPreExpandedVal String expectedDisplay = concepts.get(0).getDisplay(); return InMemoryTerminologyServerValidationSupport.createResultForDisplayMismatch( - myContext, theCode, theDisplay, expectedDisplay, systemVersion, myStorageSettings.getIssueSeverityForCodeDisplayMismatch()); + myContext, + theCode, + theDisplay, + expectedDisplay, + systemVersion, + myStorageSettings.getIssueSeverityForCodeDisplayMismatch()); } if (!concepts.isEmpty()) { @@ -2713,7 +2715,12 @@ public IValidationSupport.CodeValidationResult validateCode( return new CodeValidationResult().setCode(code.getCode()).setDisplay(code.getDisplay()); } else { return InMemoryTerminologyServerValidationSupport.createResultForDisplayMismatch( - myContext, theCode, theDisplay, code.getDisplay(), code.getSystemVersion(), myStorageSettings.getIssueSeverityForCodeDisplayMismatch()); + myContext, + theCode, + theDisplay, + code.getDisplay(), + code.getSystemVersion(), + myStorageSettings.getIssueSeverityForCodeDisplayMismatch()); } } @@ -2751,14 +2758,13 @@ IValidationSupport.CodeValidationResult validateCodeInValueSet( if (retVal == null) { if (valueSet != null) { - retVal = myInMemoryTerminologyServerValidationSupport - .validateCodeInValueSet( - theValidationSupportContext, - theValidationOptions, - theCodeSystem, - theCode, - theDisplay, - valueSet); + retVal = myInMemoryTerminologyServerValidationSupport.validateCodeInValueSet( + theValidationSupportContext, + theValidationOptions, + theCodeSystem, + theCode, + theDisplay, + valueSet); } else { String append = " - Unable to locate ValueSet[" + theValueSetUrl + "]"; retVal = createFailureCodeValidationResult(theCodeSystem, theCode, null, append); diff --git a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/config/MdmSurvivorshipConfig.java b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/config/MdmSurvivorshipConfig.java index 9b9083cdbab0..81f79c2e86ea 100644 --- a/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/config/MdmSurvivorshipConfig.java +++ b/hapi-fhir-jpaserver-mdm/src/main/java/ca/uhn/fhir/jpa/mdm/config/MdmSurvivorshipConfig.java @@ -1,3 +1,22 @@ +/*- + * #%L + * HAPI FHIR JPA Server - Master Data Management + * %% + * Copyright (C) 2014 - 2023 Smile CDR, Inc. + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ package ca.uhn.fhir.jpa.mdm.config; import ca.uhn.fhir.context.FhirContext; diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java index 262bfeecb98e..32a22686730c 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java @@ -20,7 +20,6 @@ package ca.uhn.fhir.jpa.searchparam.extractor; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.StorageSettings; import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/svc/MdmSurvivorshipSvcImpl.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/svc/MdmSurvivorshipSvcImpl.java index 23c299736f3d..99b56c5215f0 100644 --- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/svc/MdmSurvivorshipSvcImpl.java +++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/svc/MdmSurvivorshipSvcImpl.java @@ -1,6 +1,6 @@ /*- * #%L - * HAPI FHIR JPA Server - Master Data Management + * HAPI FHIR - Master Data Management * %% * Copyright (C) 2014 - 2023 Smile CDR, Inc. * %% diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/config/JpaStorageSettings.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/config/JpaStorageSettings.java index 6ee1677fbcfa..caeefe7d4c79 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/config/JpaStorageSettings.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/config/JpaStorageSettings.java @@ -332,8 +332,8 @@ public class JpaStorageSettings extends StorageSettings { * @since 7.0.0 */ @Nonnull - private IValidationSupport.IssueSeverity myIssueSeverityForCodeDisplayMismatch = IValidationSupport.IssueSeverity.WARNING; - + private IValidationSupport.IssueSeverity myIssueSeverityForCodeDisplayMismatch = + IValidationSupport.IssueSeverity.WARNING; /** * Constructor @@ -2351,8 +2351,10 @@ public IValidationSupport.IssueSeverity getIssueSeverityForCodeDisplayMismatch() * @param theIssueSeverityForCodeDisplayMismatch The severity. Must not be {@literal null}. * @since 7.0.0 */ - public void setIssueSeverityForCodeDisplayMismatch(@Nonnull IValidationSupport.IssueSeverity theIssueSeverityForCodeDisplayMismatch) { - Validate.notNull(theIssueSeverityForCodeDisplayMismatch, "theIssueSeverityForCodeDisplayMismatch must not be null"); + public void setIssueSeverityForCodeDisplayMismatch( + @Nonnull IValidationSupport.IssueSeverity theIssueSeverityForCodeDisplayMismatch) { + Validate.notNull( + theIssueSeverityForCodeDisplayMismatch, "theIssueSeverityForCodeDisplayMismatch must not be null"); myIssueSeverityForCodeDisplayMismatch = theIssueSeverityForCodeDisplayMismatch; } diff --git a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/models/TestResource.java b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/models/TestResource.java index 46b8604951ac..a19f9d0ed509 100644 --- a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/models/TestResource.java +++ b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/models/TestResource.java @@ -1,3 +1,22 @@ +/*- + * #%L + * HAPI FHIR Test Utilities + * %% + * Copyright (C) 2014 - 2023 Smile CDR, Inc. + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ package ca.uhn.fhir.models; import org.springframework.core.io.AbstractResource; 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 721fa0fbcef0..935099d573d9 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 @@ -27,8 +27,6 @@ import org.hl7.fhir.r5.model.Enumerations; import org.hl7.fhir.utilities.validation.ValidationMessage; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -39,6 +37,8 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import static org.apache.commons.lang3.StringUtils.contains; import static org.apache.commons.lang3.StringUtils.defaultString; @@ -56,1311 +56,1318 @@ */ @SuppressWarnings("EnhancedSwitchMigration") public class InMemoryTerminologyServerValidationSupport implements IValidationSupport { - private static final String OUR_PIPE_CHARACTER = "|"; - private final FhirContext myCtx; - private final VersionCanonicalizer myVersionCanonicalizer; - private IssueSeverity myIssueSeverityForCodeDisplayMismatch = IssueSeverity.WARNING; - - /** - * Constructor - * - * @param theCtx A FhirContext for the FHIR version being validated - */ - public InMemoryTerminologyServerValidationSupport(FhirContext theCtx) { - Validate.notNull(theCtx, "theCtx must not be null"); - myCtx = theCtx; - myVersionCanonicalizer = new VersionCanonicalizer(theCtx); - } - - /** - * This setting controls the validation issue severity to report when a code validation - * finds that the code is present in the given CodeSystem, but the display name being - * validated doesn't match the expected value(s). Defaults to - * {@link ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity#WARNING}. Set this - * value to {@link ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity#INFORMATION} - * if you don't want to see display name validation issues at all in resource validation - * outcomes. - * - * @since 7.0.0 - */ - public IssueSeverity getIssueSeverityForCodeDisplayMismatch() { - return myIssueSeverityForCodeDisplayMismatch; - } - - /** - * This setting controls the validation issue severity to report when a code validation - * finds that the code is present in the given CodeSystem, but the display name being - * validated doesn't match the expected value(s). Defaults to - * {@link ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity#WARNING}. Set this - * value to {@link ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity#INFORMATION} - * if you don't want to see display name validation issues at all in resource validation - * outcomes. - * - * @param theIssueSeverityForCodeDisplayMismatch The severity. Must not be {@literal null}. - * @since 7.0.0 - */ - public void setIssueSeverityForCodeDisplayMismatch(@Nonnull IssueSeverity theIssueSeverityForCodeDisplayMismatch) { - Validate.notNull(theIssueSeverityForCodeDisplayMismatch, "theIssueSeverityForCodeDisplayMismatch must not be null"); - myIssueSeverityForCodeDisplayMismatch = theIssueSeverityForCodeDisplayMismatch; - } - - @Override - public FhirContext getFhirContext() { - return myCtx; - } - - @Override - public ValueSetExpansionOutcome expandValueSet( - ValidationSupportContext theValidationSupportContext, - ValueSetExpansionOptions theExpansionOptions, - @Nonnull IBaseResource theValueSetToExpand) { - return expandValueSet(theValidationSupportContext, theValueSetToExpand, null, null); - } - - private ValueSetExpansionOutcome expandValueSet( - ValidationSupportContext theValidationSupportContext, - IBaseResource theValueSetToExpand, - String theWantSystemAndVersion, - String theWantCode) { - org.hl7.fhir.r5.model.ValueSet expansionR5; - try { - expansionR5 = expandValueSetToCanonical( - theValidationSupportContext, theValueSetToExpand, theWantSystemAndVersion, theWantCode); - } catch (ExpansionCouldNotBeCompletedInternallyException e) { - return new ValueSetExpansionOutcome(e.getMessage()); - } - if (expansionR5 == null) { - return null; - } - - IBaseResource expansion; - switch (myCtx.getVersion().getVersion()) { - case DSTU2: { - org.hl7.fhir.r4.model.ValueSet expansionR4 = (org.hl7.fhir.r4.model.ValueSet) - VersionConvertorFactory_40_50.convertResource(expansionR5, new BaseAdvisor_40_50(false)); - expansion = myVersionCanonicalizer.valueSetFromCanonical(expansionR4); - break; - } - case DSTU2_HL7ORG: { - expansion = VersionConvertorFactory_10_50.convertResource(expansionR5, new BaseAdvisor_10_50(false)); - break; - } - case DSTU3: { - expansion = VersionConvertorFactory_30_50.convertResource(expansionR5, new BaseAdvisor_30_50(false)); - break; - } - case R4: { - expansion = VersionConvertorFactory_40_50.convertResource(expansionR5, new BaseAdvisor_40_50(false)); - break; - } - case R4B: { - expansion = VersionConvertorFactory_43_50.convertResource(expansionR5, new BaseAdvisor_43_50(false)); - break; - } - case R5: { - expansion = expansionR5; - break; - } - case DSTU2_1: - default: - throw new IllegalArgumentException(Msg.code(697) + "Can not handle version: " - + myCtx.getVersion().getVersion()); - } - - return new ValueSetExpansionOutcome(expansion); - } - - private org.hl7.fhir.r5.model.ValueSet expandValueSetToCanonical( - ValidationSupportContext theValidationSupportContext, - IBaseResource theValueSetToExpand, - @Nullable String theWantSystemUrlAndVersion, - @Nullable String theWantCode) - throws ExpansionCouldNotBeCompletedInternallyException { - org.hl7.fhir.r5.model.ValueSet expansionR5; - switch (getFhirVersionEnum( - theValidationSupportContext.getRootValidationSupport().getFhirContext(), theValueSetToExpand)) { - case DSTU2: { - expansionR5 = expandValueSetDstu2( - theValidationSupportContext, - (ca.uhn.fhir.model.dstu2.resource.ValueSet) theValueSetToExpand, - theWantSystemUrlAndVersion, - theWantCode); - break; - } - case DSTU2_HL7ORG: { - expansionR5 = expandValueSetDstu2Hl7Org( - theValidationSupportContext, - (ValueSet) theValueSetToExpand, - theWantSystemUrlAndVersion, - theWantCode); - break; - } - case DSTU3: { - expansionR5 = expandValueSetDstu3( - theValidationSupportContext, - (org.hl7.fhir.dstu3.model.ValueSet) theValueSetToExpand, - theWantSystemUrlAndVersion, - theWantCode); - break; - } - case R4: { - expansionR5 = expandValueSetR4( - theValidationSupportContext, - (org.hl7.fhir.r4.model.ValueSet) theValueSetToExpand, - theWantSystemUrlAndVersion, - theWantCode); - break; - } - case R4B: { - expansionR5 = expandValueSetR4B( - theValidationSupportContext, - (org.hl7.fhir.r4b.model.ValueSet) theValueSetToExpand, - theWantSystemUrlAndVersion, - theWantCode); - break; - } - case R5: { - expansionR5 = expandValueSetR5( - theValidationSupportContext, - (org.hl7.fhir.r5.model.ValueSet) theValueSetToExpand, - theWantSystemUrlAndVersion, - theWantCode); - break; - } - case DSTU2_1: - default: - throw new IllegalArgumentException(Msg.code(698) + "Can not handle version: " - + myCtx.getVersion().getVersion()); - } - - return expansionR5; - } - - @Override - public CodeValidationResult validateCodeInValueSet( - ValidationSupportContext theValidationSupportContext, - ConceptValidationOptions theOptions, - String theCodeSystemUrlAndVersion, - String theCode, - String theDisplay, - @Nonnull IBaseResource theValueSet) { - org.hl7.fhir.r5.model.ValueSet expansion; - String vsUrl = CommonCodeSystemsTerminologyService.getValueSetUrl(getFhirContext(), theValueSet); - try { - expansion = expandValueSetToCanonical( - theValidationSupportContext, theValueSet, theCodeSystemUrlAndVersion, theCode); - } catch (ExpansionCouldNotBeCompletedInternallyException e) { - CodeValidationResult codeValidationResult = new CodeValidationResult(); - codeValidationResult.setSeverityCode("error"); - - String msg = "Failed to expand ValueSet '" + vsUrl + "' (in-memory). Could not validate code " - + theCodeSystemUrlAndVersion + "#" + theCode; - if (e.getMessage() != null) { - msg += ". Error was: " + e.getMessage(); - } - - codeValidationResult.setMessage(msg); - return codeValidationResult; - } - - if (expansion == null) { - return null; - } - - return validateCodeInExpandedValueSet( - theValidationSupportContext, - theOptions, - theCodeSystemUrlAndVersion, - theCode, - theDisplay, - expansion, - vsUrl); - } - - @Override - @Nullable - public CodeValidationResult validateCode( - @Nonnull ValidationSupportContext theValidationSupportContext, - @Nonnull ConceptValidationOptions theOptions, - String theCodeSystem, - String theCode, - String theDisplay, - String theValueSetUrl) { - IBaseResource vs; - if (isNotBlank(theValueSetUrl)) { - vs = theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrl); - if (vs == null) { - 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: - 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))); - break; - case DSTU3: - 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: - 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 R4B: - if (codeSystemVersion != null) { - vs = new org.hl7.fhir.r4b.model.ValueSet() - .setCompose(new org.hl7.fhir.r4b.model.ValueSet.ValueSetComposeComponent() - .addInclude(new org.hl7.fhir.r4b.model.ValueSet.ConceptSetComponent() - .setSystem(codeSystemUrl) - .setVersion(codeSystemVersion))); - } else { - vs = new org.hl7.fhir.r4b.model.ValueSet() - .setCompose(new org.hl7.fhir.r4b.model.ValueSet.ValueSetComposeComponent() - .addInclude(new org.hl7.fhir.r4b.model.ValueSet.ConceptSetComponent() - .setSystem(theCodeSystem))); - } - break; - case R5: - 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_1: - default: - throw new IllegalArgumentException(Msg.code(699) + "Can not handle version: " - + myCtx.getVersion().getVersion()); - } - } - - ValueSetExpansionOutcome valueSetExpansionOutcome = - expandValueSet(theValidationSupportContext, vs, theCodeSystem, theCode); - if (valueSetExpansionOutcome == null) { - return null; - } - - if (valueSetExpansionOutcome.getError() != null) { - return new CodeValidationResult() - .setSeverity(IssueSeverity.ERROR) - .setMessage(valueSetExpansionOutcome.getError()); - } - - IBaseResource expansion = valueSetExpansionOutcome.getValueSet(); - return validateCodeInExpandedValueSet( - theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, expansion, theValueSetUrl); - } - - private CodeValidationResult validateCodeInExpandedValueSet( - ValidationSupportContext theValidationSupportContext, - ConceptValidationOptions theOptions, - String theCodeSystemUrlAndVersionToValidate, - String theCodeToValidate, - String theDisplayToValidate, - IBaseResource theExpansion, - String theValueSetUrl) { - assert theExpansion != null; - - boolean caseSensitive = true; - IBaseResource codeSystemToValidateResource = null; - if (!theOptions.isInferSystem() && isNotBlank(theCodeSystemUrlAndVersionToValidate)) { - codeSystemToValidateResource = theValidationSupportContext - .getRootValidationSupport() - .fetchCodeSystem(theCodeSystemUrlAndVersionToValidate); - } - - List codes = new ArrayList<>(); - switch (getFhirVersionEnum( - theValidationSupportContext.getRootValidationSupport().getFhirContext(), theExpansion)) { - case DSTU2: { - ca.uhn.fhir.model.dstu2.resource.ValueSet expansionVs = - (ca.uhn.fhir.model.dstu2.resource.ValueSet) theExpansion; - List contains = - expansionVs.getExpansion().getContains(); - flattenAndConvertCodesDstu2(contains, codes); - break; - } - case DSTU2_HL7ORG: { - ValueSet expansionVs = (ValueSet) theExpansion; - List contains = - expansionVs.getExpansion().getContains(); - flattenAndConvertCodesDstu2Hl7Org(contains, codes); - 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); - 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); - break; - } - case R4B: { - org.hl7.fhir.r4b.model.ValueSet expansionVs = (org.hl7.fhir.r4b.model.ValueSet) theExpansion; - List contains = - expansionVs.getExpansion().getContains(); - flattenAndConvertCodesR4B(contains, codes); - 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); - break; - } - case DSTU2_1: - default: - throw new IllegalArgumentException(Msg.code(700) + "Can not handle version: " - + myCtx.getVersion().getVersion()); - } - - String codeSystemResourceName = null; - String codeSystemResourceVersion = null; - String codeSystemResourceContentMode = null; - if (codeSystemToValidateResource != null) { - switch (getFhirVersionEnum( - theValidationSupportContext.getRootValidationSupport().getFhirContext(), - codeSystemToValidateResource)) { - case DSTU2: - case DSTU2_HL7ORG: { - caseSensitive = true; - break; - } - case DSTU3: { - org.hl7.fhir.dstu3.model.CodeSystem systemDstu3 = - (org.hl7.fhir.dstu3.model.CodeSystem) codeSystemToValidateResource; - caseSensitive = systemDstu3.getCaseSensitive(); - 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) codeSystemToValidateResource; - caseSensitive = systemR4.getCaseSensitive(); - codeSystemResourceName = systemR4.getName(); - codeSystemResourceVersion = systemR4.getVersion(); - codeSystemResourceContentMode = systemR4.getContentElement().getValueAsString(); - break; - } - case R4B: { - org.hl7.fhir.r4b.model.CodeSystem systemR4B = - (org.hl7.fhir.r4b.model.CodeSystem) codeSystemToValidateResource; - caseSensitive = systemR4B.getCaseSensitive(); - codeSystemResourceName = systemR4B.getName(); - codeSystemResourceVersion = systemR4B.getVersion(); - codeSystemResourceContentMode = - systemR4B.getContentElement().getValueAsString(); - break; - } - case R5: { - CodeSystem systemR5 = (CodeSystem) codeSystemToValidateResource; - caseSensitive = systemR5.getCaseSensitive(); - codeSystemResourceName = systemR5.getName(); - codeSystemResourceVersion = systemR5.getVersion(); - codeSystemResourceContentMode = systemR5.getContentElement().getValueAsString(); - break; - } - case DSTU2_1: - default: - throw new IllegalArgumentException(Msg.code(701) + "Can not handle version: " - + myCtx.getVersion().getVersion()); - } - } - - String codeSystemUrlToValidate = null; - String codeSystemVersionToValidate = null; - if (theCodeSystemUrlAndVersionToValidate != null) { - int versionIndex = theCodeSystemUrlAndVersionToValidate.indexOf("|"); - if (versionIndex > -1) { - codeSystemUrlToValidate = theCodeSystemUrlAndVersionToValidate.substring(0, versionIndex); - codeSystemVersionToValidate = theCodeSystemUrlAndVersionToValidate.substring(versionIndex + 1); - } else { - codeSystemUrlToValidate = theCodeSystemUrlAndVersionToValidate; - } - } - for (FhirVersionIndependentConcept nextExpansionCode : codes) { - - boolean codeMatches; - if (caseSensitive) { - codeMatches = defaultString(theCodeToValidate).equals(nextExpansionCode.getCode()); - } else { - codeMatches = defaultString(theCodeToValidate).equalsIgnoreCase(nextExpansionCode.getCode()); - } - if (codeMatches) { - if (theOptions.isInferSystem() - || (nextExpansionCode.getSystem().equals(codeSystemUrlToValidate) - && (codeSystemVersionToValidate == null - || codeSystemVersionToValidate.equals(nextExpansionCode.getSystemVersion())))) { - String csVersion = codeSystemResourceVersion; - if (isNotBlank(nextExpansionCode.getSystemVersion())) { - csVersion = nextExpansionCode.getSystemVersion(); - } - if (!theOptions.isValidateDisplay() - || (isBlank(nextExpansionCode.getDisplay()) - || isBlank(theDisplayToValidate) - || nextExpansionCode.getDisplay().equals(theDisplayToValidate))) { - CodeValidationResult codeValidationResult = new CodeValidationResult() - .setCode(theCodeToValidate) - .setDisplay(nextExpansionCode.getDisplay()) - .setCodeSystemName(codeSystemResourceName) - .setCodeSystemVersion(csVersion); - if (isNotBlank(theValueSetUrl)) { - populateSourceDetailsForInMemoryExpansion(theValueSetUrl, codeValidationResult); - } - return codeValidationResult; - } else { - String messageAppend = ""; - if (isNotBlank(theValueSetUrl)) { - messageAppend = " for in-memory expansion of ValueSet: " + theValueSetUrl; - } - CodeValidationResult codeValidationResult = createResultForDisplayMismatch( - myCtx, - theCodeToValidate, - theDisplayToValidate, - nextExpansionCode.getDisplay(), - csVersion, - messageAppend, - getIssueSeverityForCodeDisplayMismatch()); - if (isNotBlank(theValueSetUrl)) { - populateSourceDetailsForInMemoryExpansion(theValueSetUrl, codeValidationResult); - } - return codeValidationResult; - } - } - } - } - - ValidationMessage.IssueSeverity severity; - String message; - if ("fragment".equals(codeSystemResourceContentMode)) { - severity = ValidationMessage.IssueSeverity.WARNING; - message = "Unknown code in fragment CodeSystem '" - + (isNotBlank(theCodeSystemUrlAndVersionToValidate) - ? theCodeSystemUrlAndVersionToValidate + "#" - : "") - + theCodeToValidate + "'"; - } else { - severity = ValidationMessage.IssueSeverity.ERROR; - message = "Unknown code '" - + (isNotBlank(theCodeSystemUrlAndVersionToValidate) - ? theCodeSystemUrlAndVersionToValidate + "#" - : "") - + theCodeToValidate + "'"; - } - if (isNotBlank(theValueSetUrl)) { - message += " for in-memory expansion of ValueSet '" + theValueSetUrl + "'"; - } - - return new CodeValidationResult().setSeverityCode(severity.toCode()).setMessage(message); - } - - @Override - public LookupCodeResult lookupCode( - ValidationSupportContext theValidationSupportContext, - String theSystem, - String theCode, - String theDisplayLanguage) { - CodeValidationResult codeValidationResult = validateCode( - theValidationSupportContext, new ConceptValidationOptions(), theSystem, theCode, null, null); - if (codeValidationResult == null) { - return null; - } - return codeValidationResult.asLookupCodeResult(theSystem, theCode); - } - - @Nullable - private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2Hl7Org( - ValidationSupportContext theValidationSupportContext, - ValueSet theInput, - @Nullable String theWantSystemUrlAndVersion, - @Nullable String theWantCode) - throws ExpansionCouldNotBeCompletedInternallyException { - org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) - VersionConvertorFactory_10_50.convertResource(theInput, new BaseAdvisor_10_50(false)); - return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode)); - } - - @Nullable - private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2( - ValidationSupportContext theValidationSupportContext, - ca.uhn.fhir.model.dstu2.resource.ValueSet theInput, - @Nullable String theWantSystemUrlAndVersion, - @Nullable String theWantCode) - throws ExpansionCouldNotBeCompletedInternallyException { - IParser parserRi = FhirContext.forCached(FhirVersionEnum.DSTU2_HL7ORG).newJsonParser(); - IParser parserHapi = FhirContext.forDstu2Cached().newJsonParser(); - - 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 = (org.hl7.fhir.r5.model.ValueSet) - VersionConvertorFactory_10_50.convertResource(valueSetRi, new BaseAdvisor_10_50(false)); - return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode)); - } - - @Override - public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) { - if (isBlank(theSystem)) { - return false; - } - - IBaseResource cs = - theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(theSystem); - - if (!myCtx.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU2_1)) { - return cs != null; - } - - if (cs != null) { - IPrimitiveType content = - getFhirContext().newTerser().getSingleValueOrNull(cs, "content", IPrimitiveType.class); - return !"not-present".equals(content.getValueAsString()); - } - - return false; - } - - @Override - public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) { - return isNotBlank(theValueSetUrl) - && theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrl) != null; - } - - private void addCodesDstu2Hl7Org( - List theSourceList, - List theTargetList) { - for (ValueSet.ConceptDefinitionComponent nextSource : theSourceList) { - CodeSystem.ConceptDefinitionComponent targetConcept = new CodeSystem.ConceptDefinitionComponent() - .setCode(nextSource.getCode()) - .setDisplay(nextSource.getDisplay()); - theTargetList.add(targetConcept); - addCodesDstu2Hl7Org(nextSource.getConcept(), targetConcept.getConcept()); - } - } - - private void addCodesDstu2( - List theSourceList, - List theTargetList) { - for (ca.uhn.fhir.model.dstu2.resource.ValueSet.CodeSystemConcept nextSource : theSourceList) { - CodeSystem.ConceptDefinitionComponent targetConcept = new CodeSystem.ConceptDefinitionComponent() - .setCode(nextSource.getCode()) - .setDisplay(nextSource.getDisplay()); - theTargetList.add(targetConcept); - addCodesDstu2(nextSource.getConcept(), targetConcept.getConcept()); - } - } - - @Nullable - private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu3( - ValidationSupportContext theValidationSupportContext, - org.hl7.fhir.dstu3.model.ValueSet theInput, - @Nullable String theWantSystemUrlAndVersion, - @Nullable String theWantCode) - throws ExpansionCouldNotBeCompletedInternallyException { - org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) - VersionConvertorFactory_30_50.convertResource(theInput, new BaseAdvisor_30_50(false)); - return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode)); - } - - @Nullable - private org.hl7.fhir.r5.model.ValueSet expandValueSetR4( - ValidationSupportContext theValidationSupportContext, - org.hl7.fhir.r4.model.ValueSet theInput, - @Nullable String theWantSystemUrlAndVersion, - @Nullable String theWantCode) - throws ExpansionCouldNotBeCompletedInternallyException { - org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) - VersionConvertorFactory_40_50.convertResource(theInput, new BaseAdvisor_40_50(false)); - return expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode); - } - - @Nullable - private org.hl7.fhir.r5.model.ValueSet expandValueSetR4B( - ValidationSupportContext theValidationSupportContext, - org.hl7.fhir.r4b.model.ValueSet theInput, - @Nullable String theWantSystemUrlAndVersion, - @Nullable String theWantCode) - throws ExpansionCouldNotBeCompletedInternallyException { - org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) - VersionConvertorFactory_43_50.convertResource(theInput, new BaseAdvisor_43_50(false)); - return expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode); - } - - @Nullable - private org.hl7.fhir.r5.model.ValueSet expandValueSetR5( - ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r5.model.ValueSet theInput) - throws ExpansionCouldNotBeCompletedInternallyException { - return expandValueSetR5(theValidationSupportContext, theInput, null, null); - } - - @Nullable - private org.hl7.fhir.r5.model.ValueSet expandValueSetR5( - ValidationSupportContext theValidationSupportContext, - org.hl7.fhir.r5.model.ValueSet theInput, - @Nullable String theWantSystemUrlAndVersion, - @Nullable String theWantCode) - throws ExpansionCouldNotBeCompletedInternallyException { - Set concepts = new HashSet<>(); - - expandValueSetR5IncludeOrExcludes( - theValidationSupportContext, - concepts, - theInput.getCompose().getInclude(), - true, - theWantSystemUrlAndVersion, - theWantCode); - expandValueSetR5IncludeOrExcludes( - theValidationSupportContext, - concepts, - theInput.getCompose().getExclude(), - false, - theWantSystemUrlAndVersion, - theWantCode); - - org.hl7.fhir.r5.model.ValueSet retVal = new org.hl7.fhir.r5.model.ValueSet(); - for (FhirVersionIndependentConcept next : concepts) { - org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent contains = - retVal.getExpansion().addContains(); - contains.setSystem(next.getSystem()); - contains.setCode(next.getCode()); - contains.setDisplay(next.getDisplay()); - contains.setVersion(next.getSystemVersion()); - } - - return retVal; - } - - /** - * Use with caution - this is not a stable API - * - * @since 5.6.0 - */ - public void expandValueSetIncludeOrExclude( - ValidationSupportContext theValidationSupportContext, - Consumer theConsumer, - org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent theIncludeOrExclude) - throws ExpansionCouldNotBeCompletedInternallyException { - expandValueSetR5IncludeOrExclude(theValidationSupportContext, theConsumer, null, null, theIncludeOrExclude); - } - - private void expandValueSetR5IncludeOrExcludes( - ValidationSupportContext theValidationSupportContext, - Set theConcepts, - List theComposeList, - boolean theComposeListIsInclude, - @Nullable String theWantSystemUrlAndVersion, - @Nullable String theWantCode) - throws ExpansionCouldNotBeCompletedInternallyException { - Consumer consumer = c -> { - if (theComposeListIsInclude) { - theConcepts.add(c); - } else { - theConcepts.remove(c); - } - }; - expandValueSetR5IncludeOrExcludes( - theValidationSupportContext, consumer, theComposeList, theWantSystemUrlAndVersion, theWantCode); - } - - private void expandValueSetR5IncludeOrExcludes( - ValidationSupportContext theValidationSupportContext, - Consumer theConsumer, - List theComposeList, - @Nullable String theWantSystemUrlAndVersion, - @Nullable String theWantCode) - throws ExpansionCouldNotBeCompletedInternallyException { - ExpansionCouldNotBeCompletedInternallyException caughtException = null; - for (org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent nextInclude : theComposeList) { - try { - boolean outcome = expandValueSetR5IncludeOrExclude( - theValidationSupportContext, theConsumer, theWantSystemUrlAndVersion, theWantCode, nextInclude); - if (isNotBlank(theWantCode)) { - if (outcome) { - return; - } - } - } catch (ExpansionCouldNotBeCompletedInternallyException e) { - if (isBlank(theWantCode)) { - throw e; - } else { - caughtException = e; - } - } - } - if (caughtException != null) { - throw caughtException; - } - } - - /** - * Returns true if at least one code was added - */ - private boolean expandValueSetR5IncludeOrExclude( - ValidationSupportContext theValidationSupportContext, - Consumer theConsumer, - @Nullable String theWantSystemUrlAndVersion, - @Nullable String theWantCode, - org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent theInclude) - throws ExpansionCouldNotBeCompletedInternallyException { - - String wantSystemUrl = null; - String wantSystemVersion = null; - - if (theWantSystemUrlAndVersion != null) { - int versionIndex = theWantSystemUrlAndVersion.indexOf(OUR_PIPE_CHARACTER); - if (versionIndex > -1) { - wantSystemUrl = theWantSystemUrlAndVersion.substring(0, versionIndex); - wantSystemVersion = theWantSystemUrlAndVersion.substring(versionIndex + 1); - } else { - wantSystemUrl = theWantSystemUrlAndVersion; - } - } - - String includeOrExcludeConceptSystemUrl = theInclude.getSystem(); - String includeOrExcludeConceptSystemVersion = theInclude.getVersion(); - - Function codeSystemLoader = newCodeSystemLoader(theValidationSupportContext); - Function valueSetLoader = - newValueSetLoader(theValidationSupportContext); - - List nextCodeList = new ArrayList<>(); - CodeSystem includeOrExcludeSystemResource = null; - - if (isNotBlank(includeOrExcludeConceptSystemUrl)) { - - includeOrExcludeConceptSystemVersion = optionallyPopulateVersionFromUrl( - includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion); - includeOrExcludeConceptSystemUrl = substringBefore(includeOrExcludeConceptSystemUrl, OUR_PIPE_CHARACTER); - - if (wantSystemUrl != null && !wantSystemUrl.equals(includeOrExcludeConceptSystemUrl)) { - return false; - } - - if (wantSystemVersion != null && !wantSystemVersion.equals(includeOrExcludeConceptSystemVersion)) { - return false; - } - - String loadedCodeSystemUrl; - if (includeOrExcludeConceptSystemVersion != null) { - loadedCodeSystemUrl = - includeOrExcludeConceptSystemUrl + OUR_PIPE_CHARACTER + includeOrExcludeConceptSystemVersion; - } else { - loadedCodeSystemUrl = includeOrExcludeConceptSystemUrl; - } - - includeOrExcludeSystemResource = codeSystemLoader.apply(loadedCodeSystemUrl); - - Set wantCodes; - if (theInclude.getConcept().isEmpty()) { - wantCodes = null; - } else { - wantCodes = - theInclude.getConcept().stream().map(t -> t.getCode()).collect(Collectors.toSet()); - } - - boolean ableToHandleCode = false; - String failureMessage = null; - FailureType failureType = FailureType.OTHER; - - if (includeOrExcludeSystemResource == null - || includeOrExcludeSystemResource.getContent() == Enumerations.CodeSystemContentMode.NOTPRESENT) { - - if (theWantCode != null) { - if (theValidationSupportContext - .getRootValidationSupport() - .isCodeSystemSupported(theValidationSupportContext, includeOrExcludeConceptSystemUrl)) { - LookupCodeResult lookup = theValidationSupportContext - .getRootValidationSupport() - .lookupCode( - theValidationSupportContext, - includeOrExcludeConceptSystemUrl, - theWantCode, - null); - if (lookup != null) { - ableToHandleCode = true; - if (lookup.isFound()) { - CodeSystem.ConceptDefinitionComponent conceptDefinition = - new CodeSystem.ConceptDefinitionComponent() - .addConcept() - .setCode(theWantCode) - .setDisplay(lookup.getCodeDisplay()); - List codesList = - Collections.singletonList(conceptDefinition); - addCodes( - includeOrExcludeConceptSystemUrl, - includeOrExcludeConceptSystemVersion, - codesList, - nextCodeList, - wantCodes); - } - } - } else { - - /* - * If we're doing an expansion specifically looking for a single code, that means we're validating that code. - * In the case where we have a ValueSet that explicitly enumerates a collection of codes - * (via ValueSet.compose.include.code) in a code system that is unknown we'll assume the code is valid - * even if we can't find the CodeSystem. This is a compromise obviously, since it would be ideal for - * CodeSystems to always be known, but realistically there are always going to be CodeSystems that - * can't be supplied because of copyright issues, or because they are grammar based. Allowing a VS to - * enumerate a set of good codes for them is a nice compromise there. - */ - if (Objects.equals(theInclude.getSystem(), theWantSystemUrlAndVersion)) { - Optional - matchingEnumeratedConcept = theInclude.getConcept().stream() - .filter(t -> Objects.equals(t.getCode(), theWantCode)) - .findFirst(); - - // If the ValueSet.compose.include has no individual concepts in it, and - // we can't find the actual referenced CodeSystem, we have no choice - // but to fail - if (!theInclude.getConcept().isEmpty()) { - ableToHandleCode = true; - } else { - failureMessage = getFailureMessageForMissingOrUnusableCodeSystem( - includeOrExcludeSystemResource, loadedCodeSystemUrl); - } - - if (matchingEnumeratedConcept.isPresent()) { - CodeSystem.ConceptDefinitionComponent conceptDefinition = - new CodeSystem.ConceptDefinitionComponent() - .addConcept() - .setCode(theWantCode) - .setDisplay(matchingEnumeratedConcept - .get() - .getDisplay()); - List codesList = - Collections.singletonList(conceptDefinition); - addCodes( - includeOrExcludeConceptSystemUrl, - includeOrExcludeConceptSystemVersion, - codesList, - nextCodeList, - wantCodes); - } - } - } - } else { - if (isNotBlank(theInclude.getSystem()) - && !theInclude.getConcept().isEmpty() - && theInclude.getFilter().isEmpty() - && theInclude.getValueSet().isEmpty()) { - theInclude.getConcept().stream() - .map(t -> new FhirVersionIndependentConcept( - theInclude.getSystem(), t.getCode(), t.getDisplay(), theInclude.getVersion())) - .forEach(t -> nextCodeList.add(t)); - ableToHandleCode = true; - } - - if (!ableToHandleCode) { - failureMessage = getFailureMessageForMissingOrUnusableCodeSystem( - includeOrExcludeSystemResource, loadedCodeSystemUrl); - } - } - - } else { - ableToHandleCode = true; - } - - if (!ableToHandleCode) { - if (includeOrExcludeSystemResource == null && failureMessage == null) { - failureMessage = getFailureMessageForMissingOrUnusableCodeSystem( - includeOrExcludeSystemResource, loadedCodeSystemUrl); - } - - if (includeOrExcludeSystemResource == null) { - failureType = FailureType.UNKNOWN_CODE_SYSTEM; - } - - throw new ExpansionCouldNotBeCompletedInternallyException(Msg.code(702) + failureMessage, failureType); - } - - if (includeOrExcludeSystemResource != null - && includeOrExcludeSystemResource.getContent() != Enumerations.CodeSystemContentMode.NOTPRESENT) { - addCodes( - includeOrExcludeConceptSystemUrl, - includeOrExcludeConceptSystemVersion, - includeOrExcludeSystemResource.getConcept(), - nextCodeList, - wantCodes); - } - } - - for (CanonicalType nextValueSetInclude : theInclude.getValueSet()) { - org.hl7.fhir.r5.model.ValueSet vs = valueSetLoader.apply(nextValueSetInclude.getValueAsString()); - if (vs != null) { - org.hl7.fhir.r5.model.ValueSet subExpansion = - expandValueSetR5(theValidationSupportContext, vs, theWantSystemUrlAndVersion, theWantCode); - if (subExpansion == null) { - throw new ExpansionCouldNotBeCompletedInternallyException( - Msg.code(703) + "Failed to expand ValueSet: " + nextValueSetInclude.getValueAsString(), - FailureType.OTHER); - } - for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : - subExpansion.getExpansion().getContains()) { - nextCodeList.add(new FhirVersionIndependentConcept( - next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); - } - } - } - - boolean retVal = false; - - for (FhirVersionIndependentConcept next : nextCodeList) { - if (includeOrExcludeSystemResource != null && theWantCode != null) { - boolean matches; - if (includeOrExcludeSystemResource.getCaseSensitive()) { - matches = theWantCode.equals(next.getCode()); - } else { - matches = theWantCode.equalsIgnoreCase(next.getCode()); - } - if (!matches) { - continue; - } - } - - theConsumer.accept(next); - retVal = true; - } - - return retVal; - } - - private Function newValueSetLoader( - ValidationSupportContext theValidationSupportContext) { - switch (myCtx.getVersion().getVersion()) { - case DSTU2: - case DSTU2_HL7ORG: - return t -> { - IBaseResource vs = theValidationSupportContext - .getRootValidationSupport() - .fetchValueSet(t); - if (vs instanceof ca.uhn.fhir.model.dstu2.resource.ValueSet) { - IParser parserRi = FhirContext.forCached(FhirVersionEnum.DSTU2_HL7ORG) - .newJsonParser(); - IParser parserHapi = FhirContext.forDstu2Cached().newJsonParser(); - ca.uhn.fhir.model.dstu2.resource.ValueSet valueSet = - (ca.uhn.fhir.model.dstu2.resource.ValueSet) vs; - org.hl7.fhir.dstu2.model.ValueSet valueSetRi = parserRi.parseResource( - org.hl7.fhir.dstu2.model.ValueSet.class, parserHapi.encodeResourceToString(valueSet)); - return (org.hl7.fhir.r5.model.ValueSet) - VersionConvertorFactory_10_50.convertResource(valueSetRi, new BaseAdvisor_10_50(false)); - } else { - org.hl7.fhir.dstu2.model.ValueSet valueSet = - (org.hl7.fhir.dstu2.model.ValueSet) theValidationSupportContext - .getRootValidationSupport() - .fetchValueSet(t); - return (org.hl7.fhir.r5.model.ValueSet) - VersionConvertorFactory_10_50.convertResource(valueSet, new BaseAdvisor_10_50(false)); - } - }; - case DSTU3: - return t -> { - org.hl7.fhir.dstu3.model.ValueSet valueSet = - (org.hl7.fhir.dstu3.model.ValueSet) theValidationSupportContext - .getRootValidationSupport() - .fetchValueSet(t); - return (org.hl7.fhir.r5.model.ValueSet) - VersionConvertorFactory_30_50.convertResource(valueSet, new BaseAdvisor_30_50(false)); - }; - case R4: - return t -> { - org.hl7.fhir.r4.model.ValueSet valueSet = - (org.hl7.fhir.r4.model.ValueSet) theValidationSupportContext - .getRootValidationSupport() - .fetchValueSet(t); - return (org.hl7.fhir.r5.model.ValueSet) - VersionConvertorFactory_40_50.convertResource(valueSet, new BaseAdvisor_40_50(false)); - }; - case R4B: - return t -> { - org.hl7.fhir.r4b.model.ValueSet valueSet = - (org.hl7.fhir.r4b.model.ValueSet) theValidationSupportContext - .getRootValidationSupport() - .fetchValueSet(t); - return (org.hl7.fhir.r5.model.ValueSet) - VersionConvertorFactory_43_50.convertResource(valueSet, new BaseAdvisor_43_50(false)); - }; - default: - case DSTU2_1: - case R5: - return t -> (org.hl7.fhir.r5.model.ValueSet) - theValidationSupportContext.getRootValidationSupport().fetchValueSet(t); - } - } - - private Function newCodeSystemLoader(ValidationSupportContext theValidationSupportContext) { - switch (myCtx.getVersion().getVersion()) { - case DSTU2: - case DSTU2_HL7ORG: - return t -> { - IBaseResource codeSystem = theValidationSupportContext - .getRootValidationSupport() - .fetchCodeSystem(t); - CodeSystem retVal = null; - if (codeSystem != null) { - retVal = new CodeSystem(); - if (codeSystem instanceof ca.uhn.fhir.model.dstu2.resource.ValueSet) { - ca.uhn.fhir.model.dstu2.resource.ValueSet codeSystemCasted = - (ca.uhn.fhir.model.dstu2.resource.ValueSet) codeSystem; - retVal.setUrl(codeSystemCasted.getUrl()); - addCodesDstu2(codeSystemCasted.getCodeSystem().getConcept(), retVal.getConcept()); - } else { - org.hl7.fhir.dstu2.model.ValueSet codeSystemCasted = - (org.hl7.fhir.dstu2.model.ValueSet) codeSystem; - retVal.setUrl(codeSystemCasted.getUrl()); - addCodesDstu2Hl7Org(codeSystemCasted.getCodeSystem().getConcept(), retVal.getConcept()); - } - } - return retVal; - }; - case DSTU3: - return t -> { - org.hl7.fhir.dstu3.model.CodeSystem codeSystem = - (org.hl7.fhir.dstu3.model.CodeSystem) theValidationSupportContext - .getRootValidationSupport() - .fetchCodeSystem(t); - return (CodeSystem) - VersionConvertorFactory_30_50.convertResource(codeSystem, new BaseAdvisor_30_50(false)); - }; - case R4: - return t -> { - org.hl7.fhir.r4.model.CodeSystem codeSystem = - (org.hl7.fhir.r4.model.CodeSystem) theValidationSupportContext - .getRootValidationSupport() - .fetchCodeSystem(t); - return (CodeSystem) - VersionConvertorFactory_40_50.convertResource(codeSystem, new BaseAdvisor_40_50(false)); - }; - case R4B: - return t -> { - org.hl7.fhir.r4b.model.CodeSystem codeSystem = - (org.hl7.fhir.r4b.model.CodeSystem) theValidationSupportContext - .getRootValidationSupport() - .fetchCodeSystem(t); - return (CodeSystem) - VersionConvertorFactory_43_50.convertResource(codeSystem, new BaseAdvisor_43_50(false)); - }; - case DSTU2_1: - case R5: - default: - return t -> (org.hl7.fhir.r5.model.CodeSystem) - theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); - } - } - - private String getFailureMessageForMissingOrUnusableCodeSystem( - CodeSystem includeOrExcludeSystemResource, String loadedCodeSystemUrl) { - String failureMessage; - if (includeOrExcludeSystemResource == null) { - failureMessage = "Unable to expand ValueSet because CodeSystem could not be found: " + loadedCodeSystemUrl; - } else { - assert includeOrExcludeSystemResource.getContent() == Enumerations.CodeSystemContentMode.NOTPRESENT; - failureMessage = - "Unable to expand ValueSet because CodeSystem has CodeSystem.content=not-present but contents were not found: " - + loadedCodeSystemUrl; - } - return failureMessage; - } - - 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 FhirVersionIndependentConcept( - theCodeSystemUrl, next.getCode(), next.getDisplay(), theCodeSystemVersion)); - } - } - addCodes(theCodeSystemUrl, theCodeSystemVersion, next.getConcept(), theTarget, theCodeFilter); - } - } - - private String optionallyPopulateVersionFromUrl(String theSystemUrl, String theVersion) { - if (contains(theSystemUrl, OUR_PIPE_CHARACTER) && isBlank(theVersion)) { - theVersion = substringAfter(theSystemUrl, OUR_PIPE_CHARACTER); - } - return theVersion; - } - - private static void populateSourceDetailsForInMemoryExpansion( - String theValueSetUrl, CodeValidationResult codeValidationResult) { - codeValidationResult.setSourceDetails( - "Code was validated against in-memory expansion of ValueSet: " + theValueSetUrl); - } - - public static CodeValidationResult createResultForDisplayMismatch( - FhirContext theFhirContext, - String theCode, - String theDisplay, - String theExpectedDisplay, - String theCodeSystemVersion, - IssueSeverity theIssueSeverityForCodeDisplayMismatch) { - return createResultForDisplayMismatch( - theFhirContext, theCode, theDisplay, theExpectedDisplay, theCodeSystemVersion, "", theIssueSeverityForCodeDisplayMismatch); - } - - private static CodeValidationResult createResultForDisplayMismatch( - FhirContext theFhirContext, - String theCode, - String theDisplay, - String theExpectedDisplay, - String theCodeSystemVersion, - String theMessageAppend, - IssueSeverity theIssueSeverityForCodeDisplayMismatch) { - - String message; - IssueSeverity issueSeverity = theIssueSeverityForCodeDisplayMismatch; - if (issueSeverity == IssueSeverity.INFORMATION) { - message = null; - issueSeverity = null; - } else { - message = theFhirContext - .getLocalizer() - .getMessage( - InMemoryTerminologyServerValidationSupport.class, - "displayMismatch", - theDisplay, - theExpectedDisplay) - + theMessageAppend; - } - return new CodeValidationResult() - .setSeverity(issueSeverity) - .setMessage(message) - .setCode(theCode) - .setCodeSystemVersion(theCodeSystemVersion) - .setDisplay(theExpectedDisplay); - } - - private static void flattenAndConvertCodesDstu2( - List theInput, - List theFhirVersionIndependentConcepts) { - for (ca.uhn.fhir.model.dstu2.resource.ValueSet.ExpansionContains next : theInput) { - theFhirVersionIndependentConcepts.add( - new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); - flattenAndConvertCodesDstu2(next.getContains(), theFhirVersionIndependentConcepts); - } - } - - private static void flattenAndConvertCodesDstu2Hl7Org( - List theInput, - List theFhirVersionIndependentConcepts) { - for (org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { - theFhirVersionIndependentConcepts.add( - new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); - flattenAndConvertCodesDstu2Hl7Org(next.getContains(), theFhirVersionIndependentConcepts); - } - } - - private static void flattenAndConvertCodesDstu3( - List theInput, - List theFhirVersionIndependentConcepts) { - for (org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { - theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept( - next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); - flattenAndConvertCodesDstu3(next.getContains(), theFhirVersionIndependentConcepts); - } - } - - private static void flattenAndConvertCodesR4( - List theInput, - List theFhirVersionIndependentConcepts) { - for (org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { - theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept( - next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); - flattenAndConvertCodesR4(next.getContains(), theFhirVersionIndependentConcepts); - } - } - - private static void flattenAndConvertCodesR4B( - List theInput, - List theFhirVersionIndependentConcepts) { - for (org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { - theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept( - next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); - flattenAndConvertCodesR4B(next.getContains(), theFhirVersionIndependentConcepts); - } - } - - private static void flattenAndConvertCodesR5( - List theInput, - List theFhirVersionIndependentConcepts) { - for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { - theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept( - next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); - flattenAndConvertCodesR5(next.getContains(), theFhirVersionIndependentConcepts); - } - } - - public enum FailureType { - UNKNOWN_CODE_SYSTEM, - OTHER - } - - public static class ExpansionCouldNotBeCompletedInternallyException extends Exception { - - private static final long serialVersionUID = -2226561628771483085L; - private final FailureType myFailureType; - - public ExpansionCouldNotBeCompletedInternallyException(String theMessage, FailureType theFailureType) { - super(theMessage); - myFailureType = theFailureType; - } - - public FailureType getFailureType() { - return myFailureType; - } - } + private static final String OUR_PIPE_CHARACTER = "|"; + private final FhirContext myCtx; + private final VersionCanonicalizer myVersionCanonicalizer; + private IssueSeverity myIssueSeverityForCodeDisplayMismatch = IssueSeverity.WARNING; + + /** + * Constructor + * + * @param theCtx A FhirContext for the FHIR version being validated + */ + public InMemoryTerminologyServerValidationSupport(FhirContext theCtx) { + Validate.notNull(theCtx, "theCtx must not be null"); + myCtx = theCtx; + myVersionCanonicalizer = new VersionCanonicalizer(theCtx); + } + + /** + * This setting controls the validation issue severity to report when a code validation + * finds that the code is present in the given CodeSystem, but the display name being + * validated doesn't match the expected value(s). Defaults to + * {@link ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity#WARNING}. Set this + * value to {@link ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity#INFORMATION} + * if you don't want to see display name validation issues at all in resource validation + * outcomes. + * + * @since 7.0.0 + */ + public IssueSeverity getIssueSeverityForCodeDisplayMismatch() { + return myIssueSeverityForCodeDisplayMismatch; + } + + /** + * This setting controls the validation issue severity to report when a code validation + * finds that the code is present in the given CodeSystem, but the display name being + * validated doesn't match the expected value(s). Defaults to + * {@link ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity#WARNING}. Set this + * value to {@link ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity#INFORMATION} + * if you don't want to see display name validation issues at all in resource validation + * outcomes. + * + * @param theIssueSeverityForCodeDisplayMismatch The severity. Must not be {@literal null}. + * @since 7.0.0 + */ + public void setIssueSeverityForCodeDisplayMismatch(@Nonnull IssueSeverity theIssueSeverityForCodeDisplayMismatch) { + Validate.notNull( + theIssueSeverityForCodeDisplayMismatch, "theIssueSeverityForCodeDisplayMismatch must not be null"); + myIssueSeverityForCodeDisplayMismatch = theIssueSeverityForCodeDisplayMismatch; + } + + @Override + public FhirContext getFhirContext() { + return myCtx; + } + + @Override + public ValueSetExpansionOutcome expandValueSet( + ValidationSupportContext theValidationSupportContext, + ValueSetExpansionOptions theExpansionOptions, + @Nonnull IBaseResource theValueSetToExpand) { + return expandValueSet(theValidationSupportContext, theValueSetToExpand, null, null); + } + + private ValueSetExpansionOutcome expandValueSet( + ValidationSupportContext theValidationSupportContext, + IBaseResource theValueSetToExpand, + String theWantSystemAndVersion, + String theWantCode) { + org.hl7.fhir.r5.model.ValueSet expansionR5; + try { + expansionR5 = expandValueSetToCanonical( + theValidationSupportContext, theValueSetToExpand, theWantSystemAndVersion, theWantCode); + } catch (ExpansionCouldNotBeCompletedInternallyException e) { + return new ValueSetExpansionOutcome(e.getMessage()); + } + if (expansionR5 == null) { + return null; + } + + IBaseResource expansion; + switch (myCtx.getVersion().getVersion()) { + case DSTU2: { + org.hl7.fhir.r4.model.ValueSet expansionR4 = (org.hl7.fhir.r4.model.ValueSet) + VersionConvertorFactory_40_50.convertResource(expansionR5, new BaseAdvisor_40_50(false)); + expansion = myVersionCanonicalizer.valueSetFromCanonical(expansionR4); + break; + } + case DSTU2_HL7ORG: { + expansion = VersionConvertorFactory_10_50.convertResource(expansionR5, new BaseAdvisor_10_50(false)); + break; + } + case DSTU3: { + expansion = VersionConvertorFactory_30_50.convertResource(expansionR5, new BaseAdvisor_30_50(false)); + break; + } + case R4: { + expansion = VersionConvertorFactory_40_50.convertResource(expansionR5, new BaseAdvisor_40_50(false)); + break; + } + case R4B: { + expansion = VersionConvertorFactory_43_50.convertResource(expansionR5, new BaseAdvisor_43_50(false)); + break; + } + case R5: { + expansion = expansionR5; + break; + } + case DSTU2_1: + default: + throw new IllegalArgumentException(Msg.code(697) + "Can not handle version: " + + myCtx.getVersion().getVersion()); + } + + return new ValueSetExpansionOutcome(expansion); + } + + private org.hl7.fhir.r5.model.ValueSet expandValueSetToCanonical( + ValidationSupportContext theValidationSupportContext, + IBaseResource theValueSetToExpand, + @Nullable String theWantSystemUrlAndVersion, + @Nullable String theWantCode) + throws ExpansionCouldNotBeCompletedInternallyException { + org.hl7.fhir.r5.model.ValueSet expansionR5; + switch (getFhirVersionEnum( + theValidationSupportContext.getRootValidationSupport().getFhirContext(), theValueSetToExpand)) { + case DSTU2: { + expansionR5 = expandValueSetDstu2( + theValidationSupportContext, + (ca.uhn.fhir.model.dstu2.resource.ValueSet) theValueSetToExpand, + theWantSystemUrlAndVersion, + theWantCode); + break; + } + case DSTU2_HL7ORG: { + expansionR5 = expandValueSetDstu2Hl7Org( + theValidationSupportContext, + (ValueSet) theValueSetToExpand, + theWantSystemUrlAndVersion, + theWantCode); + break; + } + case DSTU3: { + expansionR5 = expandValueSetDstu3( + theValidationSupportContext, + (org.hl7.fhir.dstu3.model.ValueSet) theValueSetToExpand, + theWantSystemUrlAndVersion, + theWantCode); + break; + } + case R4: { + expansionR5 = expandValueSetR4( + theValidationSupportContext, + (org.hl7.fhir.r4.model.ValueSet) theValueSetToExpand, + theWantSystemUrlAndVersion, + theWantCode); + break; + } + case R4B: { + expansionR5 = expandValueSetR4B( + theValidationSupportContext, + (org.hl7.fhir.r4b.model.ValueSet) theValueSetToExpand, + theWantSystemUrlAndVersion, + theWantCode); + break; + } + case R5: { + expansionR5 = expandValueSetR5( + theValidationSupportContext, + (org.hl7.fhir.r5.model.ValueSet) theValueSetToExpand, + theWantSystemUrlAndVersion, + theWantCode); + break; + } + case DSTU2_1: + default: + throw new IllegalArgumentException(Msg.code(698) + "Can not handle version: " + + myCtx.getVersion().getVersion()); + } + + return expansionR5; + } + + @Override + public CodeValidationResult validateCodeInValueSet( + ValidationSupportContext theValidationSupportContext, + ConceptValidationOptions theOptions, + String theCodeSystemUrlAndVersion, + String theCode, + String theDisplay, + @Nonnull IBaseResource theValueSet) { + org.hl7.fhir.r5.model.ValueSet expansion; + String vsUrl = CommonCodeSystemsTerminologyService.getValueSetUrl(getFhirContext(), theValueSet); + try { + expansion = expandValueSetToCanonical( + theValidationSupportContext, theValueSet, theCodeSystemUrlAndVersion, theCode); + } catch (ExpansionCouldNotBeCompletedInternallyException e) { + CodeValidationResult codeValidationResult = new CodeValidationResult(); + codeValidationResult.setSeverityCode("error"); + + String msg = "Failed to expand ValueSet '" + vsUrl + "' (in-memory). Could not validate code " + + theCodeSystemUrlAndVersion + "#" + theCode; + if (e.getMessage() != null) { + msg += ". Error was: " + e.getMessage(); + } + + codeValidationResult.setMessage(msg); + return codeValidationResult; + } + + if (expansion == null) { + return null; + } + + return validateCodeInExpandedValueSet( + theValidationSupportContext, + theOptions, + theCodeSystemUrlAndVersion, + theCode, + theDisplay, + expansion, + vsUrl); + } + + @Override + @Nullable + public CodeValidationResult validateCode( + @Nonnull ValidationSupportContext theValidationSupportContext, + @Nonnull ConceptValidationOptions theOptions, + String theCodeSystem, + String theCode, + String theDisplay, + String theValueSetUrl) { + IBaseResource vs; + if (isNotBlank(theValueSetUrl)) { + vs = theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrl); + if (vs == null) { + 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: + 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))); + break; + case DSTU3: + 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: + 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 R4B: + if (codeSystemVersion != null) { + vs = new org.hl7.fhir.r4b.model.ValueSet() + .setCompose(new org.hl7.fhir.r4b.model.ValueSet.ValueSetComposeComponent() + .addInclude(new org.hl7.fhir.r4b.model.ValueSet.ConceptSetComponent() + .setSystem(codeSystemUrl) + .setVersion(codeSystemVersion))); + } else { + vs = new org.hl7.fhir.r4b.model.ValueSet() + .setCompose(new org.hl7.fhir.r4b.model.ValueSet.ValueSetComposeComponent() + .addInclude(new org.hl7.fhir.r4b.model.ValueSet.ConceptSetComponent() + .setSystem(theCodeSystem))); + } + break; + case R5: + 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_1: + default: + throw new IllegalArgumentException(Msg.code(699) + "Can not handle version: " + + myCtx.getVersion().getVersion()); + } + } + + ValueSetExpansionOutcome valueSetExpansionOutcome = + expandValueSet(theValidationSupportContext, vs, theCodeSystem, theCode); + if (valueSetExpansionOutcome == null) { + return null; + } + + if (valueSetExpansionOutcome.getError() != null) { + return new CodeValidationResult() + .setSeverity(IssueSeverity.ERROR) + .setMessage(valueSetExpansionOutcome.getError()); + } + + IBaseResource expansion = valueSetExpansionOutcome.getValueSet(); + return validateCodeInExpandedValueSet( + theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, expansion, theValueSetUrl); + } + + private CodeValidationResult validateCodeInExpandedValueSet( + ValidationSupportContext theValidationSupportContext, + ConceptValidationOptions theOptions, + String theCodeSystemUrlAndVersionToValidate, + String theCodeToValidate, + String theDisplayToValidate, + IBaseResource theExpansion, + String theValueSetUrl) { + assert theExpansion != null; + + boolean caseSensitive = true; + IBaseResource codeSystemToValidateResource = null; + if (!theOptions.isInferSystem() && isNotBlank(theCodeSystemUrlAndVersionToValidate)) { + codeSystemToValidateResource = theValidationSupportContext + .getRootValidationSupport() + .fetchCodeSystem(theCodeSystemUrlAndVersionToValidate); + } + + List codes = new ArrayList<>(); + switch (getFhirVersionEnum( + theValidationSupportContext.getRootValidationSupport().getFhirContext(), theExpansion)) { + case DSTU2: { + ca.uhn.fhir.model.dstu2.resource.ValueSet expansionVs = + (ca.uhn.fhir.model.dstu2.resource.ValueSet) theExpansion; + List contains = + expansionVs.getExpansion().getContains(); + flattenAndConvertCodesDstu2(contains, codes); + break; + } + case DSTU2_HL7ORG: { + ValueSet expansionVs = (ValueSet) theExpansion; + List contains = + expansionVs.getExpansion().getContains(); + flattenAndConvertCodesDstu2Hl7Org(contains, codes); + 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); + 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); + break; + } + case R4B: { + org.hl7.fhir.r4b.model.ValueSet expansionVs = (org.hl7.fhir.r4b.model.ValueSet) theExpansion; + List contains = + expansionVs.getExpansion().getContains(); + flattenAndConvertCodesR4B(contains, codes); + 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); + break; + } + case DSTU2_1: + default: + throw new IllegalArgumentException(Msg.code(700) + "Can not handle version: " + + myCtx.getVersion().getVersion()); + } + + String codeSystemResourceName = null; + String codeSystemResourceVersion = null; + String codeSystemResourceContentMode = null; + if (codeSystemToValidateResource != null) { + switch (getFhirVersionEnum( + theValidationSupportContext.getRootValidationSupport().getFhirContext(), + codeSystemToValidateResource)) { + case DSTU2: + case DSTU2_HL7ORG: { + caseSensitive = true; + break; + } + case DSTU3: { + org.hl7.fhir.dstu3.model.CodeSystem systemDstu3 = + (org.hl7.fhir.dstu3.model.CodeSystem) codeSystemToValidateResource; + caseSensitive = systemDstu3.getCaseSensitive(); + 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) codeSystemToValidateResource; + caseSensitive = systemR4.getCaseSensitive(); + codeSystemResourceName = systemR4.getName(); + codeSystemResourceVersion = systemR4.getVersion(); + codeSystemResourceContentMode = systemR4.getContentElement().getValueAsString(); + break; + } + case R4B: { + org.hl7.fhir.r4b.model.CodeSystem systemR4B = + (org.hl7.fhir.r4b.model.CodeSystem) codeSystemToValidateResource; + caseSensitive = systemR4B.getCaseSensitive(); + codeSystemResourceName = systemR4B.getName(); + codeSystemResourceVersion = systemR4B.getVersion(); + codeSystemResourceContentMode = + systemR4B.getContentElement().getValueAsString(); + break; + } + case R5: { + CodeSystem systemR5 = (CodeSystem) codeSystemToValidateResource; + caseSensitive = systemR5.getCaseSensitive(); + codeSystemResourceName = systemR5.getName(); + codeSystemResourceVersion = systemR5.getVersion(); + codeSystemResourceContentMode = systemR5.getContentElement().getValueAsString(); + break; + } + case DSTU2_1: + default: + throw new IllegalArgumentException(Msg.code(701) + "Can not handle version: " + + myCtx.getVersion().getVersion()); + } + } + + String codeSystemUrlToValidate = null; + String codeSystemVersionToValidate = null; + if (theCodeSystemUrlAndVersionToValidate != null) { + int versionIndex = theCodeSystemUrlAndVersionToValidate.indexOf("|"); + if (versionIndex > -1) { + codeSystemUrlToValidate = theCodeSystemUrlAndVersionToValidate.substring(0, versionIndex); + codeSystemVersionToValidate = theCodeSystemUrlAndVersionToValidate.substring(versionIndex + 1); + } else { + codeSystemUrlToValidate = theCodeSystemUrlAndVersionToValidate; + } + } + for (FhirVersionIndependentConcept nextExpansionCode : codes) { + + boolean codeMatches; + if (caseSensitive) { + codeMatches = defaultString(theCodeToValidate).equals(nextExpansionCode.getCode()); + } else { + codeMatches = defaultString(theCodeToValidate).equalsIgnoreCase(nextExpansionCode.getCode()); + } + if (codeMatches) { + if (theOptions.isInferSystem() + || (nextExpansionCode.getSystem().equals(codeSystemUrlToValidate) + && (codeSystemVersionToValidate == null + || codeSystemVersionToValidate.equals(nextExpansionCode.getSystemVersion())))) { + String csVersion = codeSystemResourceVersion; + if (isNotBlank(nextExpansionCode.getSystemVersion())) { + csVersion = nextExpansionCode.getSystemVersion(); + } + if (!theOptions.isValidateDisplay() + || (isBlank(nextExpansionCode.getDisplay()) + || isBlank(theDisplayToValidate) + || nextExpansionCode.getDisplay().equals(theDisplayToValidate))) { + CodeValidationResult codeValidationResult = new CodeValidationResult() + .setCode(theCodeToValidate) + .setDisplay(nextExpansionCode.getDisplay()) + .setCodeSystemName(codeSystemResourceName) + .setCodeSystemVersion(csVersion); + if (isNotBlank(theValueSetUrl)) { + populateSourceDetailsForInMemoryExpansion(theValueSetUrl, codeValidationResult); + } + return codeValidationResult; + } else { + String messageAppend = ""; + if (isNotBlank(theValueSetUrl)) { + messageAppend = " for in-memory expansion of ValueSet: " + theValueSetUrl; + } + CodeValidationResult codeValidationResult = createResultForDisplayMismatch( + myCtx, + theCodeToValidate, + theDisplayToValidate, + nextExpansionCode.getDisplay(), + csVersion, + messageAppend, + getIssueSeverityForCodeDisplayMismatch()); + if (isNotBlank(theValueSetUrl)) { + populateSourceDetailsForInMemoryExpansion(theValueSetUrl, codeValidationResult); + } + return codeValidationResult; + } + } + } + } + + ValidationMessage.IssueSeverity severity; + String message; + if ("fragment".equals(codeSystemResourceContentMode)) { + severity = ValidationMessage.IssueSeverity.WARNING; + message = "Unknown code in fragment CodeSystem '" + + (isNotBlank(theCodeSystemUrlAndVersionToValidate) + ? theCodeSystemUrlAndVersionToValidate + "#" + : "") + + theCodeToValidate + "'"; + } else { + severity = ValidationMessage.IssueSeverity.ERROR; + message = "Unknown code '" + + (isNotBlank(theCodeSystemUrlAndVersionToValidate) + ? theCodeSystemUrlAndVersionToValidate + "#" + : "") + + theCodeToValidate + "'"; + } + if (isNotBlank(theValueSetUrl)) { + message += " for in-memory expansion of ValueSet '" + theValueSetUrl + "'"; + } + + return new CodeValidationResult().setSeverityCode(severity.toCode()).setMessage(message); + } + + @Override + public LookupCodeResult lookupCode( + ValidationSupportContext theValidationSupportContext, + String theSystem, + String theCode, + String theDisplayLanguage) { + CodeValidationResult codeValidationResult = validateCode( + theValidationSupportContext, new ConceptValidationOptions(), theSystem, theCode, null, null); + if (codeValidationResult == null) { + return null; + } + return codeValidationResult.asLookupCodeResult(theSystem, theCode); + } + + @Nullable + private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2Hl7Org( + ValidationSupportContext theValidationSupportContext, + ValueSet theInput, + @Nullable String theWantSystemUrlAndVersion, + @Nullable String theWantCode) + throws ExpansionCouldNotBeCompletedInternallyException { + org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) + VersionConvertorFactory_10_50.convertResource(theInput, new BaseAdvisor_10_50(false)); + return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode)); + } + + @Nullable + private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2( + ValidationSupportContext theValidationSupportContext, + ca.uhn.fhir.model.dstu2.resource.ValueSet theInput, + @Nullable String theWantSystemUrlAndVersion, + @Nullable String theWantCode) + throws ExpansionCouldNotBeCompletedInternallyException { + IParser parserRi = FhirContext.forCached(FhirVersionEnum.DSTU2_HL7ORG).newJsonParser(); + IParser parserHapi = FhirContext.forDstu2Cached().newJsonParser(); + + 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 = (org.hl7.fhir.r5.model.ValueSet) + VersionConvertorFactory_10_50.convertResource(valueSetRi, new BaseAdvisor_10_50(false)); + return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode)); + } + + @Override + public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) { + if (isBlank(theSystem)) { + return false; + } + + IBaseResource cs = + theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(theSystem); + + if (!myCtx.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU2_1)) { + return cs != null; + } + + if (cs != null) { + IPrimitiveType content = + getFhirContext().newTerser().getSingleValueOrNull(cs, "content", IPrimitiveType.class); + return !"not-present".equals(content.getValueAsString()); + } + + return false; + } + + @Override + public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) { + return isNotBlank(theValueSetUrl) + && theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrl) != null; + } + + private void addCodesDstu2Hl7Org( + List theSourceList, + List theTargetList) { + for (ValueSet.ConceptDefinitionComponent nextSource : theSourceList) { + CodeSystem.ConceptDefinitionComponent targetConcept = new CodeSystem.ConceptDefinitionComponent() + .setCode(nextSource.getCode()) + .setDisplay(nextSource.getDisplay()); + theTargetList.add(targetConcept); + addCodesDstu2Hl7Org(nextSource.getConcept(), targetConcept.getConcept()); + } + } + + private void addCodesDstu2( + List theSourceList, + List theTargetList) { + for (ca.uhn.fhir.model.dstu2.resource.ValueSet.CodeSystemConcept nextSource : theSourceList) { + CodeSystem.ConceptDefinitionComponent targetConcept = new CodeSystem.ConceptDefinitionComponent() + .setCode(nextSource.getCode()) + .setDisplay(nextSource.getDisplay()); + theTargetList.add(targetConcept); + addCodesDstu2(nextSource.getConcept(), targetConcept.getConcept()); + } + } + + @Nullable + private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu3( + ValidationSupportContext theValidationSupportContext, + org.hl7.fhir.dstu3.model.ValueSet theInput, + @Nullable String theWantSystemUrlAndVersion, + @Nullable String theWantCode) + throws ExpansionCouldNotBeCompletedInternallyException { + org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) + VersionConvertorFactory_30_50.convertResource(theInput, new BaseAdvisor_30_50(false)); + return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode)); + } + + @Nullable + private org.hl7.fhir.r5.model.ValueSet expandValueSetR4( + ValidationSupportContext theValidationSupportContext, + org.hl7.fhir.r4.model.ValueSet theInput, + @Nullable String theWantSystemUrlAndVersion, + @Nullable String theWantCode) + throws ExpansionCouldNotBeCompletedInternallyException { + org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) + VersionConvertorFactory_40_50.convertResource(theInput, new BaseAdvisor_40_50(false)); + return expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode); + } + + @Nullable + private org.hl7.fhir.r5.model.ValueSet expandValueSetR4B( + ValidationSupportContext theValidationSupportContext, + org.hl7.fhir.r4b.model.ValueSet theInput, + @Nullable String theWantSystemUrlAndVersion, + @Nullable String theWantCode) + throws ExpansionCouldNotBeCompletedInternallyException { + org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) + VersionConvertorFactory_43_50.convertResource(theInput, new BaseAdvisor_43_50(false)); + return expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode); + } + + @Nullable + private org.hl7.fhir.r5.model.ValueSet expandValueSetR5( + ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r5.model.ValueSet theInput) + throws ExpansionCouldNotBeCompletedInternallyException { + return expandValueSetR5(theValidationSupportContext, theInput, null, null); + } + + @Nullable + private org.hl7.fhir.r5.model.ValueSet expandValueSetR5( + ValidationSupportContext theValidationSupportContext, + org.hl7.fhir.r5.model.ValueSet theInput, + @Nullable String theWantSystemUrlAndVersion, + @Nullable String theWantCode) + throws ExpansionCouldNotBeCompletedInternallyException { + Set concepts = new HashSet<>(); + + expandValueSetR5IncludeOrExcludes( + theValidationSupportContext, + concepts, + theInput.getCompose().getInclude(), + true, + theWantSystemUrlAndVersion, + theWantCode); + expandValueSetR5IncludeOrExcludes( + theValidationSupportContext, + concepts, + theInput.getCompose().getExclude(), + false, + theWantSystemUrlAndVersion, + theWantCode); + + org.hl7.fhir.r5.model.ValueSet retVal = new org.hl7.fhir.r5.model.ValueSet(); + for (FhirVersionIndependentConcept next : concepts) { + org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent contains = + retVal.getExpansion().addContains(); + contains.setSystem(next.getSystem()); + contains.setCode(next.getCode()); + contains.setDisplay(next.getDisplay()); + contains.setVersion(next.getSystemVersion()); + } + + return retVal; + } + + /** + * Use with caution - this is not a stable API + * + * @since 5.6.0 + */ + public void expandValueSetIncludeOrExclude( + ValidationSupportContext theValidationSupportContext, + Consumer theConsumer, + org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent theIncludeOrExclude) + throws ExpansionCouldNotBeCompletedInternallyException { + expandValueSetR5IncludeOrExclude(theValidationSupportContext, theConsumer, null, null, theIncludeOrExclude); + } + + private void expandValueSetR5IncludeOrExcludes( + ValidationSupportContext theValidationSupportContext, + Set theConcepts, + List theComposeList, + boolean theComposeListIsInclude, + @Nullable String theWantSystemUrlAndVersion, + @Nullable String theWantCode) + throws ExpansionCouldNotBeCompletedInternallyException { + Consumer consumer = c -> { + if (theComposeListIsInclude) { + theConcepts.add(c); + } else { + theConcepts.remove(c); + } + }; + expandValueSetR5IncludeOrExcludes( + theValidationSupportContext, consumer, theComposeList, theWantSystemUrlAndVersion, theWantCode); + } + + private void expandValueSetR5IncludeOrExcludes( + ValidationSupportContext theValidationSupportContext, + Consumer theConsumer, + List theComposeList, + @Nullable String theWantSystemUrlAndVersion, + @Nullable String theWantCode) + throws ExpansionCouldNotBeCompletedInternallyException { + ExpansionCouldNotBeCompletedInternallyException caughtException = null; + for (org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent nextInclude : theComposeList) { + try { + boolean outcome = expandValueSetR5IncludeOrExclude( + theValidationSupportContext, theConsumer, theWantSystemUrlAndVersion, theWantCode, nextInclude); + if (isNotBlank(theWantCode)) { + if (outcome) { + return; + } + } + } catch (ExpansionCouldNotBeCompletedInternallyException e) { + if (isBlank(theWantCode)) { + throw e; + } else { + caughtException = e; + } + } + } + if (caughtException != null) { + throw caughtException; + } + } + + /** + * Returns true if at least one code was added + */ + private boolean expandValueSetR5IncludeOrExclude( + ValidationSupportContext theValidationSupportContext, + Consumer theConsumer, + @Nullable String theWantSystemUrlAndVersion, + @Nullable String theWantCode, + org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent theInclude) + throws ExpansionCouldNotBeCompletedInternallyException { + + String wantSystemUrl = null; + String wantSystemVersion = null; + + if (theWantSystemUrlAndVersion != null) { + int versionIndex = theWantSystemUrlAndVersion.indexOf(OUR_PIPE_CHARACTER); + if (versionIndex > -1) { + wantSystemUrl = theWantSystemUrlAndVersion.substring(0, versionIndex); + wantSystemVersion = theWantSystemUrlAndVersion.substring(versionIndex + 1); + } else { + wantSystemUrl = theWantSystemUrlAndVersion; + } + } + + String includeOrExcludeConceptSystemUrl = theInclude.getSystem(); + String includeOrExcludeConceptSystemVersion = theInclude.getVersion(); + + Function codeSystemLoader = newCodeSystemLoader(theValidationSupportContext); + Function valueSetLoader = + newValueSetLoader(theValidationSupportContext); + + List nextCodeList = new ArrayList<>(); + CodeSystem includeOrExcludeSystemResource = null; + + if (isNotBlank(includeOrExcludeConceptSystemUrl)) { + + includeOrExcludeConceptSystemVersion = optionallyPopulateVersionFromUrl( + includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion); + includeOrExcludeConceptSystemUrl = substringBefore(includeOrExcludeConceptSystemUrl, OUR_PIPE_CHARACTER); + + if (wantSystemUrl != null && !wantSystemUrl.equals(includeOrExcludeConceptSystemUrl)) { + return false; + } + + if (wantSystemVersion != null && !wantSystemVersion.equals(includeOrExcludeConceptSystemVersion)) { + return false; + } + + String loadedCodeSystemUrl; + if (includeOrExcludeConceptSystemVersion != null) { + loadedCodeSystemUrl = + includeOrExcludeConceptSystemUrl + OUR_PIPE_CHARACTER + includeOrExcludeConceptSystemVersion; + } else { + loadedCodeSystemUrl = includeOrExcludeConceptSystemUrl; + } + + includeOrExcludeSystemResource = codeSystemLoader.apply(loadedCodeSystemUrl); + + Set wantCodes; + if (theInclude.getConcept().isEmpty()) { + wantCodes = null; + } else { + wantCodes = + theInclude.getConcept().stream().map(t -> t.getCode()).collect(Collectors.toSet()); + } + + boolean ableToHandleCode = false; + String failureMessage = null; + FailureType failureType = FailureType.OTHER; + + if (includeOrExcludeSystemResource == null + || includeOrExcludeSystemResource.getContent() == Enumerations.CodeSystemContentMode.NOTPRESENT) { + + if (theWantCode != null) { + if (theValidationSupportContext + .getRootValidationSupport() + .isCodeSystemSupported(theValidationSupportContext, includeOrExcludeConceptSystemUrl)) { + LookupCodeResult lookup = theValidationSupportContext + .getRootValidationSupport() + .lookupCode( + theValidationSupportContext, + includeOrExcludeConceptSystemUrl, + theWantCode, + null); + if (lookup != null) { + ableToHandleCode = true; + if (lookup.isFound()) { + CodeSystem.ConceptDefinitionComponent conceptDefinition = + new CodeSystem.ConceptDefinitionComponent() + .addConcept() + .setCode(theWantCode) + .setDisplay(lookup.getCodeDisplay()); + List codesList = + Collections.singletonList(conceptDefinition); + addCodes( + includeOrExcludeConceptSystemUrl, + includeOrExcludeConceptSystemVersion, + codesList, + nextCodeList, + wantCodes); + } + } + } else { + + /* + * If we're doing an expansion specifically looking for a single code, that means we're validating that code. + * In the case where we have a ValueSet that explicitly enumerates a collection of codes + * (via ValueSet.compose.include.code) in a code system that is unknown we'll assume the code is valid + * even if we can't find the CodeSystem. This is a compromise obviously, since it would be ideal for + * CodeSystems to always be known, but realistically there are always going to be CodeSystems that + * can't be supplied because of copyright issues, or because they are grammar based. Allowing a VS to + * enumerate a set of good codes for them is a nice compromise there. + */ + if (Objects.equals(theInclude.getSystem(), theWantSystemUrlAndVersion)) { + Optional + matchingEnumeratedConcept = theInclude.getConcept().stream() + .filter(t -> Objects.equals(t.getCode(), theWantCode)) + .findFirst(); + + // If the ValueSet.compose.include has no individual concepts in it, and + // we can't find the actual referenced CodeSystem, we have no choice + // but to fail + if (!theInclude.getConcept().isEmpty()) { + ableToHandleCode = true; + } else { + failureMessage = getFailureMessageForMissingOrUnusableCodeSystem( + includeOrExcludeSystemResource, loadedCodeSystemUrl); + } + + if (matchingEnumeratedConcept.isPresent()) { + CodeSystem.ConceptDefinitionComponent conceptDefinition = + new CodeSystem.ConceptDefinitionComponent() + .addConcept() + .setCode(theWantCode) + .setDisplay(matchingEnumeratedConcept + .get() + .getDisplay()); + List codesList = + Collections.singletonList(conceptDefinition); + addCodes( + includeOrExcludeConceptSystemUrl, + includeOrExcludeConceptSystemVersion, + codesList, + nextCodeList, + wantCodes); + } + } + } + } else { + if (isNotBlank(theInclude.getSystem()) + && !theInclude.getConcept().isEmpty() + && theInclude.getFilter().isEmpty() + && theInclude.getValueSet().isEmpty()) { + theInclude.getConcept().stream() + .map(t -> new FhirVersionIndependentConcept( + theInclude.getSystem(), t.getCode(), t.getDisplay(), theInclude.getVersion())) + .forEach(t -> nextCodeList.add(t)); + ableToHandleCode = true; + } + + if (!ableToHandleCode) { + failureMessage = getFailureMessageForMissingOrUnusableCodeSystem( + includeOrExcludeSystemResource, loadedCodeSystemUrl); + } + } + + } else { + ableToHandleCode = true; + } + + if (!ableToHandleCode) { + if (includeOrExcludeSystemResource == null && failureMessage == null) { + failureMessage = getFailureMessageForMissingOrUnusableCodeSystem( + includeOrExcludeSystemResource, loadedCodeSystemUrl); + } + + if (includeOrExcludeSystemResource == null) { + failureType = FailureType.UNKNOWN_CODE_SYSTEM; + } + + throw new ExpansionCouldNotBeCompletedInternallyException(Msg.code(702) + failureMessage, failureType); + } + + if (includeOrExcludeSystemResource != null + && includeOrExcludeSystemResource.getContent() != Enumerations.CodeSystemContentMode.NOTPRESENT) { + addCodes( + includeOrExcludeConceptSystemUrl, + includeOrExcludeConceptSystemVersion, + includeOrExcludeSystemResource.getConcept(), + nextCodeList, + wantCodes); + } + } + + for (CanonicalType nextValueSetInclude : theInclude.getValueSet()) { + org.hl7.fhir.r5.model.ValueSet vs = valueSetLoader.apply(nextValueSetInclude.getValueAsString()); + if (vs != null) { + org.hl7.fhir.r5.model.ValueSet subExpansion = + expandValueSetR5(theValidationSupportContext, vs, theWantSystemUrlAndVersion, theWantCode); + if (subExpansion == null) { + throw new ExpansionCouldNotBeCompletedInternallyException( + Msg.code(703) + "Failed to expand ValueSet: " + nextValueSetInclude.getValueAsString(), + FailureType.OTHER); + } + for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : + subExpansion.getExpansion().getContains()) { + nextCodeList.add(new FhirVersionIndependentConcept( + next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); + } + } + } + + boolean retVal = false; + + for (FhirVersionIndependentConcept next : nextCodeList) { + if (includeOrExcludeSystemResource != null && theWantCode != null) { + boolean matches; + if (includeOrExcludeSystemResource.getCaseSensitive()) { + matches = theWantCode.equals(next.getCode()); + } else { + matches = theWantCode.equalsIgnoreCase(next.getCode()); + } + if (!matches) { + continue; + } + } + + theConsumer.accept(next); + retVal = true; + } + + return retVal; + } + + private Function newValueSetLoader( + ValidationSupportContext theValidationSupportContext) { + switch (myCtx.getVersion().getVersion()) { + case DSTU2: + case DSTU2_HL7ORG: + return t -> { + IBaseResource vs = theValidationSupportContext + .getRootValidationSupport() + .fetchValueSet(t); + if (vs instanceof ca.uhn.fhir.model.dstu2.resource.ValueSet) { + IParser parserRi = FhirContext.forCached(FhirVersionEnum.DSTU2_HL7ORG) + .newJsonParser(); + IParser parserHapi = FhirContext.forDstu2Cached().newJsonParser(); + ca.uhn.fhir.model.dstu2.resource.ValueSet valueSet = + (ca.uhn.fhir.model.dstu2.resource.ValueSet) vs; + org.hl7.fhir.dstu2.model.ValueSet valueSetRi = parserRi.parseResource( + org.hl7.fhir.dstu2.model.ValueSet.class, parserHapi.encodeResourceToString(valueSet)); + return (org.hl7.fhir.r5.model.ValueSet) + VersionConvertorFactory_10_50.convertResource(valueSetRi, new BaseAdvisor_10_50(false)); + } else { + org.hl7.fhir.dstu2.model.ValueSet valueSet = + (org.hl7.fhir.dstu2.model.ValueSet) theValidationSupportContext + .getRootValidationSupport() + .fetchValueSet(t); + return (org.hl7.fhir.r5.model.ValueSet) + VersionConvertorFactory_10_50.convertResource(valueSet, new BaseAdvisor_10_50(false)); + } + }; + case DSTU3: + return t -> { + org.hl7.fhir.dstu3.model.ValueSet valueSet = + (org.hl7.fhir.dstu3.model.ValueSet) theValidationSupportContext + .getRootValidationSupport() + .fetchValueSet(t); + return (org.hl7.fhir.r5.model.ValueSet) + VersionConvertorFactory_30_50.convertResource(valueSet, new BaseAdvisor_30_50(false)); + }; + case R4: + return t -> { + org.hl7.fhir.r4.model.ValueSet valueSet = + (org.hl7.fhir.r4.model.ValueSet) theValidationSupportContext + .getRootValidationSupport() + .fetchValueSet(t); + return (org.hl7.fhir.r5.model.ValueSet) + VersionConvertorFactory_40_50.convertResource(valueSet, new BaseAdvisor_40_50(false)); + }; + case R4B: + return t -> { + org.hl7.fhir.r4b.model.ValueSet valueSet = + (org.hl7.fhir.r4b.model.ValueSet) theValidationSupportContext + .getRootValidationSupport() + .fetchValueSet(t); + return (org.hl7.fhir.r5.model.ValueSet) + VersionConvertorFactory_43_50.convertResource(valueSet, new BaseAdvisor_43_50(false)); + }; + default: + case DSTU2_1: + case R5: + return t -> (org.hl7.fhir.r5.model.ValueSet) + theValidationSupportContext.getRootValidationSupport().fetchValueSet(t); + } + } + + private Function newCodeSystemLoader(ValidationSupportContext theValidationSupportContext) { + switch (myCtx.getVersion().getVersion()) { + case DSTU2: + case DSTU2_HL7ORG: + return t -> { + IBaseResource codeSystem = theValidationSupportContext + .getRootValidationSupport() + .fetchCodeSystem(t); + CodeSystem retVal = null; + if (codeSystem != null) { + retVal = new CodeSystem(); + if (codeSystem instanceof ca.uhn.fhir.model.dstu2.resource.ValueSet) { + ca.uhn.fhir.model.dstu2.resource.ValueSet codeSystemCasted = + (ca.uhn.fhir.model.dstu2.resource.ValueSet) codeSystem; + retVal.setUrl(codeSystemCasted.getUrl()); + addCodesDstu2(codeSystemCasted.getCodeSystem().getConcept(), retVal.getConcept()); + } else { + org.hl7.fhir.dstu2.model.ValueSet codeSystemCasted = + (org.hl7.fhir.dstu2.model.ValueSet) codeSystem; + retVal.setUrl(codeSystemCasted.getUrl()); + addCodesDstu2Hl7Org(codeSystemCasted.getCodeSystem().getConcept(), retVal.getConcept()); + } + } + return retVal; + }; + case DSTU3: + return t -> { + org.hl7.fhir.dstu3.model.CodeSystem codeSystem = + (org.hl7.fhir.dstu3.model.CodeSystem) theValidationSupportContext + .getRootValidationSupport() + .fetchCodeSystem(t); + return (CodeSystem) + VersionConvertorFactory_30_50.convertResource(codeSystem, new BaseAdvisor_30_50(false)); + }; + case R4: + return t -> { + org.hl7.fhir.r4.model.CodeSystem codeSystem = + (org.hl7.fhir.r4.model.CodeSystem) theValidationSupportContext + .getRootValidationSupport() + .fetchCodeSystem(t); + return (CodeSystem) + VersionConvertorFactory_40_50.convertResource(codeSystem, new BaseAdvisor_40_50(false)); + }; + case R4B: + return t -> { + org.hl7.fhir.r4b.model.CodeSystem codeSystem = + (org.hl7.fhir.r4b.model.CodeSystem) theValidationSupportContext + .getRootValidationSupport() + .fetchCodeSystem(t); + return (CodeSystem) + VersionConvertorFactory_43_50.convertResource(codeSystem, new BaseAdvisor_43_50(false)); + }; + case DSTU2_1: + case R5: + default: + return t -> (org.hl7.fhir.r5.model.CodeSystem) + theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); + } + } + + private String getFailureMessageForMissingOrUnusableCodeSystem( + CodeSystem includeOrExcludeSystemResource, String loadedCodeSystemUrl) { + String failureMessage; + if (includeOrExcludeSystemResource == null) { + failureMessage = "Unable to expand ValueSet because CodeSystem could not be found: " + loadedCodeSystemUrl; + } else { + assert includeOrExcludeSystemResource.getContent() == Enumerations.CodeSystemContentMode.NOTPRESENT; + failureMessage = + "Unable to expand ValueSet because CodeSystem has CodeSystem.content=not-present but contents were not found: " + + loadedCodeSystemUrl; + } + return failureMessage; + } + + 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 FhirVersionIndependentConcept( + theCodeSystemUrl, next.getCode(), next.getDisplay(), theCodeSystemVersion)); + } + } + addCodes(theCodeSystemUrl, theCodeSystemVersion, next.getConcept(), theTarget, theCodeFilter); + } + } + + private String optionallyPopulateVersionFromUrl(String theSystemUrl, String theVersion) { + if (contains(theSystemUrl, OUR_PIPE_CHARACTER) && isBlank(theVersion)) { + theVersion = substringAfter(theSystemUrl, OUR_PIPE_CHARACTER); + } + return theVersion; + } + + private static void populateSourceDetailsForInMemoryExpansion( + String theValueSetUrl, CodeValidationResult codeValidationResult) { + codeValidationResult.setSourceDetails( + "Code was validated against in-memory expansion of ValueSet: " + theValueSetUrl); + } + + public static CodeValidationResult createResultForDisplayMismatch( + FhirContext theFhirContext, + String theCode, + String theDisplay, + String theExpectedDisplay, + String theCodeSystemVersion, + IssueSeverity theIssueSeverityForCodeDisplayMismatch) { + return createResultForDisplayMismatch( + theFhirContext, + theCode, + theDisplay, + theExpectedDisplay, + theCodeSystemVersion, + "", + theIssueSeverityForCodeDisplayMismatch); + } + + private static CodeValidationResult createResultForDisplayMismatch( + FhirContext theFhirContext, + String theCode, + String theDisplay, + String theExpectedDisplay, + String theCodeSystemVersion, + String theMessageAppend, + IssueSeverity theIssueSeverityForCodeDisplayMismatch) { + + String message; + IssueSeverity issueSeverity = theIssueSeverityForCodeDisplayMismatch; + if (issueSeverity == IssueSeverity.INFORMATION) { + message = null; + issueSeverity = null; + } else { + message = theFhirContext + .getLocalizer() + .getMessage( + InMemoryTerminologyServerValidationSupport.class, + "displayMismatch", + theDisplay, + theExpectedDisplay) + + theMessageAppend; + } + return new CodeValidationResult() + .setSeverity(issueSeverity) + .setMessage(message) + .setCode(theCode) + .setCodeSystemVersion(theCodeSystemVersion) + .setDisplay(theExpectedDisplay); + } + + private static void flattenAndConvertCodesDstu2( + List theInput, + List theFhirVersionIndependentConcepts) { + for (ca.uhn.fhir.model.dstu2.resource.ValueSet.ExpansionContains next : theInput) { + theFhirVersionIndependentConcepts.add( + new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); + flattenAndConvertCodesDstu2(next.getContains(), theFhirVersionIndependentConcepts); + } + } + + private static void flattenAndConvertCodesDstu2Hl7Org( + List theInput, + List theFhirVersionIndependentConcepts) { + for (org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { + theFhirVersionIndependentConcepts.add( + new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); + flattenAndConvertCodesDstu2Hl7Org(next.getContains(), theFhirVersionIndependentConcepts); + } + } + + private static void flattenAndConvertCodesDstu3( + List theInput, + List theFhirVersionIndependentConcepts) { + for (org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { + theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept( + next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); + flattenAndConvertCodesDstu3(next.getContains(), theFhirVersionIndependentConcepts); + } + } + + private static void flattenAndConvertCodesR4( + List theInput, + List theFhirVersionIndependentConcepts) { + for (org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { + theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept( + next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); + flattenAndConvertCodesR4(next.getContains(), theFhirVersionIndependentConcepts); + } + } + + private static void flattenAndConvertCodesR4B( + List theInput, + List theFhirVersionIndependentConcepts) { + for (org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { + theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept( + next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); + flattenAndConvertCodesR4B(next.getContains(), theFhirVersionIndependentConcepts); + } + } + + private static void flattenAndConvertCodesR5( + List theInput, + List theFhirVersionIndependentConcepts) { + for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { + theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept( + next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); + flattenAndConvertCodesR5(next.getContains(), theFhirVersionIndependentConcepts); + } + } + + public enum FailureType { + UNKNOWN_CODE_SYSTEM, + OTHER + } + + public static class ExpansionCouldNotBeCompletedInternallyException extends Exception { + + private static final long serialVersionUID = -2226561628771483085L; + private final FailureType myFailureType; + + public ExpansionCouldNotBeCompletedInternallyException(String theMessage, FailureType theFailureType) { + super(theMessage); + myFailureType = theFailureType; + } + + public FailureType getFailureType() { + return myFailureType; + } + } } From f50f91afa3098ad074898e9deacdf2073976c39f Mon Sep 17 00:00:00 2001 From: James Agnew Date: Sun, 17 Sep 2023 17:30:54 +0700 Subject: [PATCH 08/10] Add changelog --- ....yaml => 5321-code-display-validation-now-configurable.yaml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/{FIXME-code-display-validation-now-configurable.yaml => 5321-code-display-validation-now-configurable.yaml} (95%) diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/FIXME-code-display-validation-now-configurable.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5321-code-display-validation-now-configurable.yaml similarity index 95% rename from hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/FIXME-code-display-validation-now-configurable.yaml rename to hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5321-code-display-validation-now-configurable.yaml index 650f443feeb5..bcf68fa71a66 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/FIXME-code-display-validation-now-configurable.yaml +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5321-code-display-validation-now-configurable.yaml @@ -1,6 +1,6 @@ --- type: add -issue: FIXME +issue: 5321 title: "It is now possible to configure the strictness of concept display name validation using a new flag on the InMemoryTerminologyServerValidationSupport (for non-JPA validation) and JpaStorageSettings (for JPA validation)." From 7d6e147f383b76ef3534e70755408b0d2bbd0bc4 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Sun, 17 Sep 2023 17:47:03 +0700 Subject: [PATCH 09/10] Add to changelog --- .../7_0_0/5321-code-display-validation-now-configurable.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5321-code-display-validation-now-configurable.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5321-code-display-validation-now-configurable.yaml index bcf68fa71a66..1e5b06ad12ed 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5321-code-display-validation-now-configurable.yaml +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5321-code-display-validation-now-configurable.yaml @@ -3,4 +3,6 @@ type: add issue: 5321 title: "It is now possible to configure the strictness of concept display name validation using a new flag on the InMemoryTerminologyServerValidationSupport (for non-JPA validation) - and JpaStorageSettings (for JPA validation)." + and JpaStorageSettings (for JPA validation). In addition, the error messages emitted by + the validator when a concept display doesn't match have been improved to be much + more useful." From c44926fa6b95c4e32fd97955608ebb0c4930c5b9 Mon Sep 17 00:00:00 2001 From: Tadgh Date: Tue, 19 Sep 2023 12:28:49 -0700 Subject: [PATCH 10/10] Fix test. TODO JA look at this fix please --- .../support/InMemoryTerminologyServerValidationSupportTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupportTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupportTest.java index a33284774779..1a8f683a325f 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupportTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupportTest.java @@ -251,7 +251,7 @@ public void testExpandValueSet_VsIsEnumeratedWithVersionedSystem_CsOnlyDifferent assertTrue(outcome.isOk()); assertEquals("28571000087109", outcome.getCode()); assertEquals("MODERNA COVID-19 mRNA-1273", outcome.getDisplay()); - assertEquals("http://snomed.info/sct/20611000087101/version/20210331", outcome.getCodeSystemVersion()); + assertEquals("0.17", outcome.getCodeSystemVersion()); assertThat(outcome.getMessage(), containsString("Concept Display \"BLAH\" does not match expected \"MODERNA COVID-19 mRNA-1273\"")); assertEquals("warning", outcome.getSeverityCode()); assertThat(outcome.getSourceDetails(), startsWith("Code was validated against in-memory expansion"));