001package org.hl7.fhir.common.hapi.validation.validator; 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.DefaultProfileValidationSupport; 007import ca.uhn.fhir.context.support.IValidationSupport; 008import ca.uhn.fhir.context.support.ValidationSupportContext; 009import ca.uhn.fhir.i18n.Msg; 010import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; 011import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 012import ca.uhn.hapi.converters.canonical.VersionCanonicalizer; 013import jakarta.annotation.Nonnull; 014import jakarta.annotation.Nullable; 015import org.apache.commons.lang3.StringUtils; 016import org.apache.commons.lang3.Validate; 017import org.apache.commons.lang3.builder.EqualsBuilder; 018import org.apache.commons.lang3.builder.HashCodeBuilder; 019import org.apache.commons.lang3.exception.ExceptionUtils; 020import org.fhir.ucum.UcumService; 021import org.hl7.fhir.common.hapi.validation.support.SnapshotGeneratingValidationSupport; 022import org.hl7.fhir.common.hapi.validation.support.ValidationSupportUtils; 023import org.hl7.fhir.exceptions.FHIRException; 024import org.hl7.fhir.exceptions.TerminologyServiceException; 025import org.hl7.fhir.instance.model.api.IBaseResource; 026import org.hl7.fhir.r5.context.IContextResourceLoader; 027import org.hl7.fhir.r5.context.IWorkerContext; 028import org.hl7.fhir.r5.context.IWorkerContextManager; 029import org.hl7.fhir.r5.fhirpath.FHIRPathEngine; 030import org.hl7.fhir.r5.model.CodeSystem; 031import org.hl7.fhir.r5.model.CodeableConcept; 032import org.hl7.fhir.r5.model.Coding; 033import org.hl7.fhir.r5.model.ElementDefinition; 034import org.hl7.fhir.r5.model.NamingSystem; 035import org.hl7.fhir.r5.model.OperationOutcome; 036import org.hl7.fhir.r5.model.PackageInformation; 037import org.hl7.fhir.r5.model.Parameters; 038import org.hl7.fhir.r5.model.Resource; 039import org.hl7.fhir.r5.model.StringType; 040import org.hl7.fhir.r5.model.StructureDefinition; 041import org.hl7.fhir.r5.model.ValueSet; 042import org.hl7.fhir.r5.profilemodel.PEBuilder; 043import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome; 044import org.hl7.fhir.r5.terminologies.utilities.CodingValidationRequest; 045import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass; 046import org.hl7.fhir.r5.terminologies.utilities.ValidationResult; 047import org.hl7.fhir.r5.utils.validation.IResourceValidator; 048import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier; 049import org.hl7.fhir.utilities.FhirPublication; 050import org.hl7.fhir.utilities.TimeTracker; 051import org.hl7.fhir.utilities.i18n.I18nBase; 052import org.hl7.fhir.utilities.npm.BasePackageCacheManager; 053import org.hl7.fhir.utilities.npm.NpmPackage; 054import org.hl7.fhir.utilities.validation.ValidationMessage; 055import org.hl7.fhir.utilities.validation.ValidationOptions; 056import org.slf4j.Logger; 057import org.slf4j.LoggerFactory; 058 059import java.util.ArrayList; 060import java.util.Collections; 061import java.util.Date; 062import java.util.List; 063import java.util.Locale; 064import java.util.Map; 065import java.util.Objects; 066import java.util.Set; 067import java.util.stream.Collectors; 068 069import static ca.uhn.fhir.context.support.IValidationSupport.CodeValidationIssueCoding.INVALID_DISPLAY; 070import static java.util.stream.Collectors.collectingAndThen; 071import static java.util.stream.Collectors.toSet; 072import static org.apache.commons.lang3.StringUtils.isBlank; 073import static org.apache.commons.lang3.StringUtils.isNotBlank; 074 075public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWorkerContext { 076 private static final Logger ourLog = LoggerFactory.getLogger(VersionSpecificWorkerContextWrapper.class); 077 078 /** 079 * When we fetch conformance resources such as StructureDefinitions from {@link IValidationSupport} 080 * they will be returned using whatever version of FHIR the underlying infrastructure is 081 * configured to support. But we need to convert it to R5 since that's what the validator 082 * uses. In order to avoid repeated conversions, we put the converted version of the resource 083 * in the {@link org.hl7.fhir.instance.model.api.IAnyResource#getUserData(String)} map 084 * using this key. Since conformance resources returned by validation support are typically 085 * cached by {@link org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain}, 086 * the converted version gets cached too. 087 */ 088 private static final String CANONICAL_USERDATA_KEY = 089 VersionSpecificWorkerContextWrapper.class.getName() + "_CANONICAL_USERDATA_KEY"; 090 091 public static final FhirContext FHIR_CONTEXT_R5 = FhirContext.forR5(); 092 private final ValidationSupportContext myValidationSupportContext; 093 private final VersionCanonicalizer myVersionCanonicalizer; 094 private volatile List<StructureDefinition> myAllStructures; 095 private volatile Set<String> myAllPrimitiveTypes; 096 private Parameters myExpansionProfile; 097 private volatile FHIRPathEngine myFHIRPathEngine; 098 099 public VersionSpecificWorkerContextWrapper( 100 ValidationSupportContext theValidationSupportContext, VersionCanonicalizer theVersionCanonicalizer) { 101 myValidationSupportContext = theValidationSupportContext; 102 myVersionCanonicalizer = theVersionCanonicalizer; 103 104 setValidationMessageLanguage(getLocale()); 105 } 106 107 @Override 108 public Set<String> getBinaryKeysAsSet() { 109 throw new UnsupportedOperationException(Msg.code(2118)); 110 } 111 112 @Override 113 public boolean hasBinaryKey(String s) { 114 return myValidationSupportContext.getRootValidationSupport().fetchBinary(s) != null; 115 } 116 117 @Override 118 public byte[] getBinaryForKey(String s) { 119 return myValidationSupportContext.getRootValidationSupport().fetchBinary(s); 120 } 121 122 @Override 123 public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader) throws FHIRException { 124 throw new UnsupportedOperationException(Msg.code(652)); 125 } 126 127 @Override 128 public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader, List<String> types) throws FHIRException { 129 throw new UnsupportedOperationException(Msg.code(653)); 130 } 131 132 @Override 133 public int loadFromPackageAndDependencies(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm) 134 throws FHIRException { 135 throw new UnsupportedOperationException(Msg.code(654)); 136 } 137 138 @Override 139 public boolean hasPackage(String id, String ver) { 140 throw new UnsupportedOperationException(Msg.code(655)); 141 } 142 143 @Override 144 public boolean hasPackage(PackageInformation packageInformation) { 145 return false; 146 } 147 148 @Override 149 public PackageInformation getPackage(String id, String ver) { 150 return null; 151 } 152 153 @Override 154 public int getClientRetryCount() { 155 throw new UnsupportedOperationException(Msg.code(656)); 156 } 157 158 @Override 159 public IWorkerContext setClientRetryCount(int value) { 160 throw new UnsupportedOperationException(Msg.code(657)); 161 } 162 163 @Override 164 public TimeTracker clock() { 165 return null; 166 } 167 168 @Override 169 public IWorkerContextManager.IPackageLoadingTracker getPackageTracker() { 170 throw new UnsupportedOperationException(Msg.code(2235)); 171 } 172 173 @Override 174 public IWorkerContext setPackageTracker(IWorkerContextManager.IPackageLoadingTracker packageTracker) { 175 throw new UnsupportedOperationException(Msg.code(2266)); 176 } 177 178 @Override 179 public String getSpecUrl() { 180 return ""; 181 } 182 183 @Override 184 public PEBuilder getProfiledElementBuilder( 185 PEBuilder.PEElementPropertiesPolicy thePEElementPropertiesPolicy, boolean theB) { 186 throw new UnsupportedOperationException(Msg.code(2264)); 187 } 188 189 @Override 190 public PackageInformation getPackageForUrl(String s) { 191 throw new UnsupportedOperationException(Msg.code(2236)); 192 } 193 194 @Override 195 public Parameters getExpansionParameters() { 196 return myExpansionProfile; 197 } 198 199 @Override 200 public void setExpansionParameters(Parameters expParameters) { 201 setExpansionProfile(expParameters); 202 } 203 204 public void setExpansionProfile(Parameters expParameters) { 205 myExpansionProfile = expParameters; 206 } 207 208 private List<StructureDefinition> allStructureDefinitions() { 209 210 List<StructureDefinition> retVal = myAllStructures; 211 if (retVal == null) { 212 List<IBaseResource> allStructureDefinitions = 213 myValidationSupportContext.getRootValidationSupport().fetchAllStructureDefinitions(); 214 assert allStructureDefinitions != null; 215 216 /* 217 * This method (allStructureDefinitions()) gets called recursively - As we 218 * try to return all StructureDefinitions, we want to generate snapshots for 219 * any that don't already have a snapshot. But the snapshot generator in turn 220 * also calls allStructureDefinitions() - That specific call doesn't require 221 * that the returned SDs have snapshots generated though. 222 * 223 * So, we first just convert to canonical version and store a list containing 224 * the canonical versions. That way any recursive calls will return the 225 * stored list. But after that we'll generate all the snapshots and 226 * store that list instead. If the canonicalization fails with an 227 * unexpected exception, we wipe the stored list. This is probably an 228 * unrecoverable failure since this method will probably always 229 * fail if it fails once. But at least this way we're likley to 230 * generate useful error messages for the user. 231 */ 232 retVal = allStructureDefinitions.stream() 233 .map(myVersionCanonicalizer::structureDefinitionToCanonical) 234 .collect(Collectors.toList()); 235 myAllStructures = retVal; 236 237 try { 238 for (IBaseResource next : allStructureDefinitions) { 239 Resource converted = convertToCanonicalVersionAndGenerateSnapshot(next, false); 240 retVal.add((StructureDefinition) converted); 241 } 242 myAllStructures = retVal; 243 } catch (Exception e) { 244 ourLog.error("Failure during snapshot generation", e); 245 myAllStructures = null; 246 } 247 } 248 249 return retVal; 250 } 251 252 @Override 253 public void cacheResource(Resource res) {} 254 255 @Override 256 public void cacheResourceFromPackage(Resource res, PackageInformation packageDetails) throws FHIRException {} 257 258 @Override 259 public void cachePackage(PackageInformation packageInformation) {} 260 261 @Nonnull 262 private ValidationResult convertValidationResult( 263 String theSystem, @Nullable IValidationSupport.CodeValidationResult theResult) { 264 ValidationResult retVal = null; 265 if (theResult != null) { 266 String code = theResult.getCode(); 267 String display = theResult.getDisplay(); 268 269 String issueSeverityCode = theResult.getSeverityCode(); 270 String message = theResult.getMessage(); 271 ValidationMessage.IssueSeverity issueSeverity = null; 272 if (issueSeverityCode != null) { 273 issueSeverity = ValidationMessage.IssueSeverity.fromCode(issueSeverityCode); 274 } else if (isNotBlank(message)) { 275 issueSeverity = ValidationMessage.IssueSeverity.INFORMATION; 276 } 277 278 CodeSystem.ConceptDefinitionComponent conceptDefinitionComponent = null; 279 if (code != null) { 280 conceptDefinitionComponent = new CodeSystem.ConceptDefinitionComponent() 281 .setCode(code) 282 .setDisplay(display); 283 } 284 285 retVal = new ValidationResult( 286 issueSeverity, 287 message, 288 theSystem, 289 theResult.getCodeSystemVersion(), 290 conceptDefinitionComponent, 291 display, 292 getIssuesForCodeValidation(theResult.getIssues())); 293 } 294 295 if (retVal == null) { 296 retVal = new ValidationResult(ValidationMessage.IssueSeverity.ERROR, "Validation failed", null); 297 } 298 299 return retVal; 300 } 301 302 private List<OperationOutcome.OperationOutcomeIssueComponent> getIssuesForCodeValidation( 303 List<IValidationSupport.CodeValidationIssue> theIssues) { 304 List<OperationOutcome.OperationOutcomeIssueComponent> issueComponents = new ArrayList<>(); 305 306 for (IValidationSupport.CodeValidationIssue issue : theIssues) { 307 OperationOutcome.IssueSeverity severity = 308 OperationOutcome.IssueSeverity.fromCode(issue.getSeverity().getCode()); 309 OperationOutcome.IssueType issueType = 310 OperationOutcome.IssueType.fromCode(issue.getType().getCode()); 311 String diagnostics = issue.getDiagnostics(); 312 313 IValidationSupport.CodeValidationIssueDetails details = issue.getDetails(); 314 CodeableConcept codeableConcept = new CodeableConcept().setText(details.getText()); 315 details.getCodings().forEach(detailCoding -> codeableConcept 316 .addCoding() 317 .setSystem(detailCoding.getSystem()) 318 .setCode(detailCoding.getCode())); 319 320 OperationOutcome.OperationOutcomeIssueComponent issueComponent = 321 new OperationOutcome.OperationOutcomeIssueComponent() 322 .setSeverity(severity) 323 .setCode(issueType) 324 .setDetails(codeableConcept) 325 .setDiagnostics(diagnostics); 326 issueComponent 327 .addExtension() 328 .setUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-message-id") 329 .setValue(new StringType("Terminology_PassThrough_TX_Message")); 330 issueComponents.add(issueComponent); 331 } 332 return issueComponents; 333 } 334 335 @Override 336 public ValueSetExpansionOutcome expandVS(ValueSet source, boolean cacheOk, boolean Hierarchical) { 337 IBaseResource convertedSource; 338 try { 339 convertedSource = myVersionCanonicalizer.valueSetFromValidatorCanonical(source); 340 } catch (FHIRException e) { 341 throw new InternalErrorException(Msg.code(661) + e); 342 } 343 IValidationSupport.ValueSetExpansionOutcome expanded = myValidationSupportContext 344 .getRootValidationSupport() 345 .expandValueSet(myValidationSupportContext, null, convertedSource); 346 347 ValueSet convertedResult = null; 348 if (expanded.getValueSet() != null) { 349 try { 350 convertedResult = myVersionCanonicalizer.valueSetToValidatorCanonical(expanded.getValueSet()); 351 } catch (FHIRException e) { 352 throw new InternalErrorException(Msg.code(662) + e); 353 } 354 } 355 356 String error = expanded.getError(); 357 TerminologyServiceErrorClass result = null; 358 359 return new ValueSetExpansionOutcome(convertedResult, error, result, expanded.getErrorIsFromServer()); 360 } 361 362 @Override 363 public ValueSetExpansionOutcome expandVS( 364 Resource src, 365 ElementDefinition.ElementDefinitionBindingComponent binding, 366 boolean cacheOk, 367 boolean Hierarchical) { 368 ValueSet valueSet = fetchResource(ValueSet.class, binding.getValueSet(), src); 369 return expandVS(valueSet, cacheOk, Hierarchical); 370 } 371 372 @Override 373 public ValueSetExpansionOutcome expandVS(ValueSet.ConceptSetComponent inc, boolean hierarchical, boolean noInactive) 374 throws TerminologyServiceException { 375 throw new UnsupportedOperationException(Msg.code(664)); 376 } 377 378 @Override 379 public Locale getLocale() { 380 return myValidationSupportContext 381 .getRootValidationSupport() 382 .getFhirContext() 383 .getLocalizer() 384 .getLocale(); 385 } 386 387 @Override 388 public void setLocale(Locale locale) { 389 // ignore 390 } 391 392 @Override 393 public CodeSystem fetchCodeSystem(String system) { 394 IBaseResource fetched = 395 myValidationSupportContext.getRootValidationSupport().fetchCodeSystem(system); 396 if (fetched == null) { 397 return null; 398 } 399 try { 400 return myVersionCanonicalizer.codeSystemToValidatorCanonical(fetched); 401 } catch (FHIRException e) { 402 throw new InternalErrorException(Msg.code(665) + e); 403 } 404 } 405 406 @Override 407 public CodeSystem fetchCodeSystem(String system, String verison) { 408 IBaseResource fetched = 409 myValidationSupportContext.getRootValidationSupport().fetchCodeSystem(system); 410 if (fetched == null) { 411 return null; 412 } 413 try { 414 return myVersionCanonicalizer.codeSystemToValidatorCanonical(fetched); 415 } catch (FHIRException e) { 416 throw new InternalErrorException(Msg.code(1992) + e); 417 } 418 } 419 420 @Override 421 public CodeSystem fetchCodeSystem(String system, FhirPublication fhirVersion) { 422 return null; 423 } 424 425 @Override 426 public CodeSystem fetchCodeSystem(String system, String version, FhirPublication fhirVersion) { 427 return null; 428 } 429 430 @Override 431 public CodeSystem fetchSupplementedCodeSystem(String system) { 432 return null; 433 } 434 435 @Override 436 public CodeSystem fetchSupplementedCodeSystem(String system, String version) { 437 return null; 438 } 439 440 @Override 441 public CodeSystem fetchSupplementedCodeSystem(String system, FhirPublication fhirVersion) { 442 return null; 443 } 444 445 @Override 446 public CodeSystem fetchSupplementedCodeSystem(String system, String version, FhirPublication fhirVersion) { 447 return null; 448 } 449 450 @Override 451 public <T extends Resource> T fetchResourceRaw(Class<T> class_, String uri) { 452 return fetchResource(class_, uri); 453 } 454 455 @Override 456 public <T extends Resource> T fetchResource(Class<T> class_, String theUri) { 457 if (isBlank(theUri)) { 458 return null; 459 } 460 461 if (StringUtils.countMatches(theUri, "|") > 1) { 462 ourLog.warn("Unrecognized profile uri: {}", theUri); 463 } 464 465 String resourceType = getResourceType(class_); 466 @SuppressWarnings("unchecked") 467 T retVal = (T) fetchResource(resourceType, theUri); 468 469 return retVal; 470 } 471 472 @Override 473 public Resource fetchResourceById(String type, String uri) { 474 throw new UnsupportedOperationException(Msg.code(666)); 475 } 476 477 @Override 478 public Resource fetchResourceById(String type, String uri, FhirPublication fhirVersion) { 479 return null; 480 } 481 482 @Override 483 public <T extends Resource> T fetchResourceWithException(Class<T> class_, String uri) throws FHIRException { 484 T retVal = fetchResource(class_, uri); 485 if (retVal == null) { 486 throw new FHIRException( 487 Msg.code(667) + "Can not find resource of type " + class_.getSimpleName() + " with uri " + uri); 488 } 489 return retVal; 490 } 491 492 @Override 493 public <T extends Resource> T fetchResource(Class<T> class_, String uri, String version) { 494 return fetchResource(class_, uri + "|" + version); 495 } 496 497 @Override 498 public <T extends Resource> T fetchResource(Class<T> class_, String uri, FhirPublication fhirVersion) { 499 return null; 500 } 501 502 @Override 503 public <T extends Resource> T fetchResource( 504 Class<T> class_, String uri, String version, FhirPublication fhirVersion) { 505 return null; 506 } 507 508 @Override 509 public <T extends Resource> T fetchResource(Class<T> class_, String uri, Resource canonicalForSource) { 510 return fetchResource(class_, uri); 511 } 512 513 @Override 514 public <T extends Resource> List<T> fetchResourcesByType(Class<T> class_, FhirPublication fhirVersion) { 515 return null; 516 } 517 518 @Override 519 public <T extends Resource> T fetchResourceWithException(Class<T> class_, String uri, Resource sourceOfReference) 520 throws FHIRException { 521 throw new UnsupportedOperationException(Msg.code(2214)); 522 } 523 524 @Override 525 public List<String> getResourceNames() { 526 return new ArrayList<>(myValidationSupportContext 527 .getRootValidationSupport() 528 .getFhirContext() 529 .getResourceTypes()); 530 } 531 532 @Override 533 public List<String> getResourceNames(FhirPublication fhirVersion) { 534 return null; 535 } 536 537 @Override 538 public Set<String> getResourceNamesAsSet() { 539 return myValidationSupportContext 540 .getRootValidationSupport() 541 .getFhirContext() 542 .getResourceTypes(); 543 } 544 545 @Override 546 public Set<String> getResourceNamesAsSet(FhirPublication theFhirVersion) { 547 return null; 548 } 549 550 @Override 551 public StructureDefinition fetchTypeDefinition(String theTypeName) { 552 return fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + theTypeName); 553 } 554 555 @Override 556 public StructureDefinition fetchTypeDefinition(String theTypeName, FhirPublication theFhirVersion) { 557 return null; 558 } 559 560 @Override 561 public List<StructureDefinition> fetchTypeDefinitions(String theTypeName) { 562 List<StructureDefinition> allStructures = new ArrayList<>(allStructureDefinitions()); 563 allStructures.removeIf(sd -> !sd.hasType() || !sd.getType().equals(theTypeName)); 564 return allStructures; 565 } 566 567 @Override 568 public List<StructureDefinition> fetchTypeDefinitions(String theTypeName, FhirPublication theFhirVersion) { 569 return null; 570 } 571 572 @Override 573 public boolean isPrimitiveType(String theType) { 574 return allPrimitiveTypes().contains(theType); 575 } 576 577 private Set<String> allPrimitiveTypes() { 578 Set<String> retVal = myAllPrimitiveTypes; 579 if (retVal == null) { 580 // Collector may be changed to Collectors.toUnmodifiableSet() when switching to Android API level >= 33 581 retVal = allStructureDefinitions().stream() 582 .filter(structureDefinition -> 583 structureDefinition.getKind() == StructureDefinition.StructureDefinitionKind.PRIMITIVETYPE) 584 .map(StructureDefinition::getName) 585 .filter(Objects::nonNull) 586 .collect(collectingAndThen(toSet(), Collections::unmodifiableSet)); 587 myAllPrimitiveTypes = retVal; 588 } 589 590 return retVal; 591 } 592 593 @Override 594 public boolean isDataType(String theType) { 595 return !isPrimitiveType(theType); 596 } 597 598 @Override 599 public UcumService getUcumService() { 600 throw new UnsupportedOperationException(Msg.code(676)); 601 } 602 603 @Override 604 public void setUcumService(UcumService ucumService) { 605 throw new UnsupportedOperationException(Msg.code(677)); 606 } 607 608 @Override 609 public String getVersion() { 610 return myValidationSupportContext 611 .getRootValidationSupport() 612 .getFhirContext() 613 .getVersion() 614 .getVersion() 615 .getFhirVersionString(); 616 } 617 618 @Override 619 public <T extends Resource> boolean hasResource(Class<T> class_, String uri) { 620 if (isBlank(uri)) { 621 return false; 622 } 623 624 String resourceType = getResourceType(class_); 625 return fetchResource(resourceType, uri) != null; 626 } 627 628 private static <T extends Resource> String getResourceType(Class<T> theClass) { 629 if (theClass.getSimpleName().equals("Resource")) { 630 return "Resource"; 631 } 632 return FHIR_CONTEXT_R5.getResourceType(theClass); 633 } 634 635 @Override 636 public <T extends Resource> boolean hasResource(Class<T> class_, String uri, Resource sourceOfReference) { 637 return false; 638 } 639 640 @Override 641 public <T extends Resource> boolean hasResource(Class<T> class_, String uri, FhirPublication fhirVersion) { 642 return false; 643 } 644 645 @Override 646 public boolean isNoTerminologyServer() { 647 return false; 648 } 649 650 @Override 651 public Set<String> getCodeSystemsUsed() { 652 throw new UnsupportedOperationException(Msg.code(681)); 653 } 654 655 @Override 656 public IResourceValidator newValidator() { 657 throw new UnsupportedOperationException(Msg.code(684)); 658 } 659 660 @Override 661 public Map<String, NamingSystem> getNSUrlMap() { 662 throw new UnsupportedOperationException(Msg.code(2265)); 663 } 664 665 @Override 666 public org.hl7.fhir.r5.context.ILoggingService getLogger() { 667 return null; 668 } 669 670 @Override 671 public void setLogger(org.hl7.fhir.r5.context.ILoggingService logger) { 672 throw new UnsupportedOperationException(Msg.code(687)); 673 } 674 675 @Override 676 public boolean supportsSystem(String system) { 677 return myValidationSupportContext 678 .getRootValidationSupport() 679 .isCodeSystemSupported(myValidationSupportContext, system); 680 } 681 682 @Override 683 public boolean supportsSystem(String system, FhirPublication fhirVersion) throws TerminologyServiceException { 684 return supportsSystem(system); 685 } 686 687 @Override 688 public ValueSetExpansionOutcome expandVS( 689 ValueSet source, boolean cacheOk, boolean heiarchical, boolean incompleteOk) { 690 return null; 691 } 692 693 @Override 694 public ValidationResult validateCode( 695 ValidationOptions theOptions, String system, String version, String code, String display) { 696 ConceptValidationOptions validationOptions = convertConceptValidationOptions(theOptions); 697 return doValidation(null, validationOptions, system, code, display); 698 } 699 700 @Override 701 public ValidationResult validateCode( 702 ValidationOptions theOptions, 703 String theSystem, 704 String version, 705 String theCode, 706 String display, 707 ValueSet theValueSet) { 708 IBaseResource convertedVs = null; 709 710 try { 711 if (theValueSet != null) { 712 convertedVs = myVersionCanonicalizer.valueSetFromValidatorCanonical(theValueSet); 713 } 714 } catch (FHIRException e) { 715 throw new InternalErrorException(Msg.code(689) + e); 716 } 717 718 ConceptValidationOptions validationOptions = convertConceptValidationOptions(theOptions); 719 720 return doValidation(convertedVs, validationOptions, theSystem, theCode, display); 721 } 722 723 @Override 724 public ValidationResult validateCode(ValidationOptions theOptions, String code, ValueSet theValueSet) { 725 IBaseResource convertedVs = null; 726 try { 727 if (theValueSet != null) { 728 convertedVs = myVersionCanonicalizer.valueSetFromValidatorCanonical(theValueSet); 729 } 730 } catch (FHIRException e) { 731 throw new InternalErrorException(Msg.code(690) + e); 732 } 733 734 String system = ValidationSupportUtils.extractCodeSystemForCode(theValueSet, code); 735 736 ConceptValidationOptions validationOptions = 737 convertConceptValidationOptions(theOptions).setInferSystem(true); 738 739 return doValidation(convertedVs, validationOptions, system, code, null); 740 } 741 742 @Override 743 public ValidationResult validateCode(ValidationOptions theOptions, Coding theCoding, ValueSet theValueSet) { 744 IBaseResource convertedVs = null; 745 746 try { 747 if (theValueSet != null) { 748 convertedVs = myVersionCanonicalizer.valueSetFromValidatorCanonical(theValueSet); 749 } 750 } catch (FHIRException e) { 751 throw new InternalErrorException(Msg.code(691) + e); 752 } 753 754 ConceptValidationOptions validationOptions = convertConceptValidationOptions(theOptions); 755 String system = theCoding.getSystem(); 756 String code = theCoding.getCode(); 757 String display = theCoding.getDisplay(); 758 759 return doValidation(convertedVs, validationOptions, system, code, display); 760 } 761 762 @Override 763 public ValidationResult validateCode( 764 ValidationOptions options, Coding code, ValueSet vs, ValidationContextCarrier ctxt) { 765 return validateCode(options, code, vs); 766 } 767 768 @Override 769 public void validateCodeBatch( 770 ValidationOptions options, List<? extends CodingValidationRequest> codes, ValueSet vs) { 771 for (CodingValidationRequest next : codes) { 772 ValidationResult outcome = validateCode(options, next.getCoding(), vs); 773 next.setResult(outcome); 774 } 775 } 776 777 @Override 778 public void validateCodeBatchByRef( 779 ValidationOptions validationOptions, List<? extends CodingValidationRequest> list, String s) { 780 ValueSet valueSet = fetchResource(ValueSet.class, s); 781 validateCodeBatch(validationOptions, list, valueSet); 782 } 783 784 @Nonnull 785 private ValidationResult doValidation( 786 IBaseResource theValueSet, 787 ConceptValidationOptions theValidationOptions, 788 String theSystem, 789 String theCode, 790 String theDisplay) { 791 IValidationSupport.CodeValidationResult result; 792 if (theValueSet != null) { 793 result = validateCodeInValueSet(theValueSet, theValidationOptions, theSystem, theCode, theDisplay); 794 } else { 795 result = validateCodeInCodeSystem(theValidationOptions, theSystem, theCode, theDisplay); 796 } 797 return convertValidationResult(theSystem, result); 798 } 799 800 private IValidationSupport.CodeValidationResult validateCodeInValueSet( 801 IBaseResource theValueSet, 802 ConceptValidationOptions theValidationOptions, 803 String theSystem, 804 String theCode, 805 String theDisplay) { 806 IValidationSupport.CodeValidationResult result = myValidationSupportContext 807 .getRootValidationSupport() 808 .validateCodeInValueSet( 809 myValidationSupportContext, theValidationOptions, theSystem, theCode, theDisplay, theValueSet); 810 if (result != null && isNotBlank(theSystem)) { 811 /* We got a value set result, which could be successful, or could contain errors/warnings. The code 812 might also be invalid in the code system, so we will check that as well and add those issues 813 to our result. 814 */ 815 IValidationSupport.CodeValidationResult codeSystemResult = 816 validateCodeInCodeSystem(theValidationOptions, theSystem, theCode, theDisplay); 817 final boolean valueSetResultContainsInvalidDisplay = result.getIssues().stream() 818 .anyMatch(VersionSpecificWorkerContextWrapper::hasInvalidDisplayDetailCode); 819 if (codeSystemResult != null) { 820 for (IValidationSupport.CodeValidationIssue codeValidationIssue : codeSystemResult.getIssues()) { 821 /* Value set validation should already have checked the display name. If we get INVALID_DISPLAY 822 issues from code system validation, they will only repeat what was already caught. 823 */ 824 if (!hasInvalidDisplayDetailCode(codeValidationIssue) || !valueSetResultContainsInvalidDisplay) { 825 result.addIssue(codeValidationIssue); 826 } 827 } 828 } 829 } 830 return result; 831 } 832 833 private static boolean hasInvalidDisplayDetailCode(IValidationSupport.CodeValidationIssue theIssue) { 834 return theIssue.hasIssueDetailCode(INVALID_DISPLAY.getCode()); 835 } 836 837 private IValidationSupport.CodeValidationResult validateCodeInCodeSystem( 838 ConceptValidationOptions theValidationOptions, String theSystem, String theCode, String theDisplay) { 839 return myValidationSupportContext 840 .getRootValidationSupport() 841 .validateCode(myValidationSupportContext, theValidationOptions, theSystem, theCode, theDisplay, null); 842 } 843 844 @Override 845 public ValidationResult validateCode(ValidationOptions theOptions, CodeableConcept code, ValueSet theVs) { 846 847 List<ValidationResult> validationResultsOk = new ArrayList<>(); 848 List<OperationOutcome.OperationOutcomeIssueComponent> issues = new ArrayList<>(); 849 for (Coding next : code.getCoding()) { 850 if (!next.hasSystem()) { 851 String message = 852 "Coding has no system. A code with no system has no defined meaning, and it cannot be validated. A system should be provided"; 853 OperationOutcome.OperationOutcomeIssueComponent issue = 854 new OperationOutcome.OperationOutcomeIssueComponent() 855 .setSeverity(OperationOutcome.IssueSeverity.WARNING) 856 .setCode(OperationOutcome.IssueType.NOTFOUND) 857 .setDiagnostics(message) 858 .setDetails(new CodeableConcept().setText(message)); 859 860 issues.add(issue); 861 } 862 ValidationResult retVal = validateCode(theOptions, next, theVs); 863 if (retVal.isOk()) { 864 validationResultsOk.add(retVal); 865 } else { 866 for (OperationOutcome.OperationOutcomeIssueComponent issue : retVal.getIssues()) { 867 issues.add(issue); 868 } 869 } 870 } 871 872 if (code.getCoding().size() > 0) { 873 if (!myValidationSupportContext.isCodeableConceptValidationSuccessfulIfNotAllCodingsAreValid()) { 874 if (validationResultsOk.size() == code.getCoding().size()) { 875 return validationResultsOk.get(0); 876 } 877 } else { 878 if (validationResultsOk.size() > 0) { 879 return validationResultsOk.get(0); 880 } 881 } 882 } 883 884 return new ValidationResult(ValidationMessage.IssueSeverity.ERROR, null, issues); 885 } 886 887 private static OperationOutcome.OperationOutcomeIssueComponent getOperationOutcomeTxIssueComponent( 888 String message, OperationOutcome.IssueType issueCode, String txIssueTypeCode) { 889 OperationOutcome.OperationOutcomeIssueComponent issue = new OperationOutcome.OperationOutcomeIssueComponent() 890 .setSeverity(OperationOutcome.IssueSeverity.ERROR) 891 .setDiagnostics(message); 892 issue.getDetails().setText(message); 893 894 issue.setCode(issueCode); 895 issue.getDetails().addCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", txIssueTypeCode, null); 896 return issue; 897 } 898 899 public void invalidateCaches() { 900 // nothing for now 901 } 902 903 @Override 904 public <T extends Resource> List<T> fetchResourcesByType(Class<T> theClass) { 905 if (theClass.equals(StructureDefinition.class)) { 906 return (List<T>) allStructureDefinitions(); 907 } 908 throw new UnsupportedOperationException(Msg.code(650) + "Unable to fetch resources of type: " + theClass); 909 } 910 911 @Override 912 public <T extends Resource> List<T> fetchResourcesByUrl(Class<T> class_, String url) { 913 throw new UnsupportedOperationException(Msg.code(2509) + "Can't fetch all resources of url: " + url); 914 } 915 916 @Override 917 public boolean isForPublication() { 918 return false; 919 } 920 921 @Override 922 public void setForPublication(boolean b) { 923 throw new UnsupportedOperationException(Msg.code(2351)); 924 } 925 926 @Override 927 public OIDSummary urlsForOid(String oid, String resourceType) { 928 return null; 929 } 930 931 @Override 932 public <T extends Resource> T findTxResource(Class<T> class_, String canonical, Resource sourceOfReference) { 933 if (canonical == null) { 934 return null; 935 } 936 return fetchResource(class_, canonical, sourceOfReference); 937 } 938 939 @Override 940 public <T extends Resource> T findTxResource(Class<T> class_, String canonical) { 941 if (canonical == null) { 942 return null; 943 } 944 945 return fetchResource(class_, canonical); 946 } 947 948 @Override 949 public <T extends Resource> T findTxResource(Class<T> class_, String canonical, String version) { 950 if (canonical == null) { 951 return null; 952 } 953 954 return fetchResource(class_, canonical, version); 955 } 956 957 public static ConceptValidationOptions convertConceptValidationOptions(ValidationOptions theOptions) { 958 ConceptValidationOptions retVal = new ConceptValidationOptions(); 959 if (theOptions.isGuessSystem()) { 960 retVal = retVal.setInferSystem(true); 961 } 962 return retVal; 963 } 964 965 @Nonnull 966 public static VersionSpecificWorkerContextWrapper newVersionSpecificWorkerContextWrapper( 967 IValidationSupport theValidationSupport) { 968 VersionCanonicalizer versionCanonicalizer = new VersionCanonicalizer(theValidationSupport.getFhirContext()); 969 return new VersionSpecificWorkerContextWrapper( 970 new ValidationSupportContext(theValidationSupport), versionCanonicalizer); 971 } 972 973 private static class ResourceKey { 974 private final int myHashCode; 975 private final String myResourceName; 976 private final String myUri; 977 978 private ResourceKey(String theResourceName, String theUri) { 979 myResourceName = theResourceName; 980 myUri = theUri; 981 myHashCode = new HashCodeBuilder(17, 37) 982 .append(myResourceName) 983 .append(myUri) 984 .toHashCode(); 985 } 986 987 @Override 988 public boolean equals(Object theO) { 989 if (this == theO) { 990 return true; 991 } 992 993 if (theO == null || getClass() != theO.getClass()) { 994 return false; 995 } 996 997 ResourceKey that = (ResourceKey) theO; 998 999 return new EqualsBuilder() 1000 .append(myResourceName, that.myResourceName) 1001 .append(myUri, that.myUri) 1002 .isEquals(); 1003 } 1004 1005 public String getResourceName() { 1006 return myResourceName; 1007 } 1008 1009 public String getUri() { 1010 return myUri; 1011 } 1012 1013 @Override 1014 public int hashCode() { 1015 return myHashCode; 1016 } 1017 } 1018 1019 @Override 1020 public Boolean subsumes(ValidationOptions optionsArg, Coding parent, Coding child) { 1021 throw new UnsupportedOperationException(Msg.code(2489)); 1022 } 1023 1024 @Override 1025 public boolean isServerSideSystem(String url) { 1026 return false; 1027 } 1028 1029 private IBaseResource fetchResource(String theResourceType, String theUrl) { 1030 String fetchResourceName = theResourceType; 1031 if (myValidationSupportContext 1032 .getRootValidationSupport() 1033 .getFhirContext() 1034 .getVersion() 1035 .getVersion() 1036 == FhirVersionEnum.DSTU2) { 1037 if ("CodeSystem".equals(fetchResourceName)) { 1038 fetchResourceName = "ValueSet"; 1039 } 1040 } 1041 1042 Class<? extends IBaseResource> fetchResourceType; 1043 if (fetchResourceName.equals("Resource")) { 1044 fetchResourceType = null; 1045 } else { 1046 fetchResourceType = myValidationSupportContext 1047 .getRootValidationSupport() 1048 .getFhirContext() 1049 .getResourceDefinition(fetchResourceName) 1050 .getImplementingClass(); 1051 } 1052 1053 IBaseResource fetched = 1054 myValidationSupportContext.getRootValidationSupport().fetchResource(fetchResourceType, theUrl); 1055 1056 if (fetched == null) { 1057 return null; 1058 } 1059 1060 return convertToCanonicalVersionAndGenerateSnapshot(fetched, true); 1061 } 1062 1063 private Resource convertToCanonicalVersionAndGenerateSnapshot( 1064 @Nonnull IBaseResource theResource, boolean thePropagateSnapshotException) { 1065 Resource canonical; 1066 synchronized (theResource) { 1067 canonical = (Resource) theResource.getUserData(CANONICAL_USERDATA_KEY); 1068 if (canonical == null) { 1069 boolean storeCanonical = true; 1070 canonical = myVersionCanonicalizer.resourceToValidatorCanonical(theResource); 1071 1072 if (canonical instanceof StructureDefinition) { 1073 StructureDefinition canonicalSd = (StructureDefinition) canonical; 1074 if (canonicalSd.getSnapshot().isEmpty()) { 1075 ourLog.info("Generating snapshot for StructureDefinition: {}", canonicalSd.getUrl()); 1076 IBaseResource resource = theResource; 1077 try { 1078 1079 FhirContext fhirContext = myValidationSupportContext 1080 .getRootValidationSupport() 1081 .getFhirContext(); 1082 SnapshotGeneratingValidationSupport snapshotGenerator = 1083 new SnapshotGeneratingValidationSupport(fhirContext, this, getFHIRPathEngine()); 1084 resource = snapshotGenerator.generateSnapshot( 1085 myValidationSupportContext, resource, "", null, ""); 1086 Validate.isTrue( 1087 resource != null, 1088 "StructureDefinition %s has no snapshot, and no snapshot generator is configured", 1089 canonicalSd.getUrl()); 1090 1091 } catch (BaseServerResponseException e) { 1092 if (thePropagateSnapshotException) { 1093 throw e; 1094 } 1095 String message = e.toString(); 1096 Throwable rootCause = ExceptionUtils.getRootCause(e); 1097 if (rootCause != null) { 1098 message = rootCause.getMessage(); 1099 } 1100 ourLog.warn( 1101 "Failed to generate snapshot for profile with URL[{}]: {}", 1102 canonicalSd.getUrl(), 1103 message); 1104 storeCanonical = false; 1105 } 1106 1107 canonical = myVersionCanonicalizer.resourceToValidatorCanonical(resource); 1108 } 1109 } 1110 1111 String sourcePackageId = 1112 (String) theResource.getUserData(DefaultProfileValidationSupport.SOURCE_PACKAGE_ID); 1113 if (sourcePackageId != null) { 1114 canonical.setSourcePackage(new PackageInformation(sourcePackageId, null, null, new Date())); 1115 } 1116 1117 if (storeCanonical) { 1118 theResource.setUserData(CANONICAL_USERDATA_KEY, canonical); 1119 } 1120 } 1121 } 1122 return canonical; 1123 } 1124 1125 private FHIRPathEngine getFHIRPathEngine() { 1126 FHIRPathEngine retVal = myFHIRPathEngine; 1127 if (retVal == null) { 1128 retVal = new FHIRPathEngine(this); 1129 myFHIRPathEngine = retVal; 1130 } 1131 return retVal; 1132 } 1133}