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.LookupCodeRequest; 008import ca.uhn.fhir.context.support.ValidationSupportContext; 009import ca.uhn.fhir.context.support.ValueSetExpansionOptions; 010import ca.uhn.fhir.i18n.Msg; 011import ca.uhn.fhir.parser.IParser; 012import ca.uhn.fhir.util.FhirVersionIndependentConcept; 013import ca.uhn.hapi.converters.canonical.VersionCanonicalizer; 014import jakarta.annotation.Nonnull; 015import jakarta.annotation.Nullable; 016import org.apache.commons.lang3.Validate; 017import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_10_50; 018import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_30_50; 019import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_40_50; 020import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_43_50; 021import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_50; 022import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_50; 023import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50; 024import org.hl7.fhir.convertors.factory.VersionConvertorFactory_43_50; 025import org.hl7.fhir.dstu2.model.ValueSet; 026import org.hl7.fhir.instance.model.api.IBaseResource; 027import org.hl7.fhir.instance.model.api.IPrimitiveType; 028import org.hl7.fhir.r5.model.CanonicalType; 029import org.hl7.fhir.r5.model.CodeSystem; 030import org.hl7.fhir.r5.model.Enumerations; 031import org.hl7.fhir.utilities.validation.ValidationMessage; 032 033import java.util.ArrayList; 034import java.util.Collections; 035import java.util.HashSet; 036import java.util.List; 037import java.util.Objects; 038import java.util.Optional; 039import java.util.Set; 040import java.util.function.Consumer; 041import java.util.function.Function; 042import java.util.stream.Collectors; 043 044import static org.apache.commons.lang3.StringUtils.contains; 045import static org.apache.commons.lang3.StringUtils.defaultString; 046import static org.apache.commons.lang3.StringUtils.isBlank; 047import static org.apache.commons.lang3.StringUtils.isNotBlank; 048import static org.apache.commons.lang3.StringUtils.substringAfter; 049import static org.apache.commons.lang3.StringUtils.substringBefore; 050import static org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService.getFhirVersionEnum; 051 052/** 053 * This class is a basic in-memory terminology service, designed to expand ValueSets and validate codes 054 * completely in-memory. It is suitable for runtime validation purposes where no dedicated terminology 055 * service exists (either an internal one such as the HAPI FHIR JPA terminology service, or an 056 * external term service API) 057 */ 058@SuppressWarnings("EnhancedSwitchMigration") 059public class InMemoryTerminologyServerValidationSupport implements IValidationSupport { 060 private static final String OUR_PIPE_CHARACTER = "|"; 061 private final FhirContext myCtx; 062 private final VersionCanonicalizer myVersionCanonicalizer; 063 private IssueSeverity myIssueSeverityForCodeDisplayMismatch = IssueSeverity.WARNING; 064 065 /** 066 * Constructor 067 * 068 * @param theCtx A FhirContext for the FHIR version being validated 069 */ 070 public InMemoryTerminologyServerValidationSupport(FhirContext theCtx) { 071 Validate.notNull(theCtx, "theCtx must not be null"); 072 myCtx = theCtx; 073 myVersionCanonicalizer = new VersionCanonicalizer(theCtx); 074 } 075 076 @Override 077 public String getName() { 078 return myCtx.getVersion().getVersion() + " In-Memory Validation Support"; 079 } 080 081 /** 082 * This setting controls the validation issue severity to report when a code validation 083 * finds that the code is present in the given CodeSystem, but the display name being 084 * validated doesn't match the expected value(s). Defaults to 085 * {@link ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity#WARNING}. Set this 086 * value to {@link ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity#INFORMATION} 087 * if you don't want to see display name validation issues at all in resource validation 088 * outcomes. 089 * 090 * @since 7.0.0 091 */ 092 public IssueSeverity getIssueSeverityForCodeDisplayMismatch() { 093 return myIssueSeverityForCodeDisplayMismatch; 094 } 095 096 /** 097 * This setting controls the validation issue severity to report when a code validation 098 * finds that the code is present in the given CodeSystem, but the display name being 099 * validated doesn't match the expected value(s). Defaults to 100 * {@link ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity#WARNING}. Set this 101 * value to {@link ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity#INFORMATION} 102 * if you don't want to see display name validation issues at all in resource validation 103 * outcomes. 104 * 105 * @param theIssueSeverityForCodeDisplayMismatch The severity. Must not be {@literal null}. 106 * @since 7.0.0 107 */ 108 public void setIssueSeverityForCodeDisplayMismatch(@Nonnull IssueSeverity theIssueSeverityForCodeDisplayMismatch) { 109 Validate.notNull( 110 theIssueSeverityForCodeDisplayMismatch, "theIssueSeverityForCodeDisplayMismatch must not be null"); 111 myIssueSeverityForCodeDisplayMismatch = theIssueSeverityForCodeDisplayMismatch; 112 } 113 114 @Override 115 public FhirContext getFhirContext() { 116 return myCtx; 117 } 118 119 @Override 120 public ValueSetExpansionOutcome expandValueSet( 121 ValidationSupportContext theValidationSupportContext, 122 ValueSetExpansionOptions theExpansionOptions, 123 @Nonnull IBaseResource theValueSetToExpand) { 124 return expandValueSet(theValidationSupportContext, theValueSetToExpand, null, null); 125 } 126 127 private ValueSetExpansionOutcome expandValueSet( 128 ValidationSupportContext theValidationSupportContext, 129 IBaseResource theValueSetToExpand, 130 String theWantSystemAndVersion, 131 String theWantCode) { 132 org.hl7.fhir.r5.model.ValueSet expansionR5; 133 try { 134 expansionR5 = expandValueSetToCanonical( 135 theValidationSupportContext, theValueSetToExpand, theWantSystemAndVersion, theWantCode); 136 } catch (ExpansionCouldNotBeCompletedInternallyException e) { 137 return new ValueSetExpansionOutcome(e.getMessage()); 138 } 139 if (expansionR5 == null) { 140 return null; 141 } 142 143 IBaseResource expansion; 144 switch (myCtx.getVersion().getVersion()) { 145 case DSTU2: { 146 org.hl7.fhir.r4.model.ValueSet expansionR4 = (org.hl7.fhir.r4.model.ValueSet) 147 VersionConvertorFactory_40_50.convertResource(expansionR5, new BaseAdvisor_40_50(false)); 148 expansion = myVersionCanonicalizer.valueSetFromCanonical(expansionR4); 149 break; 150 } 151 case DSTU2_HL7ORG: { 152 expansion = VersionConvertorFactory_10_50.convertResource(expansionR5, new BaseAdvisor_10_50(false)); 153 break; 154 } 155 case DSTU3: { 156 expansion = VersionConvertorFactory_30_50.convertResource(expansionR5, new BaseAdvisor_30_50(false)); 157 break; 158 } 159 case R4: { 160 expansion = VersionConvertorFactory_40_50.convertResource(expansionR5, new BaseAdvisor_40_50(false)); 161 break; 162 } 163 case R4B: { 164 expansion = VersionConvertorFactory_43_50.convertResource(expansionR5, new BaseAdvisor_43_50(false)); 165 break; 166 } 167 case R5: { 168 expansion = expansionR5; 169 break; 170 } 171 case DSTU2_1: 172 default: 173 throw new IllegalArgumentException(Msg.code(697) + "Can not handle version: " 174 + myCtx.getVersion().getVersion()); 175 } 176 177 return new ValueSetExpansionOutcome(expansion); 178 } 179 180 private org.hl7.fhir.r5.model.ValueSet expandValueSetToCanonical( 181 ValidationSupportContext theValidationSupportContext, 182 IBaseResource theValueSetToExpand, 183 @Nullable String theWantSystemUrlAndVersion, 184 @Nullable String theWantCode) 185 throws ExpansionCouldNotBeCompletedInternallyException { 186 org.hl7.fhir.r5.model.ValueSet expansionR5; 187 switch (getFhirVersionEnum( 188 theValidationSupportContext.getRootValidationSupport().getFhirContext(), theValueSetToExpand)) { 189 case DSTU2: { 190 expansionR5 = expandValueSetDstu2( 191 theValidationSupportContext, 192 (ca.uhn.fhir.model.dstu2.resource.ValueSet) theValueSetToExpand, 193 theWantSystemUrlAndVersion, 194 theWantCode); 195 break; 196 } 197 case DSTU2_HL7ORG: { 198 expansionR5 = expandValueSetDstu2Hl7Org( 199 theValidationSupportContext, 200 (ValueSet) theValueSetToExpand, 201 theWantSystemUrlAndVersion, 202 theWantCode); 203 break; 204 } 205 case DSTU3: { 206 expansionR5 = expandValueSetDstu3( 207 theValidationSupportContext, 208 (org.hl7.fhir.dstu3.model.ValueSet) theValueSetToExpand, 209 theWantSystemUrlAndVersion, 210 theWantCode); 211 break; 212 } 213 case R4: { 214 expansionR5 = expandValueSetR4( 215 theValidationSupportContext, 216 (org.hl7.fhir.r4.model.ValueSet) theValueSetToExpand, 217 theWantSystemUrlAndVersion, 218 theWantCode); 219 break; 220 } 221 case R4B: { 222 expansionR5 = expandValueSetR4B( 223 theValidationSupportContext, 224 (org.hl7.fhir.r4b.model.ValueSet) theValueSetToExpand, 225 theWantSystemUrlAndVersion, 226 theWantCode); 227 break; 228 } 229 case R5: { 230 expansionR5 = expandValueSetR5( 231 theValidationSupportContext, 232 (org.hl7.fhir.r5.model.ValueSet) theValueSetToExpand, 233 theWantSystemUrlAndVersion, 234 theWantCode); 235 break; 236 } 237 case DSTU2_1: 238 default: 239 throw new IllegalArgumentException(Msg.code(698) + "Can not handle version: " 240 + myCtx.getVersion().getVersion()); 241 } 242 243 return expansionR5; 244 } 245 246 @Override 247 public CodeValidationResult validateCodeInValueSet( 248 ValidationSupportContext theValidationSupportContext, 249 ConceptValidationOptions theOptions, 250 String theCodeSystemUrlAndVersion, 251 String theCode, 252 String theDisplay, 253 @Nonnull IBaseResource theValueSet) { 254 org.hl7.fhir.r5.model.ValueSet expansion; 255 String vsUrl = CommonCodeSystemsTerminologyService.getValueSetUrl(getFhirContext(), theValueSet); 256 try { 257 expansion = expandValueSetToCanonical( 258 theValidationSupportContext, theValueSet, theCodeSystemUrlAndVersion, theCode); 259 } catch (ExpansionCouldNotBeCompletedInternallyException e) { 260 CodeValidationResult codeValidationResult = new CodeValidationResult(); 261 codeValidationResult.setSeverityCode("error"); 262 263 String msg = "Failed to expand ValueSet '" + vsUrl + "' (in-memory). Could not validate code " 264 + theCodeSystemUrlAndVersion + "#" + theCode; 265 if (e.getMessage() != null) { 266 msg += ". Error was: " + e.getMessage(); 267 } 268 269 codeValidationResult.setMessage(msg); 270 return codeValidationResult; 271 } 272 273 if (expansion == null) { 274 return null; 275 } 276 277 return validateCodeInExpandedValueSet( 278 theValidationSupportContext, 279 theOptions, 280 theCodeSystemUrlAndVersion, 281 theCode, 282 theDisplay, 283 expansion, 284 vsUrl); 285 } 286 287 @Override 288 @Nullable 289 public CodeValidationResult validateCode( 290 @Nonnull ValidationSupportContext theValidationSupportContext, 291 @Nonnull ConceptValidationOptions theOptions, 292 String theCodeSystem, 293 String theCode, 294 String theDisplay, 295 String theValueSetUrl) { 296 IBaseResource vs; 297 if (isNotBlank(theValueSetUrl)) { 298 vs = theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrl); 299 if (vs == null) { 300 return null; 301 } 302 } else { 303 String codeSystemUrl; 304 String codeSystemVersion = null; 305 int codeSystemVersionIndex = theCodeSystem.indexOf("|"); 306 if (codeSystemVersionIndex > -1) { 307 codeSystemUrl = theCodeSystem.substring(0, codeSystemVersionIndex); 308 codeSystemVersion = theCodeSystem.substring(codeSystemVersionIndex + 1); 309 } else { 310 codeSystemUrl = theCodeSystem; 311 } 312 switch (myCtx.getVersion().getVersion()) { 313 case DSTU2: 314 case DSTU2_HL7ORG: 315 vs = new org.hl7.fhir.dstu2.model.ValueSet() 316 .setCompose(new org.hl7.fhir.dstu2.model.ValueSet.ValueSetComposeComponent() 317 .addInclude(new org.hl7.fhir.dstu2.model.ValueSet.ConceptSetComponent() 318 .setSystem(theCodeSystem))); 319 break; 320 case DSTU3: 321 if (codeSystemVersion != null) { 322 vs = new org.hl7.fhir.dstu3.model.ValueSet() 323 .setCompose(new org.hl7.fhir.dstu3.model.ValueSet.ValueSetComposeComponent() 324 .addInclude(new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent() 325 .setSystem(codeSystemUrl) 326 .setVersion(codeSystemVersion))); 327 } else { 328 vs = new org.hl7.fhir.dstu3.model.ValueSet() 329 .setCompose(new org.hl7.fhir.dstu3.model.ValueSet.ValueSetComposeComponent() 330 .addInclude(new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent() 331 .setSystem(theCodeSystem))); 332 } 333 break; 334 case R4: 335 if (codeSystemVersion != null) { 336 vs = new org.hl7.fhir.r4.model.ValueSet() 337 .setCompose(new org.hl7.fhir.r4.model.ValueSet.ValueSetComposeComponent() 338 .addInclude(new org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent() 339 .setSystem(codeSystemUrl) 340 .setVersion(codeSystemVersion))); 341 } else { 342 vs = new org.hl7.fhir.r4.model.ValueSet() 343 .setCompose(new org.hl7.fhir.r4.model.ValueSet.ValueSetComposeComponent() 344 .addInclude(new org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent() 345 .setSystem(theCodeSystem))); 346 } 347 break; 348 case R4B: 349 if (codeSystemVersion != null) { 350 vs = new org.hl7.fhir.r4b.model.ValueSet() 351 .setCompose(new org.hl7.fhir.r4b.model.ValueSet.ValueSetComposeComponent() 352 .addInclude(new org.hl7.fhir.r4b.model.ValueSet.ConceptSetComponent() 353 .setSystem(codeSystemUrl) 354 .setVersion(codeSystemVersion))); 355 } else { 356 vs = new org.hl7.fhir.r4b.model.ValueSet() 357 .setCompose(new org.hl7.fhir.r4b.model.ValueSet.ValueSetComposeComponent() 358 .addInclude(new org.hl7.fhir.r4b.model.ValueSet.ConceptSetComponent() 359 .setSystem(theCodeSystem))); 360 } 361 break; 362 case R5: 363 if (codeSystemVersion != null) { 364 vs = new org.hl7.fhir.r5.model.ValueSet() 365 .setCompose(new org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent() 366 .addInclude(new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent() 367 .setSystem(codeSystemUrl) 368 .setVersion(codeSystemVersion))); 369 } else { 370 vs = new org.hl7.fhir.r5.model.ValueSet() 371 .setCompose(new org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent() 372 .addInclude(new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent() 373 .setSystem(theCodeSystem))); 374 } 375 break; 376 case DSTU2_1: 377 default: 378 throw new IllegalArgumentException(Msg.code(699) + "Can not handle version: " 379 + myCtx.getVersion().getVersion()); 380 } 381 } 382 383 ValueSetExpansionOutcome valueSetExpansionOutcome = 384 expandValueSet(theValidationSupportContext, vs, theCodeSystem, theCode); 385 if (valueSetExpansionOutcome == null) { 386 return null; 387 } 388 389 if (valueSetExpansionOutcome.getError() != null) { 390 return new CodeValidationResult() 391 .setSeverity(IssueSeverity.ERROR) 392 .setMessage(valueSetExpansionOutcome.getError()); 393 } 394 395 IBaseResource expansion = valueSetExpansionOutcome.getValueSet(); 396 return validateCodeInExpandedValueSet( 397 theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, expansion, theValueSetUrl); 398 } 399 400 private CodeValidationResult validateCodeInExpandedValueSet( 401 ValidationSupportContext theValidationSupportContext, 402 ConceptValidationOptions theOptions, 403 String theCodeSystemUrlAndVersionToValidate, 404 String theCodeToValidate, 405 String theDisplayToValidate, 406 IBaseResource theExpansion, 407 String theValueSetUrl) { 408 assert theExpansion != null; 409 410 boolean caseSensitive = true; 411 IBaseResource codeSystemToValidateResource = null; 412 if (!theOptions.isInferSystem() && isNotBlank(theCodeSystemUrlAndVersionToValidate)) { 413 codeSystemToValidateResource = theValidationSupportContext 414 .getRootValidationSupport() 415 .fetchCodeSystem(theCodeSystemUrlAndVersionToValidate); 416 } 417 418 List<FhirVersionIndependentConcept> codes = new ArrayList<>(); 419 switch (getFhirVersionEnum( 420 theValidationSupportContext.getRootValidationSupport().getFhirContext(), theExpansion)) { 421 case DSTU2: { 422 ca.uhn.fhir.model.dstu2.resource.ValueSet expansionVs = 423 (ca.uhn.fhir.model.dstu2.resource.ValueSet) theExpansion; 424 List<ca.uhn.fhir.model.dstu2.resource.ValueSet.ExpansionContains> contains = 425 expansionVs.getExpansion().getContains(); 426 flattenAndConvertCodesDstu2(contains, codes); 427 break; 428 } 429 case DSTU2_HL7ORG: { 430 ValueSet expansionVs = (ValueSet) theExpansion; 431 List<ValueSet.ValueSetExpansionContainsComponent> contains = 432 expansionVs.getExpansion().getContains(); 433 flattenAndConvertCodesDstu2Hl7Org(contains, codes); 434 break; 435 } 436 case DSTU3: { 437 org.hl7.fhir.dstu3.model.ValueSet expansionVs = (org.hl7.fhir.dstu3.model.ValueSet) theExpansion; 438 List<org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent> contains = 439 expansionVs.getExpansion().getContains(); 440 flattenAndConvertCodesDstu3(contains, codes); 441 break; 442 } 443 case R4: { 444 org.hl7.fhir.r4.model.ValueSet expansionVs = (org.hl7.fhir.r4.model.ValueSet) theExpansion; 445 List<org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent> contains = 446 expansionVs.getExpansion().getContains(); 447 flattenAndConvertCodesR4(contains, codes); 448 break; 449 } 450 case R4B: { 451 org.hl7.fhir.r4b.model.ValueSet expansionVs = (org.hl7.fhir.r4b.model.ValueSet) theExpansion; 452 List<org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionContainsComponent> contains = 453 expansionVs.getExpansion().getContains(); 454 flattenAndConvertCodesR4B(contains, codes); 455 break; 456 } 457 case R5: { 458 org.hl7.fhir.r5.model.ValueSet expansionVs = (org.hl7.fhir.r5.model.ValueSet) theExpansion; 459 List<org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent> contains = 460 expansionVs.getExpansion().getContains(); 461 flattenAndConvertCodesR5(contains, codes); 462 break; 463 } 464 case DSTU2_1: 465 default: 466 throw new IllegalArgumentException(Msg.code(700) + "Can not handle version: " 467 + myCtx.getVersion().getVersion()); 468 } 469 470 String codeSystemResourceName = null; 471 String codeSystemResourceVersion = null; 472 String codeSystemResourceContentMode = null; 473 if (codeSystemToValidateResource != null) { 474 switch (getFhirVersionEnum( 475 theValidationSupportContext.getRootValidationSupport().getFhirContext(), 476 codeSystemToValidateResource)) { 477 case DSTU2: 478 case DSTU2_HL7ORG: { 479 caseSensitive = true; 480 break; 481 } 482 case DSTU3: { 483 org.hl7.fhir.dstu3.model.CodeSystem systemDstu3 = 484 (org.hl7.fhir.dstu3.model.CodeSystem) codeSystemToValidateResource; 485 caseSensitive = systemDstu3.getCaseSensitive(); 486 codeSystemResourceName = systemDstu3.getName(); 487 codeSystemResourceVersion = systemDstu3.getVersion(); 488 codeSystemResourceContentMode = 489 systemDstu3.getContentElement().getValueAsString(); 490 break; 491 } 492 case R4: { 493 org.hl7.fhir.r4.model.CodeSystem systemR4 = 494 (org.hl7.fhir.r4.model.CodeSystem) codeSystemToValidateResource; 495 caseSensitive = systemR4.getCaseSensitive(); 496 codeSystemResourceName = systemR4.getName(); 497 codeSystemResourceVersion = systemR4.getVersion(); 498 codeSystemResourceContentMode = systemR4.getContentElement().getValueAsString(); 499 break; 500 } 501 case R4B: { 502 org.hl7.fhir.r4b.model.CodeSystem systemR4B = 503 (org.hl7.fhir.r4b.model.CodeSystem) codeSystemToValidateResource; 504 caseSensitive = systemR4B.getCaseSensitive(); 505 codeSystemResourceName = systemR4B.getName(); 506 codeSystemResourceVersion = systemR4B.getVersion(); 507 codeSystemResourceContentMode = 508 systemR4B.getContentElement().getValueAsString(); 509 break; 510 } 511 case R5: { 512 CodeSystem systemR5 = (CodeSystem) codeSystemToValidateResource; 513 caseSensitive = systemR5.getCaseSensitive(); 514 codeSystemResourceName = systemR5.getName(); 515 codeSystemResourceVersion = systemR5.getVersion(); 516 codeSystemResourceContentMode = systemR5.getContentElement().getValueAsString(); 517 break; 518 } 519 case DSTU2_1: 520 default: 521 throw new IllegalArgumentException(Msg.code(701) + "Can not handle version: " 522 + myCtx.getVersion().getVersion()); 523 } 524 } 525 526 String codeSystemUrlToValidate = null; 527 String codeSystemVersionToValidate = null; 528 if (theCodeSystemUrlAndVersionToValidate != null) { 529 int versionIndex = theCodeSystemUrlAndVersionToValidate.indexOf("|"); 530 if (versionIndex > -1) { 531 codeSystemUrlToValidate = theCodeSystemUrlAndVersionToValidate.substring(0, versionIndex); 532 codeSystemVersionToValidate = theCodeSystemUrlAndVersionToValidate.substring(versionIndex + 1); 533 } else { 534 codeSystemUrlToValidate = theCodeSystemUrlAndVersionToValidate; 535 } 536 } 537 for (FhirVersionIndependentConcept nextExpansionCode : codes) { 538 539 boolean codeMatches; 540 if (caseSensitive) { 541 codeMatches = defaultString(theCodeToValidate).equals(nextExpansionCode.getCode()); 542 } else { 543 codeMatches = defaultString(theCodeToValidate).equalsIgnoreCase(nextExpansionCode.getCode()); 544 } 545 if (codeMatches) { 546 if (theOptions.isInferSystem() 547 || (nextExpansionCode.getSystem().equals(codeSystemUrlToValidate) 548 && (codeSystemVersionToValidate == null 549 || codeSystemVersionToValidate.equals(nextExpansionCode.getSystemVersion())))) { 550 String csVersion = codeSystemResourceVersion; 551 if (isNotBlank(nextExpansionCode.getSystemVersion())) { 552 csVersion = nextExpansionCode.getSystemVersion(); 553 } 554 if (!theOptions.isValidateDisplay() 555 || (isBlank(nextExpansionCode.getDisplay()) 556 || isBlank(theDisplayToValidate) 557 || nextExpansionCode.getDisplay().equals(theDisplayToValidate))) { 558 CodeValidationResult codeValidationResult = new CodeValidationResult() 559 .setCode(theCodeToValidate) 560 .setDisplay(nextExpansionCode.getDisplay()) 561 .setCodeSystemName(codeSystemResourceName) 562 .setCodeSystemVersion(csVersion); 563 if (isNotBlank(theValueSetUrl)) { 564 populateSourceDetailsForInMemoryExpansion(theValueSetUrl, codeValidationResult); 565 } 566 return codeValidationResult; 567 } else { 568 String messageAppend = ""; 569 if (isNotBlank(theValueSetUrl)) { 570 messageAppend = " for in-memory expansion of ValueSet: " + theValueSetUrl; 571 } 572 CodeValidationResult codeValidationResult = createResultForDisplayMismatch( 573 myCtx, 574 theCodeToValidate, 575 theDisplayToValidate, 576 nextExpansionCode.getDisplay(), 577 csVersion, 578 messageAppend, 579 getIssueSeverityForCodeDisplayMismatch()); 580 if (isNotBlank(theValueSetUrl)) { 581 populateSourceDetailsForInMemoryExpansion(theValueSetUrl, codeValidationResult); 582 } 583 return codeValidationResult; 584 } 585 } 586 } 587 } 588 589 ValidationMessage.IssueSeverity severity; 590 String message; 591 if ("fragment".equals(codeSystemResourceContentMode)) { 592 severity = ValidationMessage.IssueSeverity.WARNING; 593 message = "Unknown code in fragment CodeSystem '" 594 + (isNotBlank(theCodeSystemUrlAndVersionToValidate) 595 ? theCodeSystemUrlAndVersionToValidate + "#" 596 : "") 597 + theCodeToValidate + "'"; 598 } else { 599 severity = ValidationMessage.IssueSeverity.ERROR; 600 message = "Unknown code '" 601 + (isNotBlank(theCodeSystemUrlAndVersionToValidate) 602 ? theCodeSystemUrlAndVersionToValidate + "#" 603 : "") 604 + theCodeToValidate + "'"; 605 } 606 if (isNotBlank(theValueSetUrl)) { 607 message += " for in-memory expansion of ValueSet '" + theValueSetUrl + "'"; 608 } 609 610 return new CodeValidationResult().setSeverityCode(severity.toCode()).setMessage(message); 611 } 612 613 @Override 614 public LookupCodeResult lookupCode( 615 ValidationSupportContext theValidationSupportContext, @Nonnull LookupCodeRequest theLookupCodeRequest) { 616 final String code = theLookupCodeRequest.getCode(); 617 final String system = theLookupCodeRequest.getSystem(); 618 CodeValidationResult codeValidationResult = validateCode( 619 theValidationSupportContext, 620 new ConceptValidationOptions(), 621 system, 622 code, 623 theLookupCodeRequest.getDisplayLanguage(), 624 null); 625 if (codeValidationResult == null) { 626 return null; 627 } 628 return codeValidationResult.asLookupCodeResult(system, code); 629 } 630 631 @Nullable 632 private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2Hl7Org( 633 ValidationSupportContext theValidationSupportContext, 634 ValueSet theInput, 635 @Nullable String theWantSystemUrlAndVersion, 636 @Nullable String theWantCode) 637 throws ExpansionCouldNotBeCompletedInternallyException { 638 org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) 639 VersionConvertorFactory_10_50.convertResource(theInput, new BaseAdvisor_10_50(false)); 640 return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode)); 641 } 642 643 @Nullable 644 private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2( 645 ValidationSupportContext theValidationSupportContext, 646 ca.uhn.fhir.model.dstu2.resource.ValueSet theInput, 647 @Nullable String theWantSystemUrlAndVersion, 648 @Nullable String theWantCode) 649 throws ExpansionCouldNotBeCompletedInternallyException { 650 IParser parserRi = FhirContext.forCached(FhirVersionEnum.DSTU2_HL7ORG).newJsonParser(); 651 IParser parserHapi = FhirContext.forDstu2Cached().newJsonParser(); 652 653 org.hl7.fhir.dstu2.model.ValueSet valueSetRi = parserRi.parseResource( 654 org.hl7.fhir.dstu2.model.ValueSet.class, parserHapi.encodeResourceToString(theInput)); 655 org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) 656 VersionConvertorFactory_10_50.convertResource(valueSetRi, new BaseAdvisor_10_50(false)); 657 return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode)); 658 } 659 660 @Override 661 public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) { 662 if (isBlank(theSystem)) { 663 return false; 664 } 665 666 IBaseResource cs = 667 theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(theSystem); 668 669 if (!myCtx.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU2_1)) { 670 return cs != null; 671 } 672 673 if (cs != null) { 674 IPrimitiveType<?> content = 675 getFhirContext().newTerser().getSingleValueOrNull(cs, "content", IPrimitiveType.class); 676 return !"not-present".equals(content.getValueAsString()); 677 } 678 679 return false; 680 } 681 682 @Override 683 public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) { 684 return isNotBlank(theValueSetUrl) 685 && theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrl) != null; 686 } 687 688 private void addCodesDstu2Hl7Org( 689 List<ValueSet.ConceptDefinitionComponent> theSourceList, 690 List<CodeSystem.ConceptDefinitionComponent> theTargetList) { 691 for (ValueSet.ConceptDefinitionComponent nextSource : theSourceList) { 692 CodeSystem.ConceptDefinitionComponent targetConcept = new CodeSystem.ConceptDefinitionComponent() 693 .setCode(nextSource.getCode()) 694 .setDisplay(nextSource.getDisplay()); 695 theTargetList.add(targetConcept); 696 addCodesDstu2Hl7Org(nextSource.getConcept(), targetConcept.getConcept()); 697 } 698 } 699 700 private void addCodesDstu2( 701 List<ca.uhn.fhir.model.dstu2.resource.ValueSet.CodeSystemConcept> theSourceList, 702 List<CodeSystem.ConceptDefinitionComponent> theTargetList) { 703 for (ca.uhn.fhir.model.dstu2.resource.ValueSet.CodeSystemConcept nextSource : theSourceList) { 704 CodeSystem.ConceptDefinitionComponent targetConcept = new CodeSystem.ConceptDefinitionComponent() 705 .setCode(nextSource.getCode()) 706 .setDisplay(nextSource.getDisplay()); 707 theTargetList.add(targetConcept); 708 addCodesDstu2(nextSource.getConcept(), targetConcept.getConcept()); 709 } 710 } 711 712 @Nullable 713 private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu3( 714 ValidationSupportContext theValidationSupportContext, 715 org.hl7.fhir.dstu3.model.ValueSet theInput, 716 @Nullable String theWantSystemUrlAndVersion, 717 @Nullable String theWantCode) 718 throws ExpansionCouldNotBeCompletedInternallyException { 719 org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) 720 VersionConvertorFactory_30_50.convertResource(theInput, new BaseAdvisor_30_50(false)); 721 return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode)); 722 } 723 724 @Nullable 725 private org.hl7.fhir.r5.model.ValueSet expandValueSetR4( 726 ValidationSupportContext theValidationSupportContext, 727 org.hl7.fhir.r4.model.ValueSet theInput, 728 @Nullable String theWantSystemUrlAndVersion, 729 @Nullable String theWantCode) 730 throws ExpansionCouldNotBeCompletedInternallyException { 731 org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) 732 VersionConvertorFactory_40_50.convertResource(theInput, new BaseAdvisor_40_50(false)); 733 return expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode); 734 } 735 736 @Nullable 737 private org.hl7.fhir.r5.model.ValueSet expandValueSetR4B( 738 ValidationSupportContext theValidationSupportContext, 739 org.hl7.fhir.r4b.model.ValueSet theInput, 740 @Nullable String theWantSystemUrlAndVersion, 741 @Nullable String theWantCode) 742 throws ExpansionCouldNotBeCompletedInternallyException { 743 org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) 744 VersionConvertorFactory_43_50.convertResource(theInput, new BaseAdvisor_43_50(false)); 745 return expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode); 746 } 747 748 @Nullable 749 private org.hl7.fhir.r5.model.ValueSet expandValueSetR5( 750 ValidationSupportContext theValidationSupportContext, 751 org.hl7.fhir.r5.model.ValueSet theInput, 752 @Nullable String theWantSystemUrlAndVersion, 753 @Nullable String theWantCode) 754 throws ExpansionCouldNotBeCompletedInternallyException { 755 Set<FhirVersionIndependentConcept> concepts = new HashSet<>(); 756 757 expandValueSetR5IncludeOrExcludes( 758 theValidationSupportContext, 759 concepts, 760 theInput.getCompose().getInclude(), 761 true, 762 theWantSystemUrlAndVersion, 763 theWantCode); 764 expandValueSetR5IncludeOrExcludes( 765 theValidationSupportContext, 766 concepts, 767 theInput.getCompose().getExclude(), 768 false, 769 theWantSystemUrlAndVersion, 770 theWantCode); 771 772 org.hl7.fhir.r5.model.ValueSet retVal = new org.hl7.fhir.r5.model.ValueSet(); 773 for (FhirVersionIndependentConcept next : concepts) { 774 org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent contains = 775 retVal.getExpansion().addContains(); 776 contains.setSystem(next.getSystem()); 777 contains.setCode(next.getCode()); 778 contains.setDisplay(next.getDisplay()); 779 contains.setVersion(next.getSystemVersion()); 780 } 781 782 return retVal; 783 } 784 785 /** 786 * Use with caution - this is not a stable API 787 * 788 * @since 5.6.0 789 */ 790 public void expandValueSetIncludeOrExclude( 791 ValidationSupportContext theValidationSupportContext, 792 Consumer<FhirVersionIndependentConcept> theConsumer, 793 org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent theIncludeOrExclude) 794 throws ExpansionCouldNotBeCompletedInternallyException { 795 expandValueSetR5IncludeOrExclude(theValidationSupportContext, theConsumer, null, null, theIncludeOrExclude); 796 } 797 798 private void expandValueSetR5IncludeOrExcludes( 799 ValidationSupportContext theValidationSupportContext, 800 Set<FhirVersionIndependentConcept> theConcepts, 801 List<org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent> theComposeList, 802 boolean theComposeListIsInclude, 803 @Nullable String theWantSystemUrlAndVersion, 804 @Nullable String theWantCode) 805 throws ExpansionCouldNotBeCompletedInternallyException { 806 Consumer<FhirVersionIndependentConcept> consumer = c -> { 807 if (theComposeListIsInclude) { 808 theConcepts.add(c); 809 } else { 810 theConcepts.remove(c); 811 } 812 }; 813 expandValueSetR5IncludeOrExcludes( 814 theValidationSupportContext, consumer, theComposeList, theWantSystemUrlAndVersion, theWantCode); 815 } 816 817 private void expandValueSetR5IncludeOrExcludes( 818 ValidationSupportContext theValidationSupportContext, 819 Consumer<FhirVersionIndependentConcept> theConsumer, 820 List<org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent> theComposeList, 821 @Nullable String theWantSystemUrlAndVersion, 822 @Nullable String theWantCode) 823 throws ExpansionCouldNotBeCompletedInternallyException { 824 ExpansionCouldNotBeCompletedInternallyException caughtException = null; 825 for (org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent nextInclude : theComposeList) { 826 try { 827 boolean outcome = expandValueSetR5IncludeOrExclude( 828 theValidationSupportContext, theConsumer, theWantSystemUrlAndVersion, theWantCode, nextInclude); 829 if (isNotBlank(theWantCode)) { 830 if (outcome) { 831 return; 832 } 833 } 834 } catch (ExpansionCouldNotBeCompletedInternallyException e) { 835 if (isBlank(theWantCode)) { 836 throw e; 837 } else { 838 caughtException = e; 839 } 840 } 841 } 842 if (caughtException != null) { 843 throw caughtException; 844 } 845 } 846 847 /** 848 * Returns <code>true</code> if at least one code was added 849 */ 850 private boolean expandValueSetR5IncludeOrExclude( 851 ValidationSupportContext theValidationSupportContext, 852 Consumer<FhirVersionIndependentConcept> theConsumer, 853 @Nullable String theWantSystemUrlAndVersion, 854 @Nullable String theWantCode, 855 org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent theInclude) 856 throws ExpansionCouldNotBeCompletedInternallyException { 857 858 String wantSystemUrl = null; 859 String wantSystemVersion = null; 860 861 if (theWantSystemUrlAndVersion != null) { 862 int versionIndex = theWantSystemUrlAndVersion.indexOf(OUR_PIPE_CHARACTER); 863 if (versionIndex > -1) { 864 wantSystemUrl = theWantSystemUrlAndVersion.substring(0, versionIndex); 865 wantSystemVersion = theWantSystemUrlAndVersion.substring(versionIndex + 1); 866 } else { 867 wantSystemUrl = theWantSystemUrlAndVersion; 868 } 869 } 870 871 String includeOrExcludeConceptSystemUrl = theInclude.getSystem(); 872 String includeOrExcludeConceptSystemVersion = theInclude.getVersion(); 873 874 Function<String, CodeSystem> codeSystemLoader = newCodeSystemLoader(theValidationSupportContext); 875 Function<String, org.hl7.fhir.r5.model.ValueSet> valueSetLoader = 876 newValueSetLoader(theValidationSupportContext); 877 878 List<FhirVersionIndependentConcept> nextCodeList = new ArrayList<>(); 879 CodeSystem includeOrExcludeSystemResource = null; 880 881 if (isNotBlank(includeOrExcludeConceptSystemUrl)) { 882 883 includeOrExcludeConceptSystemVersion = optionallyPopulateVersionFromUrl( 884 includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion); 885 includeOrExcludeConceptSystemUrl = substringBefore(includeOrExcludeConceptSystemUrl, OUR_PIPE_CHARACTER); 886 887 if (wantSystemUrl != null && !wantSystemUrl.equals(includeOrExcludeConceptSystemUrl)) { 888 return false; 889 } 890 891 if (wantSystemVersion != null && !wantSystemVersion.equals(includeOrExcludeConceptSystemVersion)) { 892 return false; 893 } 894 895 String loadedCodeSystemUrl; 896 if (includeOrExcludeConceptSystemVersion != null) { 897 loadedCodeSystemUrl = 898 includeOrExcludeConceptSystemUrl + OUR_PIPE_CHARACTER + includeOrExcludeConceptSystemVersion; 899 } else { 900 loadedCodeSystemUrl = includeOrExcludeConceptSystemUrl; 901 } 902 903 includeOrExcludeSystemResource = codeSystemLoader.apply(loadedCodeSystemUrl); 904 905 boolean isIncludeWithDeclaredConcepts = !theInclude.getConcept().isEmpty(); 906 907 final Set<String> wantCodes; 908 if (isIncludeWithDeclaredConcepts) { 909 wantCodes = theInclude.getConcept().stream() 910 .map(org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent::getCode) 911 .collect(Collectors.toSet()); 912 } else { 913 wantCodes = null; 914 } 915 916 boolean ableToHandleCode = false; 917 String failureMessage = null; 918 FailureType failureType = FailureType.OTHER; 919 920 boolean isIncludeCodeSystemIgnored = includeOrExcludeSystemResource != null 921 && includeOrExcludeSystemResource.getContent() == Enumerations.CodeSystemContentMode.NOTPRESENT; 922 923 if (includeOrExcludeSystemResource == null || isIncludeCodeSystemIgnored) { 924 925 if (theWantCode != null) { 926 if (theValidationSupportContext 927 .getRootValidationSupport() 928 .isCodeSystemSupported(theValidationSupportContext, includeOrExcludeConceptSystemUrl)) { 929 LookupCodeResult lookup = theValidationSupportContext 930 .getRootValidationSupport() 931 .lookupCode( 932 theValidationSupportContext, 933 new LookupCodeRequest(includeOrExcludeConceptSystemUrl, theWantCode)); 934 if (lookup != null) { 935 ableToHandleCode = true; 936 if (lookup.isFound()) { 937 CodeSystem.ConceptDefinitionComponent conceptDefinition = 938 new CodeSystem.ConceptDefinitionComponent() 939 .addConcept() 940 .setCode(theWantCode) 941 .setDisplay(lookup.getCodeDisplay()); 942 List<CodeSystem.ConceptDefinitionComponent> codesList = 943 Collections.singletonList(conceptDefinition); 944 addCodes( 945 includeOrExcludeConceptSystemUrl, 946 includeOrExcludeConceptSystemVersion, 947 codesList, 948 nextCodeList, 949 wantCodes); 950 } 951 } 952 } else { 953 954 /* 955 * If we're doing an expansion specifically looking for a single code, that means we're validating that code. 956 * In the case where we have a ValueSet that explicitly enumerates a collection of codes 957 * (via ValueSet.compose.include.code) in a code system that is unknown we'll assume the code is valid 958 * even if we can't find the CodeSystem. This is a compromise obviously, since it would be ideal for 959 * CodeSystems to always be known, but realistically there are always going to be CodeSystems that 960 * can't be supplied because of copyright issues, or because they are grammar based. Allowing a VS to 961 * enumerate a set of good codes for them is a nice compromise there. 962 */ 963 if (Objects.equals(theInclude.getSystem(), theWantSystemUrlAndVersion)) { 964 Optional<org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent> 965 matchingEnumeratedConcept = theInclude.getConcept().stream() 966 .filter(t -> Objects.equals(t.getCode(), theWantCode)) 967 .findFirst(); 968 969 // If the ValueSet.compose.include has no individual concepts in it, and 970 // we can't find the actual referenced CodeSystem, we have no choice 971 // but to fail 972 if (isIncludeWithDeclaredConcepts) { 973 ableToHandleCode = true; 974 } else { 975 failureMessage = getFailureMessageForMissingOrUnusableCodeSystem( 976 includeOrExcludeSystemResource, loadedCodeSystemUrl); 977 } 978 979 if (matchingEnumeratedConcept.isPresent()) { 980 CodeSystem.ConceptDefinitionComponent conceptDefinition = 981 new CodeSystem.ConceptDefinitionComponent() 982 .addConcept() 983 .setCode(theWantCode) 984 .setDisplay(matchingEnumeratedConcept 985 .get() 986 .getDisplay()); 987 List<CodeSystem.ConceptDefinitionComponent> codesList = 988 Collections.singletonList(conceptDefinition); 989 addCodes( 990 includeOrExcludeConceptSystemUrl, 991 includeOrExcludeConceptSystemVersion, 992 codesList, 993 nextCodeList, 994 wantCodes); 995 } 996 } 997 } 998 } else { 999 boolean isIncludeFromSystem = isNotBlank(theInclude.getSystem()) 1000 && theInclude.getValueSet().isEmpty(); 1001 boolean isIncludeWithFilter = !theInclude.getFilter().isEmpty(); 1002 if (isIncludeFromSystem && !isIncludeWithFilter) { 1003 if (isIncludeWithDeclaredConcepts) { 1004 theInclude.getConcept().stream() 1005 .map(t -> new FhirVersionIndependentConcept( 1006 theInclude.getSystem(), 1007 t.getCode(), 1008 t.getDisplay(), 1009 theInclude.getVersion())) 1010 .forEach(nextCodeList::add); 1011 ableToHandleCode = true; 1012 } else if (isIncludeCodeSystemIgnored) { 1013 ableToHandleCode = true; 1014 } 1015 } 1016 1017 if (!ableToHandleCode) { 1018 failureMessage = getFailureMessageForMissingOrUnusableCodeSystem( 1019 includeOrExcludeSystemResource, loadedCodeSystemUrl); 1020 } 1021 } 1022 1023 } else { 1024 ableToHandleCode = true; 1025 } 1026 1027 if (!ableToHandleCode) { 1028 if (includeOrExcludeSystemResource == null && failureMessage == null) { 1029 failureMessage = getFailureMessageForMissingOrUnusableCodeSystem( 1030 includeOrExcludeSystemResource, loadedCodeSystemUrl); 1031 } 1032 1033 if (includeOrExcludeSystemResource == null) { 1034 failureType = FailureType.UNKNOWN_CODE_SYSTEM; 1035 } 1036 1037 throw new ExpansionCouldNotBeCompletedInternallyException(Msg.code(702) + failureMessage, failureType); 1038 } 1039 1040 if (includeOrExcludeSystemResource != null 1041 && includeOrExcludeSystemResource.getContent() != Enumerations.CodeSystemContentMode.NOTPRESENT) { 1042 addCodes( 1043 includeOrExcludeConceptSystemUrl, 1044 includeOrExcludeConceptSystemVersion, 1045 includeOrExcludeSystemResource.getConcept(), 1046 nextCodeList, 1047 wantCodes); 1048 } 1049 } 1050 1051 for (CanonicalType nextValueSetInclude : theInclude.getValueSet()) { 1052 org.hl7.fhir.r5.model.ValueSet vs = valueSetLoader.apply(nextValueSetInclude.getValueAsString()); 1053 if (vs != null) { 1054 org.hl7.fhir.r5.model.ValueSet subExpansion = 1055 expandValueSetR5(theValidationSupportContext, vs, theWantSystemUrlAndVersion, theWantCode); 1056 if (subExpansion == null) { 1057 throw new ExpansionCouldNotBeCompletedInternallyException( 1058 Msg.code(703) + "Failed to expand ValueSet: " + nextValueSetInclude.getValueAsString(), 1059 FailureType.OTHER); 1060 } 1061 for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : 1062 subExpansion.getExpansion().getContains()) { 1063 nextCodeList.add(new FhirVersionIndependentConcept( 1064 next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); 1065 } 1066 } 1067 } 1068 1069 boolean retVal = false; 1070 1071 for (FhirVersionIndependentConcept next : nextCodeList) { 1072 if (includeOrExcludeSystemResource != null && theWantCode != null) { 1073 boolean matches; 1074 if (includeOrExcludeSystemResource.getCaseSensitive()) { 1075 matches = theWantCode.equals(next.getCode()); 1076 } else { 1077 matches = theWantCode.equalsIgnoreCase(next.getCode()); 1078 } 1079 if (!matches) { 1080 continue; 1081 } 1082 } 1083 1084 theConsumer.accept(next); 1085 retVal = true; 1086 } 1087 1088 return retVal; 1089 } 1090 1091 private Function<String, org.hl7.fhir.r5.model.ValueSet> newValueSetLoader( 1092 ValidationSupportContext theValidationSupportContext) { 1093 switch (myCtx.getVersion().getVersion()) { 1094 case DSTU2: 1095 case DSTU2_HL7ORG: 1096 return t -> { 1097 IBaseResource vs = theValidationSupportContext 1098 .getRootValidationSupport() 1099 .fetchValueSet(t); 1100 if (vs instanceof ca.uhn.fhir.model.dstu2.resource.ValueSet) { 1101 IParser parserRi = FhirContext.forCached(FhirVersionEnum.DSTU2_HL7ORG) 1102 .newJsonParser(); 1103 IParser parserHapi = FhirContext.forDstu2Cached().newJsonParser(); 1104 ca.uhn.fhir.model.dstu2.resource.ValueSet valueSet = 1105 (ca.uhn.fhir.model.dstu2.resource.ValueSet) vs; 1106 org.hl7.fhir.dstu2.model.ValueSet valueSetRi = parserRi.parseResource( 1107 org.hl7.fhir.dstu2.model.ValueSet.class, parserHapi.encodeResourceToString(valueSet)); 1108 return (org.hl7.fhir.r5.model.ValueSet) 1109 VersionConvertorFactory_10_50.convertResource(valueSetRi, new BaseAdvisor_10_50(false)); 1110 } else { 1111 org.hl7.fhir.dstu2.model.ValueSet valueSet = 1112 (org.hl7.fhir.dstu2.model.ValueSet) theValidationSupportContext 1113 .getRootValidationSupport() 1114 .fetchValueSet(t); 1115 return (org.hl7.fhir.r5.model.ValueSet) 1116 VersionConvertorFactory_10_50.convertResource(valueSet, new BaseAdvisor_10_50(false)); 1117 } 1118 }; 1119 case DSTU3: 1120 return t -> { 1121 org.hl7.fhir.dstu3.model.ValueSet valueSet = 1122 (org.hl7.fhir.dstu3.model.ValueSet) theValidationSupportContext 1123 .getRootValidationSupport() 1124 .fetchValueSet(t); 1125 return (org.hl7.fhir.r5.model.ValueSet) 1126 VersionConvertorFactory_30_50.convertResource(valueSet, new BaseAdvisor_30_50(false)); 1127 }; 1128 case R4: 1129 return t -> { 1130 org.hl7.fhir.r4.model.ValueSet valueSet = 1131 (org.hl7.fhir.r4.model.ValueSet) theValidationSupportContext 1132 .getRootValidationSupport() 1133 .fetchValueSet(t); 1134 return (org.hl7.fhir.r5.model.ValueSet) 1135 VersionConvertorFactory_40_50.convertResource(valueSet, new BaseAdvisor_40_50(false)); 1136 }; 1137 case R4B: 1138 return t -> { 1139 org.hl7.fhir.r4b.model.ValueSet valueSet = 1140 (org.hl7.fhir.r4b.model.ValueSet) theValidationSupportContext 1141 .getRootValidationSupport() 1142 .fetchValueSet(t); 1143 return (org.hl7.fhir.r5.model.ValueSet) 1144 VersionConvertorFactory_43_50.convertResource(valueSet, new BaseAdvisor_43_50(false)); 1145 }; 1146 default: 1147 case DSTU2_1: 1148 case R5: 1149 return t -> (org.hl7.fhir.r5.model.ValueSet) 1150 theValidationSupportContext.getRootValidationSupport().fetchValueSet(t); 1151 } 1152 } 1153 1154 private Function<String, CodeSystem> newCodeSystemLoader(ValidationSupportContext theValidationSupportContext) { 1155 switch (myCtx.getVersion().getVersion()) { 1156 case DSTU2: 1157 case DSTU2_HL7ORG: 1158 return t -> { 1159 IBaseResource codeSystem = theValidationSupportContext 1160 .getRootValidationSupport() 1161 .fetchCodeSystem(t); 1162 CodeSystem retVal = null; 1163 if (codeSystem != null) { 1164 retVal = new CodeSystem(); 1165 if (codeSystem instanceof ca.uhn.fhir.model.dstu2.resource.ValueSet) { 1166 ca.uhn.fhir.model.dstu2.resource.ValueSet codeSystemCasted = 1167 (ca.uhn.fhir.model.dstu2.resource.ValueSet) codeSystem; 1168 retVal.setUrl(codeSystemCasted.getUrl()); 1169 addCodesDstu2(codeSystemCasted.getCodeSystem().getConcept(), retVal.getConcept()); 1170 } else { 1171 org.hl7.fhir.dstu2.model.ValueSet codeSystemCasted = 1172 (org.hl7.fhir.dstu2.model.ValueSet) codeSystem; 1173 retVal.setUrl(codeSystemCasted.getUrl()); 1174 addCodesDstu2Hl7Org(codeSystemCasted.getCodeSystem().getConcept(), retVal.getConcept()); 1175 } 1176 } 1177 return retVal; 1178 }; 1179 case DSTU3: 1180 return t -> { 1181 org.hl7.fhir.dstu3.model.CodeSystem codeSystem = 1182 (org.hl7.fhir.dstu3.model.CodeSystem) theValidationSupportContext 1183 .getRootValidationSupport() 1184 .fetchCodeSystem(t); 1185 return (CodeSystem) 1186 VersionConvertorFactory_30_50.convertResource(codeSystem, new BaseAdvisor_30_50(false)); 1187 }; 1188 case R4: 1189 return t -> { 1190 org.hl7.fhir.r4.model.CodeSystem codeSystem = 1191 (org.hl7.fhir.r4.model.CodeSystem) theValidationSupportContext 1192 .getRootValidationSupport() 1193 .fetchCodeSystem(t); 1194 return (CodeSystem) 1195 VersionConvertorFactory_40_50.convertResource(codeSystem, new BaseAdvisor_40_50(false)); 1196 }; 1197 case R4B: 1198 return t -> { 1199 org.hl7.fhir.r4b.model.CodeSystem codeSystem = 1200 (org.hl7.fhir.r4b.model.CodeSystem) theValidationSupportContext 1201 .getRootValidationSupport() 1202 .fetchCodeSystem(t); 1203 return (CodeSystem) 1204 VersionConvertorFactory_43_50.convertResource(codeSystem, new BaseAdvisor_43_50(false)); 1205 }; 1206 case DSTU2_1: 1207 case R5: 1208 default: 1209 return t -> (org.hl7.fhir.r5.model.CodeSystem) 1210 theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); 1211 } 1212 } 1213 1214 private String getFailureMessageForMissingOrUnusableCodeSystem( 1215 CodeSystem includeOrExcludeSystemResource, String loadedCodeSystemUrl) { 1216 String failureMessage; 1217 if (includeOrExcludeSystemResource == null) { 1218 failureMessage = "Unable to expand ValueSet because CodeSystem could not be found: " + loadedCodeSystemUrl; 1219 } else { 1220 assert includeOrExcludeSystemResource.getContent() == Enumerations.CodeSystemContentMode.NOTPRESENT; 1221 failureMessage = 1222 "Unable to expand ValueSet because CodeSystem has CodeSystem.content=not-present but contents were not found: " 1223 + loadedCodeSystemUrl; 1224 } 1225 return failureMessage; 1226 } 1227 1228 private void addCodes( 1229 String theCodeSystemUrl, 1230 String theCodeSystemVersion, 1231 List<CodeSystem.ConceptDefinitionComponent> theSource, 1232 List<FhirVersionIndependentConcept> theTarget, 1233 Set<String> theCodeFilter) { 1234 for (CodeSystem.ConceptDefinitionComponent next : theSource) { 1235 if (isNotBlank(next.getCode())) { 1236 if (theCodeFilter == null || theCodeFilter.contains(next.getCode())) { 1237 theTarget.add(new FhirVersionIndependentConcept( 1238 theCodeSystemUrl, next.getCode(), next.getDisplay(), theCodeSystemVersion)); 1239 } 1240 } 1241 addCodes(theCodeSystemUrl, theCodeSystemVersion, next.getConcept(), theTarget, theCodeFilter); 1242 } 1243 } 1244 1245 private String optionallyPopulateVersionFromUrl(String theSystemUrl, String theVersion) { 1246 if (contains(theSystemUrl, OUR_PIPE_CHARACTER) && isBlank(theVersion)) { 1247 theVersion = substringAfter(theSystemUrl, OUR_PIPE_CHARACTER); 1248 } 1249 return theVersion; 1250 } 1251 1252 private static void populateSourceDetailsForInMemoryExpansion( 1253 String theValueSetUrl, CodeValidationResult codeValidationResult) { 1254 codeValidationResult.setSourceDetails( 1255 "Code was validated against in-memory expansion of ValueSet: " + theValueSetUrl); 1256 } 1257 1258 public static CodeValidationResult createResultForDisplayMismatch( 1259 FhirContext theFhirContext, 1260 String theCode, 1261 String theDisplay, 1262 String theExpectedDisplay, 1263 String theCodeSystemVersion, 1264 IssueSeverity theIssueSeverityForCodeDisplayMismatch) { 1265 return createResultForDisplayMismatch( 1266 theFhirContext, 1267 theCode, 1268 theDisplay, 1269 theExpectedDisplay, 1270 theCodeSystemVersion, 1271 "", 1272 theIssueSeverityForCodeDisplayMismatch); 1273 } 1274 1275 private static CodeValidationResult createResultForDisplayMismatch( 1276 FhirContext theFhirContext, 1277 String theCode, 1278 String theDisplay, 1279 String theExpectedDisplay, 1280 String theCodeSystemVersion, 1281 String theMessageAppend, 1282 IssueSeverity theIssueSeverityForCodeDisplayMismatch) { 1283 1284 String message; 1285 IssueSeverity issueSeverity = theIssueSeverityForCodeDisplayMismatch; 1286 if (issueSeverity == IssueSeverity.INFORMATION) { 1287 message = null; 1288 issueSeverity = null; 1289 } else { 1290 message = theFhirContext 1291 .getLocalizer() 1292 .getMessage( 1293 InMemoryTerminologyServerValidationSupport.class, 1294 "displayMismatch", 1295 theDisplay, 1296 theExpectedDisplay) 1297 + theMessageAppend; 1298 } 1299 return new CodeValidationResult() 1300 .setSeverity(issueSeverity) 1301 .setMessage(message) 1302 .setCode(theCode) 1303 .setCodeSystemVersion(theCodeSystemVersion) 1304 .setDisplay(theExpectedDisplay); 1305 } 1306 1307 private static void flattenAndConvertCodesDstu2( 1308 List<ca.uhn.fhir.model.dstu2.resource.ValueSet.ExpansionContains> theInput, 1309 List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) { 1310 for (ca.uhn.fhir.model.dstu2.resource.ValueSet.ExpansionContains next : theInput) { 1311 theFhirVersionIndependentConcepts.add( 1312 new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); 1313 flattenAndConvertCodesDstu2(next.getContains(), theFhirVersionIndependentConcepts); 1314 } 1315 } 1316 1317 private static void flattenAndConvertCodesDstu2Hl7Org( 1318 List<org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionContainsComponent> theInput, 1319 List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) { 1320 for (org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { 1321 theFhirVersionIndependentConcepts.add( 1322 new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); 1323 flattenAndConvertCodesDstu2Hl7Org(next.getContains(), theFhirVersionIndependentConcepts); 1324 } 1325 } 1326 1327 private static void flattenAndConvertCodesDstu3( 1328 List<org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent> theInput, 1329 List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) { 1330 for (org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { 1331 theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept( 1332 next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); 1333 flattenAndConvertCodesDstu3(next.getContains(), theFhirVersionIndependentConcepts); 1334 } 1335 } 1336 1337 private static void flattenAndConvertCodesR4( 1338 List<org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent> theInput, 1339 List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) { 1340 for (org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { 1341 theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept( 1342 next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); 1343 flattenAndConvertCodesR4(next.getContains(), theFhirVersionIndependentConcepts); 1344 } 1345 } 1346 1347 private static void flattenAndConvertCodesR4B( 1348 List<org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionContainsComponent> theInput, 1349 List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) { 1350 for (org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { 1351 theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept( 1352 next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); 1353 flattenAndConvertCodesR4B(next.getContains(), theFhirVersionIndependentConcepts); 1354 } 1355 } 1356 1357 private static void flattenAndConvertCodesR5( 1358 List<org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent> theInput, 1359 List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) { 1360 for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { 1361 theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept( 1362 next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); 1363 flattenAndConvertCodesR5(next.getContains(), theFhirVersionIndependentConcepts); 1364 } 1365 } 1366 1367 public enum FailureType { 1368 UNKNOWN_CODE_SYSTEM, 1369 OTHER 1370 } 1371 1372 public static class ExpansionCouldNotBeCompletedInternallyException extends Exception { 1373 1374 private static final long serialVersionUID = -2226561628771483085L; 1375 private final FailureType myFailureType; 1376 1377 public ExpansionCouldNotBeCompletedInternallyException(String theMessage, FailureType theFailureType) { 1378 super(theMessage); 1379 myFailureType = theFailureType; 1380 } 1381 1382 public FailureType getFailureType() { 1383 return myFailureType; 1384 } 1385 } 1386}