001package org.hl7.fhir.common.hapi.validation.support; 002 003import ca.uhn.fhir.context.FhirContext; 004import ca.uhn.fhir.context.FhirVersionEnum; 005import ca.uhn.fhir.context.support.ConceptValidationOptions; 006import ca.uhn.fhir.context.support.IValidationSupport; 007import ca.uhn.fhir.context.support.ValidationSupportContext; 008import ca.uhn.fhir.context.support.ValueSetExpansionOptions; 009import ca.uhn.fhir.i18n.Msg; 010import ca.uhn.fhir.parser.IParser; 011import ca.uhn.fhir.util.FhirVersionIndependentConcept; 012import ca.uhn.hapi.converters.canonical.VersionCanonicalizer; 013import org.apache.commons.lang3.Validate; 014import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_10_50; 015import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_30_50; 016import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_40_50; 017import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_43_50; 018import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_50; 019import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_50; 020import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50; 021import org.hl7.fhir.convertors.factory.VersionConvertorFactory_43_50; 022import org.hl7.fhir.dstu2.model.ValueSet; 023import org.hl7.fhir.instance.model.api.IBaseResource; 024import org.hl7.fhir.instance.model.api.IPrimitiveType; 025import org.hl7.fhir.r5.model.CanonicalType; 026import org.hl7.fhir.r5.model.CodeSystem; 027import org.hl7.fhir.utilities.validation.ValidationMessage; 028 029import javax.annotation.Nonnull; 030import javax.annotation.Nullable; 031import java.util.ArrayList; 032import java.util.Collections; 033import java.util.HashSet; 034import java.util.List; 035import java.util.Objects; 036import java.util.Optional; 037import java.util.Set; 038import java.util.function.Consumer; 039import java.util.function.Function; 040import java.util.stream.Collectors; 041 042import static org.apache.commons.lang3.StringUtils.contains; 043import static org.apache.commons.lang3.StringUtils.defaultString; 044import static org.apache.commons.lang3.StringUtils.isBlank; 045import static org.apache.commons.lang3.StringUtils.isNotBlank; 046import static org.apache.commons.lang3.StringUtils.substringAfter; 047import static org.apache.commons.lang3.StringUtils.substringBefore; 048import static org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService.getFhirVersionEnum; 049 050/** 051 * This class is a basic in-memory terminology service, designed to expand ValueSets and validate codes 052 * completely in-memory. It is suitable for runtime validation purposes where no dedicated terminology 053 * service exists (either an internal one such as the HAPI FHIR JPA terminology service, or an 054 * external term service API) 055 */ 056@SuppressWarnings("EnhancedSwitchMigration") 057public class InMemoryTerminologyServerValidationSupport implements IValidationSupport { 058 private static final String OUR_PIPE_CHARACTER = "|"; 059 060 private final FhirContext myCtx; 061 private final VersionCanonicalizer myVersionCanonicalizer; 062 063 public InMemoryTerminologyServerValidationSupport(FhirContext theCtx) { 064 Validate.notNull(theCtx, "theCtx must not be null"); 065 myCtx = theCtx; 066 myVersionCanonicalizer = new VersionCanonicalizer(theCtx); 067 } 068 069 @Override 070 public FhirContext getFhirContext() { 071 return myCtx; 072 } 073 074 @Override 075 public ValueSetExpansionOutcome expandValueSet(ValidationSupportContext theValidationSupportContext, ValueSetExpansionOptions theExpansionOptions, @Nonnull IBaseResource theValueSetToExpand) { 076 return expandValueSet(theValidationSupportContext, theValueSetToExpand, null, null); 077 } 078 079 private ValueSetExpansionOutcome expandValueSet(ValidationSupportContext theValidationSupportContext, IBaseResource theValueSetToExpand, String theWantSystemAndVersion, String theWantCode) { 080 org.hl7.fhir.r5.model.ValueSet expansionR5; 081 try { 082 expansionR5 = expandValueSetToCanonical(theValidationSupportContext, theValueSetToExpand, theWantSystemAndVersion, theWantCode); 083 } catch (ExpansionCouldNotBeCompletedInternallyException e) { 084 return new ValueSetExpansionOutcome(e.getMessage()); 085 } 086 if (expansionR5 == null) { 087 return null; 088 } 089 090 IBaseResource expansion; 091 switch (myCtx.getVersion().getVersion()) { 092 case DSTU2: { 093 org.hl7.fhir.r4.model.ValueSet expansionR4 = (org.hl7.fhir.r4.model.ValueSet) VersionConvertorFactory_40_50.convertResource(expansionR5, new BaseAdvisor_40_50(false)); 094 expansion = myVersionCanonicalizer.valueSetFromCanonical(expansionR4); 095 break; 096 } 097 case DSTU2_HL7ORG: { 098 expansion = VersionConvertorFactory_10_50.convertResource(expansionR5, new BaseAdvisor_10_50(false)); 099 break; 100 } 101 case DSTU3: { 102 expansion = VersionConvertorFactory_30_50.convertResource(expansionR5, new BaseAdvisor_30_50(false)); 103 break; 104 } 105 case R4: { 106 expansion = VersionConvertorFactory_40_50.convertResource(expansionR5, new BaseAdvisor_40_50(false)); 107 break; 108 } 109 case R4B: { 110 expansion = VersionConvertorFactory_43_50.convertResource(expansionR5, new BaseAdvisor_43_50(false)); 111 break; 112 } 113 case R5: { 114 expansion = expansionR5; 115 break; 116 } 117 case DSTU2_1: 118 default: 119 throw new IllegalArgumentException(Msg.code(697) + "Can not handle version: " + myCtx.getVersion().getVersion()); 120 } 121 122 return new ValueSetExpansionOutcome(expansion); 123 } 124 125 private org.hl7.fhir.r5.model.ValueSet expandValueSetToCanonical(ValidationSupportContext theValidationSupportContext, IBaseResource theValueSetToExpand, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException { 126 org.hl7.fhir.r5.model.ValueSet expansionR5; 127 switch (getFhirVersionEnum(theValidationSupportContext.getRootValidationSupport().getFhirContext(), theValueSetToExpand)) { 128 case DSTU2: { 129 expansionR5 = expandValueSetDstu2(theValidationSupportContext, (ca.uhn.fhir.model.dstu2.resource.ValueSet) theValueSetToExpand, theWantSystemUrlAndVersion, theWantCode); 130 break; 131 } 132 case DSTU2_HL7ORG: { 133 expansionR5 = expandValueSetDstu2Hl7Org(theValidationSupportContext, (ValueSet) theValueSetToExpand, theWantSystemUrlAndVersion, theWantCode); 134 break; 135 } 136 case DSTU3: { 137 expansionR5 = expandValueSetDstu3(theValidationSupportContext, (org.hl7.fhir.dstu3.model.ValueSet) theValueSetToExpand, theWantSystemUrlAndVersion, theWantCode); 138 break; 139 } 140 case R4: { 141 expansionR5 = expandValueSetR4(theValidationSupportContext, (org.hl7.fhir.r4.model.ValueSet) theValueSetToExpand, theWantSystemUrlAndVersion, theWantCode); 142 break; 143 } 144 case R4B: { 145 expansionR5 = expandValueSetR4B(theValidationSupportContext, (org.hl7.fhir.r4b.model.ValueSet) theValueSetToExpand, theWantSystemUrlAndVersion, theWantCode); 146 break; 147 } 148 case R5: { 149 expansionR5 = expandValueSetR5(theValidationSupportContext, (org.hl7.fhir.r5.model.ValueSet) theValueSetToExpand, theWantSystemUrlAndVersion, theWantCode); 150 break; 151 } 152 case DSTU2_1: 153 default: 154 throw new IllegalArgumentException(Msg.code(698) + "Can not handle version: " + myCtx.getVersion().getVersion()); 155 } 156 157 return expansionR5; 158 } 159 160 @Override 161 public CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystemUrlAndVersion, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { 162 org.hl7.fhir.r5.model.ValueSet expansion; 163 String vsUrl = CommonCodeSystemsTerminologyService.getValueSetUrl(getFhirContext(), theValueSet); 164 try { 165 expansion = expandValueSetToCanonical(theValidationSupportContext, theValueSet, theCodeSystemUrlAndVersion, theCode); 166 } catch (ExpansionCouldNotBeCompletedInternallyException e) { 167 CodeValidationResult codeValidationResult = new CodeValidationResult(); 168 codeValidationResult.setSeverityCode("error"); 169 170 String msg = "Failed to expand ValueSet '" + vsUrl + "' (in-memory). Could not validate code " + theCodeSystemUrlAndVersion + "#" + theCode; 171 if (e.getMessage() != null) { 172 msg += ". Error was: " + e.getMessage(); 173 } 174 175 codeValidationResult.setMessage(msg); 176 return codeValidationResult; 177 } 178 179 if (expansion == null) { 180 return null; 181 } 182 183 return validateCodeInExpandedValueSet(theValidationSupportContext, theOptions, theCodeSystemUrlAndVersion, theCode, theDisplay, expansion, vsUrl); 184 } 185 186 187 @Override 188 @Nullable 189 public CodeValidationResult validateCode(@Nonnull ValidationSupportContext theValidationSupportContext, @Nonnull ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { 190 IBaseResource vs; 191 if (isNotBlank(theValueSetUrl)) { 192 vs = theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrl); 193 if (vs == null) { 194 return null; 195 } 196 } else { 197 String codeSystemUrl; 198 String codeSystemVersion = null; 199 int codeSystemVersionIndex = theCodeSystem.indexOf("|"); 200 if (codeSystemVersionIndex > -1) { 201 codeSystemUrl = theCodeSystem.substring(0, codeSystemVersionIndex); 202 codeSystemVersion = theCodeSystem.substring(codeSystemVersionIndex + 1); 203 } else { 204 codeSystemUrl = theCodeSystem; 205 } 206 switch (myCtx.getVersion().getVersion()) { 207 case DSTU2: 208 case DSTU2_HL7ORG: 209 vs = new org.hl7.fhir.dstu2.model.ValueSet() 210 .setCompose(new org.hl7.fhir.dstu2.model.ValueSet.ValueSetComposeComponent() 211 .addInclude(new org.hl7.fhir.dstu2.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); 212 break; 213 case DSTU3: 214 if (codeSystemVersion != null) { 215 vs = new org.hl7.fhir.dstu3.model.ValueSet() 216 .setCompose(new org.hl7.fhir.dstu3.model.ValueSet.ValueSetComposeComponent() 217 .addInclude(new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent().setSystem(codeSystemUrl).setVersion(codeSystemVersion))); 218 } else { 219 vs = new org.hl7.fhir.dstu3.model.ValueSet() 220 .setCompose(new org.hl7.fhir.dstu3.model.ValueSet.ValueSetComposeComponent() 221 .addInclude(new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); 222 } 223 break; 224 case R4: 225 if (codeSystemVersion != null) { 226 vs = new org.hl7.fhir.r4.model.ValueSet() 227 .setCompose(new org.hl7.fhir.r4.model.ValueSet.ValueSetComposeComponent() 228 .addInclude(new org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent().setSystem(codeSystemUrl).setVersion(codeSystemVersion))); 229 } else { 230 vs = new org.hl7.fhir.r4.model.ValueSet() 231 .setCompose(new org.hl7.fhir.r4.model.ValueSet.ValueSetComposeComponent() 232 .addInclude(new org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); 233 } 234 break; 235 case R4B: 236 if (codeSystemVersion != null) { 237 vs = new org.hl7.fhir.r4b.model.ValueSet() 238 .setCompose(new org.hl7.fhir.r4b.model.ValueSet.ValueSetComposeComponent() 239 .addInclude(new org.hl7.fhir.r4b.model.ValueSet.ConceptSetComponent().setSystem(codeSystemUrl).setVersion(codeSystemVersion))); 240 } else { 241 vs = new org.hl7.fhir.r4b.model.ValueSet() 242 .setCompose(new org.hl7.fhir.r4b.model.ValueSet.ValueSetComposeComponent() 243 .addInclude(new org.hl7.fhir.r4b.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); 244 } 245 break; 246 case R5: 247 if (codeSystemVersion != null) { 248 vs = new org.hl7.fhir.r5.model.ValueSet() 249 .setCompose(new org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent() 250 .addInclude(new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent().setSystem(codeSystemUrl).setVersion(codeSystemVersion))); 251 } else { 252 vs = new org.hl7.fhir.r5.model.ValueSet() 253 .setCompose(new org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent() 254 .addInclude(new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); 255 } 256 break; 257 case DSTU2_1: 258 default: 259 throw new IllegalArgumentException(Msg.code(699) + "Can not handle version: " + myCtx.getVersion().getVersion()); 260 } 261 } 262 263 ValueSetExpansionOutcome valueSetExpansionOutcome = expandValueSet(theValidationSupportContext, vs, theCodeSystem, theCode); 264 if (valueSetExpansionOutcome == null) { 265 return null; 266 } 267 268 if (valueSetExpansionOutcome.getError() != null) { 269 return new CodeValidationResult() 270 .setSeverity(IssueSeverity.ERROR) 271 .setMessage(valueSetExpansionOutcome.getError()); 272 } 273 274 IBaseResource expansion = valueSetExpansionOutcome.getValueSet(); 275 return validateCodeInExpandedValueSet(theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, expansion, theValueSetUrl); 276 } 277 278 private CodeValidationResult validateCodeInExpandedValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystemUrlAndVersionToValidate, String theCodeToValidate, String theDisplayToValidate, IBaseResource theExpansion, String theValueSetUrl) { 279 assert theExpansion != null; 280 281 boolean caseSensitive = true; 282 IBaseResource codeSystemToValidateResource = null; 283 if (!theOptions.isInferSystem() && isNotBlank(theCodeSystemUrlAndVersionToValidate)) { 284 codeSystemToValidateResource = theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(theCodeSystemUrlAndVersionToValidate); 285 } 286 287 List<FhirVersionIndependentConcept> codes = new ArrayList<>(); 288 switch (getFhirVersionEnum(theValidationSupportContext.getRootValidationSupport().getFhirContext(), theExpansion)) { 289 case DSTU2: { 290 ca.uhn.fhir.model.dstu2.resource.ValueSet expansionVs = (ca.uhn.fhir.model.dstu2.resource.ValueSet) theExpansion; 291 List<ca.uhn.fhir.model.dstu2.resource.ValueSet.ExpansionContains> contains = expansionVs.getExpansion().getContains(); 292 flattenAndConvertCodesDstu2(contains, codes); 293 break; 294 } 295 case DSTU2_HL7ORG: { 296 ValueSet expansionVs = (ValueSet) theExpansion; 297 List<ValueSet.ValueSetExpansionContainsComponent> contains = expansionVs.getExpansion().getContains(); 298 flattenAndConvertCodesDstu2Hl7Org(contains, codes); 299 break; 300 } 301 case DSTU3: { 302 org.hl7.fhir.dstu3.model.ValueSet expansionVs = (org.hl7.fhir.dstu3.model.ValueSet) theExpansion; 303 List<org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent> contains = expansionVs.getExpansion().getContains(); 304 flattenAndConvertCodesDstu3(contains, codes); 305 break; 306 } 307 case R4: { 308 org.hl7.fhir.r4.model.ValueSet expansionVs = (org.hl7.fhir.r4.model.ValueSet) theExpansion; 309 List<org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent> contains = expansionVs.getExpansion().getContains(); 310 flattenAndConvertCodesR4(contains, codes); 311 break; 312 } 313 case R4B: { 314 org.hl7.fhir.r4b.model.ValueSet expansionVs = (org.hl7.fhir.r4b.model.ValueSet) theExpansion; 315 List<org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionContainsComponent> contains = expansionVs.getExpansion().getContains(); 316 flattenAndConvertCodesR4B(contains, codes); 317 break; 318 } 319 case R5: { 320 org.hl7.fhir.r5.model.ValueSet expansionVs = (org.hl7.fhir.r5.model.ValueSet) theExpansion; 321 List<org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent> contains = expansionVs.getExpansion().getContains(); 322 flattenAndConvertCodesR5(contains, codes); 323 break; 324 } 325 case DSTU2_1: 326 default: 327 throw new IllegalArgumentException(Msg.code(700) + "Can not handle version: " + myCtx.getVersion().getVersion()); 328 } 329 330 String codeSystemResourceName = null; 331 String codeSystemResourceVersion = null; 332 String codeSystemResourceContentMode = null; 333 if (codeSystemToValidateResource != null) { 334 switch (getFhirVersionEnum(theValidationSupportContext.getRootValidationSupport().getFhirContext(), codeSystemToValidateResource)) { 335 case DSTU2: 336 case DSTU2_HL7ORG: { 337 caseSensitive = true; 338 break; 339 } 340 case DSTU3: { 341 org.hl7.fhir.dstu3.model.CodeSystem systemDstu3 = (org.hl7.fhir.dstu3.model.CodeSystem) codeSystemToValidateResource; 342 caseSensitive = systemDstu3.getCaseSensitive(); 343 codeSystemResourceName = systemDstu3.getName(); 344 codeSystemResourceVersion = systemDstu3.getVersion(); 345 codeSystemResourceContentMode = systemDstu3.getContentElement().getValueAsString(); 346 break; 347 } 348 case R4: { 349 org.hl7.fhir.r4.model.CodeSystem systemR4 = (org.hl7.fhir.r4.model.CodeSystem) codeSystemToValidateResource; 350 caseSensitive = systemR4.getCaseSensitive(); 351 codeSystemResourceName = systemR4.getName(); 352 codeSystemResourceVersion = systemR4.getVersion(); 353 codeSystemResourceContentMode = systemR4.getContentElement().getValueAsString(); 354 break; 355 } 356 case R4B: { 357 org.hl7.fhir.r4b.model.CodeSystem systemR4B = (org.hl7.fhir.r4b.model.CodeSystem) codeSystemToValidateResource; 358 caseSensitive = systemR4B.getCaseSensitive(); 359 codeSystemResourceName = systemR4B.getName(); 360 codeSystemResourceVersion = systemR4B.getVersion(); 361 codeSystemResourceContentMode = systemR4B.getContentElement().getValueAsString(); 362 break; 363 } 364 case R5: { 365 CodeSystem systemR5 = (CodeSystem) codeSystemToValidateResource; 366 caseSensitive = systemR5.getCaseSensitive(); 367 codeSystemResourceName = systemR5.getName(); 368 codeSystemResourceVersion = systemR5.getVersion(); 369 codeSystemResourceContentMode = systemR5.getContentElement().getValueAsString(); 370 break; 371 } 372 case DSTU2_1: 373 default: 374 throw new IllegalArgumentException(Msg.code(701) + "Can not handle version: " + myCtx.getVersion().getVersion()); 375 } 376 } 377 378 String codeSystemUrlToValidate = null; 379 String codeSystemVersionToValidate = null; 380 if (theCodeSystemUrlAndVersionToValidate != null) { 381 int versionIndex = theCodeSystemUrlAndVersionToValidate.indexOf("|"); 382 if (versionIndex > -1) { 383 codeSystemUrlToValidate = theCodeSystemUrlAndVersionToValidate.substring(0, versionIndex); 384 codeSystemVersionToValidate = theCodeSystemUrlAndVersionToValidate.substring(versionIndex + 1); 385 } else { 386 codeSystemUrlToValidate = theCodeSystemUrlAndVersionToValidate; 387 } 388 } 389 for (FhirVersionIndependentConcept nextExpansionCode : codes) { 390 391 boolean codeMatches; 392 if (caseSensitive) { 393 codeMatches = defaultString(theCodeToValidate).equals(nextExpansionCode.getCode()); 394 } else { 395 codeMatches = defaultString(theCodeToValidate).equalsIgnoreCase(nextExpansionCode.getCode()); 396 } 397 if (codeMatches) { 398 if (theOptions.isInferSystem() || (nextExpansionCode.getSystem().equals(codeSystemUrlToValidate) && (codeSystemVersionToValidate == null || codeSystemVersionToValidate.equals(nextExpansionCode.getSystemVersion())))) { 399 String csVersion = codeSystemResourceVersion; 400 if (isNotBlank(nextExpansionCode.getSystemVersion())) { 401 csVersion = nextExpansionCode.getSystemVersion(); 402 } 403 if (!theOptions.isValidateDisplay() || (isBlank(nextExpansionCode.getDisplay()) || isBlank(theDisplayToValidate) || nextExpansionCode.getDisplay().equals(theDisplayToValidate))) { 404 CodeValidationResult codeValidationResult = new CodeValidationResult() 405 .setCode(theCodeToValidate) 406 .setDisplay(nextExpansionCode.getDisplay()) 407 .setCodeSystemName(codeSystemResourceName) 408 .setCodeSystemVersion(csVersion); 409 if (isNotBlank(theValueSetUrl)) { 410 codeValidationResult.setMessage("Code was validated against in-memory expansion of ValueSet: " + theValueSetUrl); 411 } 412 return codeValidationResult; 413 } else { 414 String message = "Concept Display \"" + theDisplayToValidate + "\" does not match expected \"" + nextExpansionCode.getDisplay() + "\""; 415 if (isNotBlank(theValueSetUrl)) { 416 message += " for in-memory expansion of ValueSet: " + theValueSetUrl; 417 } 418 return new CodeValidationResult() 419 .setSeverity(IssueSeverity.ERROR) 420 .setDisplay(nextExpansionCode.getDisplay()) 421 .setMessage(message) 422 .setCodeSystemName(codeSystemResourceName) 423 .setCodeSystemVersion(csVersion); 424 } 425 } 426 } 427 } 428 429 ValidationMessage.IssueSeverity severity; 430 String message; 431 if ("fragment".equals(codeSystemResourceContentMode)) { 432 severity = ValidationMessage.IssueSeverity.WARNING; 433 message = "Unknown code in fragment CodeSystem '" + (isNotBlank(theCodeSystemUrlAndVersionToValidate) ? theCodeSystemUrlAndVersionToValidate + "#" : "") + theCodeToValidate + "'"; 434 } else { 435 severity = ValidationMessage.IssueSeverity.ERROR; 436 message = "Unknown code '" + (isNotBlank(theCodeSystemUrlAndVersionToValidate) ? theCodeSystemUrlAndVersionToValidate + "#" : "") + theCodeToValidate + "'"; 437 } 438 if (isNotBlank(theValueSetUrl)) { 439 message += " for in-memory expansion of ValueSet '" + theValueSetUrl + "'"; 440 } 441 442 return new CodeValidationResult() 443 .setSeverityCode(severity.toCode()) 444 .setMessage(message); 445 } 446 447 @Override 448 public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theDisplayLanguage) { 449 CodeValidationResult codeValidationResult = validateCode(theValidationSupportContext, new ConceptValidationOptions(), theSystem, theCode, null, null); 450 if (codeValidationResult == null) { 451 return null; 452 } 453 return codeValidationResult.asLookupCodeResult(theSystem, theCode); 454 } 455 456 @Nullable 457 private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2Hl7Org(ValidationSupportContext theValidationSupportContext, ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException { 458 org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_10_50.convertResource(theInput, new BaseAdvisor_10_50(false)); 459 return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode)); 460 } 461 462 @Nullable 463 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 { 464 IParser parserRi = FhirContext.forCached(FhirVersionEnum.DSTU2_HL7ORG).newJsonParser(); 465 IParser parserHapi = FhirContext.forDstu2Cached().newJsonParser(); 466 467 org.hl7.fhir.dstu2.model.ValueSet valueSetRi = parserRi.parseResource(org.hl7.fhir.dstu2.model.ValueSet.class, parserHapi.encodeResourceToString(theInput)); 468 org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_10_50.convertResource(valueSetRi, new BaseAdvisor_10_50(false)); 469 return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode)); 470 } 471 472 @Override 473 public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) { 474 if (isBlank(theSystem)) { 475 return false; 476 } 477 478 IBaseResource cs = theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(theSystem); 479 480 if (!myCtx.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU2_1)) { 481 return cs != null; 482 } 483 484 if (cs != null) { 485 IPrimitiveType<?> content = getFhirContext().newTerser().getSingleValueOrNull(cs, "content", IPrimitiveType.class); 486 return !"not-present".equals(content.getValueAsString()); 487 } 488 489 return false; 490 } 491 492 @Override 493 public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) { 494 return isNotBlank(theValueSetUrl) && theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrl) != null; 495 } 496 497 498 private void addCodesDstu2Hl7Org(List<ValueSet.ConceptDefinitionComponent> theSourceList, List<CodeSystem.ConceptDefinitionComponent> theTargetList) { 499 for (ValueSet.ConceptDefinitionComponent nextSource : theSourceList) { 500 CodeSystem.ConceptDefinitionComponent targetConcept = new CodeSystem.ConceptDefinitionComponent().setCode(nextSource.getCode()).setDisplay(nextSource.getDisplay()); 501 theTargetList.add(targetConcept); 502 addCodesDstu2Hl7Org(nextSource.getConcept(), targetConcept.getConcept()); 503 } 504 } 505 506 private void addCodesDstu2(List<ca.uhn.fhir.model.dstu2.resource.ValueSet.CodeSystemConcept> theSourceList, List<CodeSystem.ConceptDefinitionComponent> theTargetList) { 507 for (ca.uhn.fhir.model.dstu2.resource.ValueSet.CodeSystemConcept nextSource : theSourceList) { 508 CodeSystem.ConceptDefinitionComponent targetConcept = new CodeSystem.ConceptDefinitionComponent().setCode(nextSource.getCode()).setDisplay(nextSource.getDisplay()); 509 theTargetList.add(targetConcept); 510 addCodesDstu2(nextSource.getConcept(), targetConcept.getConcept()); 511 } 512 } 513 514 @Nullable 515 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 { 516 org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_30_50.convertResource(theInput, new BaseAdvisor_30_50(false)); 517 return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode)); 518 } 519 520 @Nullable 521 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 { 522 org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_40_50.convertResource(theInput, new BaseAdvisor_40_50(false)); 523 return expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode); 524 } 525 526 @Nullable 527 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 { 528 org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_43_50.convertResource(theInput, new BaseAdvisor_43_50(false)); 529 return expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode); 530 } 531 532 @Nullable 533 private org.hl7.fhir.r5.model.ValueSet expandValueSetR5(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r5.model.ValueSet theInput) throws ExpansionCouldNotBeCompletedInternallyException { 534 return expandValueSetR5(theValidationSupportContext, theInput, null, null); 535 } 536 537 @Nullable 538 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 { 539 Set<FhirVersionIndependentConcept> concepts = new HashSet<>(); 540 541 expandValueSetR5IncludeOrExcludes(theValidationSupportContext, concepts, theInput.getCompose().getInclude(), true, theWantSystemUrlAndVersion, theWantCode); 542 expandValueSetR5IncludeOrExcludes(theValidationSupportContext, concepts, theInput.getCompose().getExclude(), false, theWantSystemUrlAndVersion, theWantCode); 543 544 org.hl7.fhir.r5.model.ValueSet retVal = new org.hl7.fhir.r5.model.ValueSet(); 545 for (FhirVersionIndependentConcept next : concepts) { 546 org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent contains = retVal.getExpansion().addContains(); 547 contains.setSystem(next.getSystem()); 548 contains.setCode(next.getCode()); 549 contains.setDisplay(next.getDisplay()); 550 contains.setVersion(next.getSystemVersion()); 551 } 552 553 return retVal; 554 } 555 556 /** 557 * Use with caution - this is not a stable API 558 * 559 * @since 5.6.0 560 */ 561 public void expandValueSetIncludeOrExclude(ValidationSupportContext theValidationSupportContext, Consumer<FhirVersionIndependentConcept> theConsumer, org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent theIncludeOrExclude) throws ExpansionCouldNotBeCompletedInternallyException { 562 expandValueSetR5IncludeOrExclude(theValidationSupportContext, theConsumer, null, null, theIncludeOrExclude); 563 } 564 565 566 private void expandValueSetR5IncludeOrExcludes(ValidationSupportContext theValidationSupportContext, Set<FhirVersionIndependentConcept> theConcepts, List<org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent> theComposeList, boolean theComposeListIsInclude, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException { 567 Consumer<FhirVersionIndependentConcept> consumer = c -> { 568 if (theComposeListIsInclude) { 569 theConcepts.add(c); 570 } else { 571 theConcepts.remove(c); 572 } 573 }; 574 expandValueSetR5IncludeOrExcludes(theValidationSupportContext, consumer, theComposeList, theWantSystemUrlAndVersion, theWantCode); 575 } 576 577 578 private void expandValueSetR5IncludeOrExcludes(ValidationSupportContext theValidationSupportContext, Consumer<FhirVersionIndependentConcept> theConsumer, List<org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent> theComposeList, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException { 579 ExpansionCouldNotBeCompletedInternallyException caughtException = null; 580 for (org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent nextInclude : theComposeList) { 581 try { 582 boolean outcome = expandValueSetR5IncludeOrExclude(theValidationSupportContext, theConsumer, theWantSystemUrlAndVersion, theWantCode, nextInclude); 583 if (isNotBlank(theWantCode)) { 584 if (outcome) { 585 return; 586 } 587 } 588 } catch (ExpansionCouldNotBeCompletedInternallyException e) { 589 if (isBlank(theWantCode)) { 590 throw e; 591 } else { 592 caughtException = e; 593 } 594 } 595 } 596 if (caughtException != null) { 597 throw caughtException; 598 } 599 } 600 601 /** 602 * Returns <code>true</code> if at least one code was added 603 */ 604 private boolean expandValueSetR5IncludeOrExclude(ValidationSupportContext theValidationSupportContext, Consumer<FhirVersionIndependentConcept> theConsumer, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode, org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent theInclude) throws ExpansionCouldNotBeCompletedInternallyException { 605 606 String wantSystemUrl = null; 607 String wantSystemVersion = null; 608 609 if (theWantSystemUrlAndVersion != null) { 610 int versionIndex = theWantSystemUrlAndVersion.indexOf(OUR_PIPE_CHARACTER); 611 if (versionIndex > -1) { 612 wantSystemUrl = theWantSystemUrlAndVersion.substring(0, versionIndex); 613 wantSystemVersion = theWantSystemUrlAndVersion.substring(versionIndex + 1); 614 } else { 615 wantSystemUrl = theWantSystemUrlAndVersion; 616 } 617 } 618 619 String includeOrExcludeConceptSystemUrl = theInclude.getSystem(); 620 String includeOrExcludeConceptSystemVersion = theInclude.getVersion(); 621 622 Function<String, CodeSystem> codeSystemLoader = newCodeSystemLoader(theValidationSupportContext); 623 Function<String, org.hl7.fhir.r5.model.ValueSet> valueSetLoader = newValueSetLoader(theValidationSupportContext); 624 625 List<FhirVersionIndependentConcept> nextCodeList = new ArrayList<>(); 626 CodeSystem includeOrExcludeSystemResource = null; 627 628 if (isNotBlank(includeOrExcludeConceptSystemUrl)) { 629 630 includeOrExcludeConceptSystemVersion = optionallyPopulateVersionFromUrl(includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion); 631 includeOrExcludeConceptSystemUrl = substringBefore(includeOrExcludeConceptSystemUrl, OUR_PIPE_CHARACTER); 632 633 if (wantSystemUrl != null && !wantSystemUrl.equals(includeOrExcludeConceptSystemUrl)) { 634 return false; 635 } 636 637 if (wantSystemVersion != null && !wantSystemVersion.equals(includeOrExcludeConceptSystemVersion)) { 638 return false; 639 } 640 641 String loadedCodeSystemUrl; 642 if (includeOrExcludeConceptSystemVersion != null) { 643 loadedCodeSystemUrl = includeOrExcludeConceptSystemUrl + OUR_PIPE_CHARACTER + includeOrExcludeConceptSystemVersion; 644 } else { 645 loadedCodeSystemUrl = includeOrExcludeConceptSystemUrl; 646 } 647 648 includeOrExcludeSystemResource = codeSystemLoader.apply(loadedCodeSystemUrl); 649 650 Set<String> wantCodes; 651 if (theInclude.getConcept().isEmpty()) { 652 wantCodes = null; 653 } else { 654 wantCodes = theInclude 655 .getConcept() 656 .stream().map(t -> t.getCode()).collect(Collectors.toSet()); 657 } 658 659 boolean ableToHandleCode = false; 660 String failureMessage = null; 661 FailureType failureType = FailureType.OTHER; 662 663 if (includeOrExcludeSystemResource == null || includeOrExcludeSystemResource.getContent() == CodeSystem.CodeSystemContentMode.NOTPRESENT) { 664 665 if (theWantCode != null) { 666 if (theValidationSupportContext.getRootValidationSupport().isCodeSystemSupported(theValidationSupportContext, includeOrExcludeConceptSystemUrl)) { 667 LookupCodeResult lookup = theValidationSupportContext.getRootValidationSupport().lookupCode(theValidationSupportContext, includeOrExcludeConceptSystemUrl, theWantCode, null); 668 if (lookup != null) { 669 ableToHandleCode = true; 670 if (lookup.isFound()) { 671 CodeSystem.ConceptDefinitionComponent conceptDefinition = new CodeSystem.ConceptDefinitionComponent() 672 .addConcept() 673 .setCode(theWantCode) 674 .setDisplay(lookup.getCodeDisplay()); 675 List<CodeSystem.ConceptDefinitionComponent> codesList = Collections.singletonList(conceptDefinition); 676 addCodes(includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion, codesList, nextCodeList, wantCodes); 677 } 678 } 679 } else { 680 681 /* 682 * If we're doing an expansion specifically looking for a single code, that means we're validating that code. 683 * In the case where we have a ValueSet that explicitly enumerates a collection of codes 684 * (via ValueSet.compose.include.code) in a code system that is unknown we'll assume the code is valid 685 * even if we can't find the CodeSystem. This is a compromise obviously, since it would be ideal for 686 * CodeSystems to always be known, but realistically there are always going to be CodeSystems that 687 * can't be supplied because of copyright issues, or because they are grammar based. Allowing a VS to 688 * enumerate a set of good codes for them is a nice compromise there. 689 */ 690 if (Objects.equals(theInclude.getSystem(), theWantSystemUrlAndVersion)) { 691 Optional<org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent> matchingEnumeratedConcept = theInclude.getConcept().stream().filter(t -> Objects.equals(t.getCode(), theWantCode)).findFirst(); 692 693 // If the ValueSet.compose.include has no individual concepts in it, and 694 // we can't find the actual referenced CodeSystem, we have no choice 695 // but to fail 696 if (!theInclude.getConcept().isEmpty()) { 697 ableToHandleCode = true; 698 } else { 699 failureMessage = getFailureMessageForMissingOrUnusableCodeSystem(includeOrExcludeSystemResource, loadedCodeSystemUrl); 700 } 701 702 if (matchingEnumeratedConcept.isPresent()) { 703 CodeSystem.ConceptDefinitionComponent conceptDefinition = new CodeSystem.ConceptDefinitionComponent() 704 .addConcept() 705 .setCode(theWantCode) 706 .setDisplay(matchingEnumeratedConcept.get().getDisplay()); 707 List<CodeSystem.ConceptDefinitionComponent> codesList = Collections.singletonList(conceptDefinition); 708 addCodes(includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion, codesList, nextCodeList, wantCodes); 709 } 710 } 711 712 } 713 } else { 714 if (isNotBlank(theInclude.getSystem()) && !theInclude.getConcept().isEmpty() && theInclude.getFilter().isEmpty() && theInclude.getValueSet().isEmpty()) { 715 theInclude 716 .getConcept() 717 .stream() 718 .map(t -> new FhirVersionIndependentConcept(theInclude.getSystem(), t.getCode(), t.getDisplay(), theInclude.getVersion())) 719 .forEach(t -> nextCodeList.add(t)); 720 ableToHandleCode = true; 721 } 722 723 if (!ableToHandleCode) { 724 failureMessage = getFailureMessageForMissingOrUnusableCodeSystem(includeOrExcludeSystemResource, loadedCodeSystemUrl); 725 } 726 } 727 728 } else { 729 ableToHandleCode = true; 730 } 731 732 if (!ableToHandleCode) { 733 if (includeOrExcludeSystemResource == null && failureMessage == null) { 734 failureMessage = getFailureMessageForMissingOrUnusableCodeSystem(includeOrExcludeSystemResource, loadedCodeSystemUrl); 735 } 736 737 if (includeOrExcludeSystemResource == null) { 738 failureType = FailureType.UNKNOWN_CODE_SYSTEM; 739 } 740 741 throw new ExpansionCouldNotBeCompletedInternallyException(Msg.code(702) + failureMessage, failureType); 742 } 743 744 if (includeOrExcludeSystemResource != null && includeOrExcludeSystemResource.getContent() != CodeSystem.CodeSystemContentMode.NOTPRESENT) { 745 addCodes(includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion, includeOrExcludeSystemResource.getConcept(), nextCodeList, wantCodes); 746 } 747 748 } 749 750 for (CanonicalType nextValueSetInclude : theInclude.getValueSet()) { 751 org.hl7.fhir.r5.model.ValueSet vs = valueSetLoader.apply(nextValueSetInclude.getValueAsString()); 752 if (vs != null) { 753 org.hl7.fhir.r5.model.ValueSet subExpansion = expandValueSetR5(theValidationSupportContext, vs, theWantSystemUrlAndVersion, theWantCode); 754 if (subExpansion == null) { 755 throw new ExpansionCouldNotBeCompletedInternallyException(Msg.code(703) + "Failed to expand ValueSet: " + nextValueSetInclude.getValueAsString(), FailureType.OTHER); 756 } 757 for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : subExpansion.getExpansion().getContains()) { 758 nextCodeList.add(new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); 759 } 760 } 761 } 762 763 boolean retVal = false; 764 765 for (FhirVersionIndependentConcept next : nextCodeList) { 766 if (includeOrExcludeSystemResource != null && theWantCode != null) { 767 boolean matches; 768 if (includeOrExcludeSystemResource.getCaseSensitive()) { 769 matches = theWantCode.equals(next.getCode()); 770 } else { 771 matches = theWantCode.equalsIgnoreCase(next.getCode()); 772 } 773 if (!matches) { 774 continue; 775 } 776 } 777 778 theConsumer.accept(next); 779 retVal = true; 780 } 781 782 return retVal; 783 } 784 785 private Function<String, org.hl7.fhir.r5.model.ValueSet> newValueSetLoader(ValidationSupportContext theValidationSupportContext) { 786 switch (myCtx.getVersion().getVersion()) { 787 case DSTU2: 788 case DSTU2_HL7ORG: 789 return t -> { 790 IBaseResource vs = theValidationSupportContext.getRootValidationSupport().fetchValueSet(t); 791 if (vs instanceof ca.uhn.fhir.model.dstu2.resource.ValueSet) { 792 IParser parserRi = FhirContext.forCached(FhirVersionEnum.DSTU2_HL7ORG).newJsonParser(); 793 IParser parserHapi = FhirContext.forDstu2Cached().newJsonParser(); 794 ca.uhn.fhir.model.dstu2.resource.ValueSet valueSet = (ca.uhn.fhir.model.dstu2.resource.ValueSet) vs; 795 org.hl7.fhir.dstu2.model.ValueSet valueSetRi = parserRi.parseResource(org.hl7.fhir.dstu2.model.ValueSet.class, parserHapi.encodeResourceToString(valueSet)); 796 return (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_10_50.convertResource(valueSetRi, new BaseAdvisor_10_50(false)); 797 } else { 798 org.hl7.fhir.dstu2.model.ValueSet valueSet = (org.hl7.fhir.dstu2.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t); 799 return (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_10_50.convertResource(valueSet, new BaseAdvisor_10_50(false)); 800 } 801 }; 802 case DSTU3: 803 return t -> { 804 org.hl7.fhir.dstu3.model.ValueSet valueSet = (org.hl7.fhir.dstu3.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t); 805 return (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_30_50.convertResource(valueSet, new BaseAdvisor_30_50(false)); 806 }; 807 case R4: 808 return t -> { 809 org.hl7.fhir.r4.model.ValueSet valueSet = (org.hl7.fhir.r4.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t); 810 return (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_40_50.convertResource(valueSet, new BaseAdvisor_40_50(false)); 811 }; 812 case R4B: 813 return t -> { 814 org.hl7.fhir.r4b.model.ValueSet valueSet = (org.hl7.fhir.r4b.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t); 815 return (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_43_50.convertResource(valueSet, new BaseAdvisor_43_50(false)); 816 }; 817 default: 818 case DSTU2_1: 819 case R5: 820 return t -> (org.hl7.fhir.r5.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t); 821 } 822 } 823 824 private Function<String, CodeSystem> newCodeSystemLoader(ValidationSupportContext theValidationSupportContext) { 825 switch (myCtx.getVersion().getVersion()) { 826 case DSTU2: 827 case DSTU2_HL7ORG: 828 return t -> { 829 IBaseResource codeSystem = theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); 830 CodeSystem retVal = null; 831 if (codeSystem != null) { 832 retVal = new CodeSystem(); 833 if (codeSystem instanceof ca.uhn.fhir.model.dstu2.resource.ValueSet) { 834 ca.uhn.fhir.model.dstu2.resource.ValueSet codeSystemCasted = (ca.uhn.fhir.model.dstu2.resource.ValueSet) codeSystem; 835 retVal.setUrl(codeSystemCasted.getUrl()); 836 addCodesDstu2(codeSystemCasted.getCodeSystem().getConcept(), retVal.getConcept()); 837 } else { 838 org.hl7.fhir.dstu2.model.ValueSet codeSystemCasted = (org.hl7.fhir.dstu2.model.ValueSet) codeSystem; 839 retVal.setUrl(codeSystemCasted.getUrl()); 840 addCodesDstu2Hl7Org(codeSystemCasted.getCodeSystem().getConcept(), retVal.getConcept()); 841 } 842 } 843 return retVal; 844 }; 845 case DSTU3: 846 return t -> { 847 org.hl7.fhir.dstu3.model.CodeSystem codeSystem = (org.hl7.fhir.dstu3.model.CodeSystem) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); 848 return (CodeSystem) VersionConvertorFactory_30_50.convertResource(codeSystem, new BaseAdvisor_30_50(false)); 849 }; 850 case R4: 851 return t -> { 852 org.hl7.fhir.r4.model.CodeSystem codeSystem = (org.hl7.fhir.r4.model.CodeSystem) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); 853 return (CodeSystem) VersionConvertorFactory_40_50.convertResource(codeSystem, new BaseAdvisor_40_50(false)); 854 }; 855 case R4B: 856 return t -> { 857 org.hl7.fhir.r4b.model.CodeSystem codeSystem = (org.hl7.fhir.r4b.model.CodeSystem) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); 858 return (CodeSystem) VersionConvertorFactory_43_50.convertResource(codeSystem, new BaseAdvisor_43_50(false)); 859 }; 860 case DSTU2_1: 861 case R5: 862 default: 863 return t -> (org.hl7.fhir.r5.model.CodeSystem) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); 864 865 } 866 } 867 868 private String getFailureMessageForMissingOrUnusableCodeSystem(CodeSystem includeOrExcludeSystemResource, String loadedCodeSystemUrl) { 869 String failureMessage; 870 if (includeOrExcludeSystemResource == null) { 871 failureMessage = "Unable to expand ValueSet because CodeSystem could not be found: " + loadedCodeSystemUrl; 872 } else { 873 assert includeOrExcludeSystemResource.getContent() == CodeSystem.CodeSystemContentMode.NOTPRESENT; 874 failureMessage = "Unable to expand ValueSet because CodeSystem has CodeSystem.content=not-present but contents were not found: " + loadedCodeSystemUrl; 875 } 876 return failureMessage; 877 } 878 879 private void addCodes(String theCodeSystemUrl, String theCodeSystemVersion, List<CodeSystem.ConceptDefinitionComponent> theSource, List<FhirVersionIndependentConcept> theTarget, Set<String> theCodeFilter) { 880 for (CodeSystem.ConceptDefinitionComponent next : theSource) { 881 if (isNotBlank(next.getCode())) { 882 if (theCodeFilter == null || theCodeFilter.contains(next.getCode())) { 883 theTarget.add(new FhirVersionIndependentConcept(theCodeSystemUrl, next.getCode(), next.getDisplay(), theCodeSystemVersion)); 884 } 885 } 886 addCodes(theCodeSystemUrl, theCodeSystemVersion, next.getConcept(), theTarget, theCodeFilter); 887 } 888 } 889 890 private String optionallyPopulateVersionFromUrl(String theSystemUrl, String theVersion) { 891 if(contains(theSystemUrl, OUR_PIPE_CHARACTER) && isBlank(theVersion)){ 892 theVersion = substringAfter(theSystemUrl, OUR_PIPE_CHARACTER); 893 } 894 return theVersion; 895 } 896 897 public enum FailureType { 898 899 UNKNOWN_CODE_SYSTEM, 900 OTHER 901 902 } 903 904 public static class ExpansionCouldNotBeCompletedInternallyException extends Exception { 905 906 private static final long serialVersionUID = -2226561628771483085L; 907 private final FailureType myFailureType; 908 909 public ExpansionCouldNotBeCompletedInternallyException(String theMessage, FailureType theFailureType) { 910 super(theMessage); 911 myFailureType = theFailureType; 912 } 913 914 public FailureType getFailureType() { 915 return myFailureType; 916 } 917 } 918 919 private static void flattenAndConvertCodesDstu2(List<ca.uhn.fhir.model.dstu2.resource.ValueSet.ExpansionContains> theInput, List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) { 920 for (ca.uhn.fhir.model.dstu2.resource.ValueSet.ExpansionContains next : theInput) { 921 theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); 922 flattenAndConvertCodesDstu2(next.getContains(), theFhirVersionIndependentConcepts); 923 } 924 } 925 926 private static void flattenAndConvertCodesDstu2Hl7Org(List<org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionContainsComponent> theInput, List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) { 927 for (org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { 928 theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); 929 flattenAndConvertCodesDstu2Hl7Org(next.getContains(), theFhirVersionIndependentConcepts); 930 } 931 } 932 933 private static void flattenAndConvertCodesDstu3(List<org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent> theInput, List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) { 934 for (org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { 935 theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); 936 flattenAndConvertCodesDstu3(next.getContains(), theFhirVersionIndependentConcepts); 937 } 938 } 939 940 private static void flattenAndConvertCodesR4(List<org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent> theInput, List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) { 941 for (org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { 942 theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); 943 flattenAndConvertCodesR4(next.getContains(), theFhirVersionIndependentConcepts); 944 } 945 } 946 947 private static void flattenAndConvertCodesR4B(List<org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionContainsComponent> theInput, List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) { 948 for (org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { 949 theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); 950 flattenAndConvertCodesR4B(next.getContains(), theFhirVersionIndependentConcepts); 951 } 952 } 953 954 private static void flattenAndConvertCodesR5(List<org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent> theInput, List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) { 955 for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { 956 theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); 957 flattenAndConvertCodesR5(next.getContains(), theFhirVersionIndependentConcepts); 958 } 959 } 960 961}