001package org.hl7.fhir.common.hapi.validation.validator; 002 003import ca.uhn.fhir.context.FhirVersionEnum; 004import ca.uhn.fhir.context.support.ConceptValidationOptions; 005import ca.uhn.fhir.context.support.IValidationSupport; 006import ca.uhn.fhir.context.support.ValidationSupportContext; 007import ca.uhn.fhir.i18n.Msg; 008import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 009import ca.uhn.fhir.sl.cache.CacheFactory; 010import ca.uhn.fhir.sl.cache.LoadingCache; 011import ca.uhn.fhir.system.HapiSystemProperties; 012import ca.uhn.hapi.converters.canonical.VersionCanonicalizer; 013import org.apache.commons.lang3.Validate; 014import org.apache.commons.lang3.builder.EqualsBuilder; 015import org.apache.commons.lang3.builder.HashCodeBuilder; 016import org.fhir.ucum.UcumService; 017import org.hl7.fhir.exceptions.FHIRException; 018import org.hl7.fhir.exceptions.TerminologyServiceException; 019import org.hl7.fhir.instance.model.api.IBaseResource; 020import org.hl7.fhir.r5.context.IWorkerContext; 021import org.hl7.fhir.r5.context.IWorkerContextManager; 022import org.hl7.fhir.r5.model.CodeSystem; 023import org.hl7.fhir.r5.model.Coding; 024import org.hl7.fhir.r5.model.NamingSystem; 025import org.hl7.fhir.r5.model.PackageInformation; 026import org.hl7.fhir.r5.model.Resource; 027import org.hl7.fhir.r5.model.StructureDefinition; 028import org.hl7.fhir.r5.model.ValueSet; 029import org.hl7.fhir.r5.profilemodel.PEBuilder; 030import org.hl7.fhir.r5.terminologies.ValueSetExpander; 031import org.hl7.fhir.r5.utils.validation.IResourceValidator; 032import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier; 033import org.hl7.fhir.utilities.TimeTracker; 034import org.hl7.fhir.utilities.TranslationServices; 035import org.hl7.fhir.utilities.i18n.I18nBase; 036import org.hl7.fhir.utilities.npm.BasePackageCacheManager; 037import org.hl7.fhir.utilities.npm.NpmPackage; 038import org.hl7.fhir.utilities.validation.ValidationMessage; 039import org.hl7.fhir.utilities.validation.ValidationOptions; 040import org.slf4j.Logger; 041import org.slf4j.LoggerFactory; 042 043import javax.annotation.Nonnull; 044import javax.annotation.Nullable; 045import java.util.ArrayList; 046import java.util.List; 047import java.util.Locale; 048import java.util.Map; 049import java.util.Set; 050 051import static org.apache.commons.lang3.StringUtils.isBlank; 052import static org.apache.commons.lang3.StringUtils.isNotBlank; 053 054public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWorkerContext { 055 private static final Logger ourLog = LoggerFactory.getLogger(VersionSpecificWorkerContextWrapper.class); 056 private final ValidationSupportContext myValidationSupportContext; 057 private final VersionCanonicalizer myVersionCanonicalizer; 058 private final LoadingCache<ResourceKey, IBaseResource> myFetchResourceCache; 059 private volatile List<StructureDefinition> myAllStructures; 060 private org.hl7.fhir.r5.model.Parameters myExpansionProfile; 061 062 public VersionSpecificWorkerContextWrapper(ValidationSupportContext theValidationSupportContext, VersionCanonicalizer theVersionCanonicalizer) { 063 myValidationSupportContext = theValidationSupportContext; 064 myVersionCanonicalizer = theVersionCanonicalizer; 065 066 long timeoutMillis = HapiSystemProperties.getTestValidationResourceCachesMs(); 067 068 myFetchResourceCache = CacheFactory.build(timeoutMillis, 10000, key -> { 069 070 String fetchResourceName = key.getResourceName(); 071 if (myValidationSupportContext.getRootValidationSupport().getFhirContext().getVersion().getVersion() == FhirVersionEnum.DSTU2) { 072 if ("CodeSystem".equals(fetchResourceName)) { 073 fetchResourceName = "ValueSet"; 074 } 075 } 076 077 Class<? extends IBaseResource> fetchResourceType; 078 if (fetchResourceName.equals("Resource")) { 079 fetchResourceType = null; 080 } else { 081 fetchResourceType = myValidationSupportContext.getRootValidationSupport().getFhirContext().getResourceDefinition(fetchResourceName).getImplementingClass(); 082 } 083 084 IBaseResource fetched = myValidationSupportContext.getRootValidationSupport().fetchResource(fetchResourceType, key.getUri()); 085 086 Resource canonical = myVersionCanonicalizer.resourceToValidatorCanonical(fetched); 087 088 if (canonical instanceof StructureDefinition) { 089 StructureDefinition canonicalSd = (StructureDefinition) canonical; 090 if (canonicalSd.getSnapshot().isEmpty()) { 091 ourLog.info("Generating snapshot for StructureDefinition: {}", canonicalSd.getUrl()); 092 fetched = myValidationSupportContext.getRootValidationSupport().generateSnapshot(theValidationSupportContext, fetched, "", null, ""); 093 Validate.isTrue(fetched != null, "StructureDefinition %s has no snapshot, and no snapshot generator is configured", key.getUri()); 094 canonical = myVersionCanonicalizer.resourceToValidatorCanonical(fetched); 095 } 096 } 097 098 return canonical; 099 }); 100 101 setValidationMessageLanguage(getLocale()); 102 } 103 104 @Override 105 public Set<String> getBinaryKeysAsSet() { 106 throw new UnsupportedOperationException(Msg.code(2118)); 107 } 108 109 @Override 110 public boolean hasBinaryKey(String s) { 111 return myValidationSupportContext.getRootValidationSupport().fetchBinary(s) != null; 112 } 113 114 @Override 115 public byte[] getBinaryForKey(String s) { 116 return myValidationSupportContext.getRootValidationSupport().fetchBinary(s); 117 } 118 119 @Override 120 public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader) throws FHIRException { 121 throw new UnsupportedOperationException(Msg.code(652)); 122 } 123 124 @Override 125 public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader, String[] types) throws FHIRException { 126 throw new UnsupportedOperationException(Msg.code(653)); 127 } 128 129 @Override 130 public int loadFromPackageAndDependencies(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm) throws FHIRException { 131 throw new UnsupportedOperationException(Msg.code(654)); 132 } 133 134 @Override 135 public boolean hasPackage(String id, String ver) { 136 throw new UnsupportedOperationException(Msg.code(655)); 137 } 138 139 @Override 140 public boolean hasPackage(PackageInformation packageInformation) { 141 return false; 142 } 143 144 @Override 145 public PackageInformation getPackage(String id, String ver) { 146 return null; 147 } 148 149 @Override 150 public int getClientRetryCount() { 151 throw new UnsupportedOperationException(Msg.code(656)); 152 } 153 154 @Override 155 public IWorkerContext setClientRetryCount(int value) { 156 throw new UnsupportedOperationException(Msg.code(657)); 157 } 158 159 @Override 160 public TimeTracker clock() { 161 return null; 162 } 163 164 @Override 165 public IWorkerContextManager.IPackageLoadingTracker getPackageTracker() { 166 throw new UnsupportedOperationException(Msg.code(2108)); 167 } 168 169 @Override 170 public IWorkerContext setPackageTracker( 171 IWorkerContextManager.IPackageLoadingTracker packageTracker) { 172 throw new UnsupportedOperationException(Msg.code(2114)); 173 } 174 175 @Override 176 public String getSpecUrl() { 177 178 return ""; 179 180 } 181 182 @Override 183 public PEBuilder getProfiledElementBuilder(PEBuilder.PEElementPropertiesPolicy thePEElementPropertiesPolicy, boolean theB) { 184 throw new UnsupportedOperationException(Msg.code(2264)); 185 } 186 187 @Override 188 public PackageInformation getPackageForUrl(String s) { 189 throw new UnsupportedOperationException(Msg.code(2109)); 190 } 191 192 @Override 193 public org.hl7.fhir.r5.model.Parameters getExpansionParameters() { 194 return myExpansionProfile; 195 } 196 197 @Override 198 public void setExpansionProfile(org.hl7.fhir.r5.model.Parameters expParameters) { 199 myExpansionProfile = expParameters; 200 } 201 202 private List<StructureDefinition> allStructures() { 203 204 List<StructureDefinition> retVal = myAllStructures; 205 if (retVal == null) { 206 retVal = new ArrayList<>(); 207 for (IBaseResource next : myValidationSupportContext.getRootValidationSupport().fetchAllStructureDefinitions()) { 208 try { 209 StructureDefinition converted = myVersionCanonicalizer.structureDefinitionToCanonical(next); 210 retVal.add(converted); 211 } catch (FHIRException e) { 212 throw new InternalErrorException(Msg.code(659) + e); 213 } 214 } 215 myAllStructures = retVal; 216 } 217 218 return retVal; 219 } 220 221 @Override 222 public void cacheResource(Resource res) { 223 224 } 225 226 @Override 227 public void cacheResourceFromPackage(Resource res, PackageInformation packageDetails) throws FHIRException { 228 229 } 230 231 @Override 232 public void cachePackage(PackageInformation packageInformation) { 233 234 } 235 236 @Nonnull 237 private ValidationResult convertValidationResult(String theSystem, @Nullable IValidationSupport.CodeValidationResult theResult) { 238 ValidationResult retVal = null; 239 if (theResult != null) { 240 String code = theResult.getCode(); 241 String display = theResult.getDisplay(); 242 String issueSeverity = theResult.getSeverityCode(); 243 String message = theResult.getMessage(); 244 if (isNotBlank(code)) { 245 retVal = new ValidationResult(theSystem, new org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent() 246 .setCode(code) 247 .setDisplay(display)); 248 } else if (isNotBlank(issueSeverity)) { 249 retVal = new ValidationResult(ValidationMessage.IssueSeverity.fromCode(issueSeverity), message, ValueSetExpander.TerminologyServiceErrorClass.UNKNOWN); 250 } 251 252 } 253 254 if (retVal == null) { 255 retVal = new ValidationResult(ValidationMessage.IssueSeverity.ERROR, "Validation failed"); 256 } 257 258 return retVal; 259 } 260 261 @Override 262 public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ValueSet source, boolean cacheOk, boolean Hierarchical) { 263 IBaseResource convertedSource; 264 try { 265 convertedSource = myVersionCanonicalizer.valueSetFromValidatorCanonical(source); 266 } catch (FHIRException e) { 267 throw new InternalErrorException(Msg.code(661) + e); 268 } 269 IValidationSupport.ValueSetExpansionOutcome expanded = myValidationSupportContext.getRootValidationSupport().expandValueSet(myValidationSupportContext, null, convertedSource); 270 271 org.hl7.fhir.r5.model.ValueSet convertedResult = null; 272 if (expanded.getValueSet() != null) { 273 try { 274 convertedResult = myVersionCanonicalizer.valueSetToValidatorCanonical(expanded.getValueSet()); 275 } catch (FHIRException e) { 276 throw new InternalErrorException(Msg.code(662) + e); 277 } 278 } 279 280 String error = expanded.getError(); 281 ValueSetExpander.TerminologyServiceErrorClass result = null; 282 283 return new ValueSetExpander.ValueSetExpansionOutcome(convertedResult, error, result); 284 } 285 286 @Override 287 public ValueSetExpander.ValueSetExpansionOutcome expandVS(Resource src, org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent binding, boolean cacheOk, boolean Hierarchical) { 288 throw new UnsupportedOperationException(Msg.code(663)); 289 } 290 291 @Override 292 public ValueSetExpander.ValueSetExpansionOutcome expandVS(ValueSet.ConceptSetComponent inc, boolean hierarchical, boolean noInactive) throws TerminologyServiceException { 293 throw new UnsupportedOperationException(Msg.code(664)); 294 } 295 296 @Override 297 public Locale getLocale() { 298 return myValidationSupportContext.getRootValidationSupport().getFhirContext().getLocalizer().getLocale(); 299 } 300 301 @Override 302 public void setLocale(Locale locale) { 303 // ignore 304 } 305 306 @Override 307 public org.hl7.fhir.r5.model.CodeSystem fetchCodeSystem(String system) { 308 IBaseResource fetched = myValidationSupportContext.getRootValidationSupport().fetchCodeSystem(system); 309 if (fetched == null) { 310 return null; 311 } 312 try { 313 return (org.hl7.fhir.r5.model.CodeSystem) myVersionCanonicalizer.codeSystemToValidatorCanonical(fetched); 314 } catch (FHIRException e) { 315 throw new InternalErrorException(Msg.code(665) + e); 316 } 317 } 318 319 @Override 320 public CodeSystem fetchCodeSystem(String system, String verison) { 321 IBaseResource fetched = myValidationSupportContext.getRootValidationSupport().fetchCodeSystem(system); 322 if (fetched == null) { 323 return null; 324 } 325 try { 326 return (org.hl7.fhir.r5.model.CodeSystem) myVersionCanonicalizer.codeSystemToValidatorCanonical(fetched); 327 } catch (FHIRException e) { 328 throw new InternalErrorException(Msg.code(1992) + e); 329 } 330 } 331 332 @Override 333 public <T extends Resource> T fetchResource(Class<T> class_, String uri) { 334 335 if (isBlank(uri)) { 336 return null; 337 } 338 339 ResourceKey key = new ResourceKey(class_.getSimpleName(), uri); 340 @SuppressWarnings("unchecked") 341 T retVal = (T) myFetchResourceCache.get(key); 342 343 return retVal; 344 } 345 346 @Override 347 public Resource fetchResourceById(String type, String uri) { 348 throw new UnsupportedOperationException(Msg.code(666)); 349 } 350 351 @Override 352 public <T extends Resource> T fetchResourceWithException(Class<T> class_, String uri) throws FHIRException { 353 T retVal = fetchResource(class_, uri); 354 if (retVal == null) { 355 throw new FHIRException(Msg.code(667) + "Can not find resource of type " + class_.getSimpleName() + " with uri " + uri); 356 } 357 return retVal; 358 } 359 360 @Override 361 public <T extends Resource> T fetchResource(Class<T> class_, String uri, String version) { 362 return fetchResource(class_, uri + "|" + version); 363 } 364 365 @Override 366 public <T extends Resource> T fetchResource(Class<T> class_, String uri, Resource canonicalForSource) { 367 return fetchResource(class_, uri); 368 } 369 370 public <T extends Resource> T fetchResourceWithException(Class<T> class_, String uri, Resource sourceOfReference) throws FHIRException { 371 throw new UnsupportedOperationException(Msg.code(2214)); 372 } 373 374 @Override 375 public List<String> getResourceNames() { 376 return new ArrayList<>(myValidationSupportContext.getRootValidationSupport().getFhirContext().getResourceTypes()); 377 } 378 379 @Override 380 public Set<String> getResourceNamesAsSet() { 381 return myValidationSupportContext.getRootValidationSupport().getFhirContext().getResourceTypes(); 382 } 383 384 @Override 385 public StructureDefinition fetchTypeDefinition(String typeName) { 386 return fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + typeName); 387 } 388 389 @Override 390 public UcumService getUcumService() { 391 throw new UnsupportedOperationException(Msg.code(676)); 392 } 393 394 @Override 395 public void setUcumService(UcumService ucumService) { 396 throw new UnsupportedOperationException(Msg.code(677)); 397 } 398 399 @Override 400 public String getVersion() { 401 return myValidationSupportContext.getRootValidationSupport().getFhirContext().getVersion().getVersion().getFhirVersionString(); 402 } 403 404 @Override 405 public <T extends Resource> boolean hasResource(Class<T> class_, String uri) { 406 throw new UnsupportedOperationException(Msg.code(680)); 407 } 408 409 @Override 410 public boolean isNoTerminologyServer() { 411 return false; 412 } 413 414 @Override 415 public Set<String> getCodeSystemsUsed() { 416 throw new UnsupportedOperationException(Msg.code(681)); 417 } 418 419 @Override 420 public IResourceValidator newValidator() { 421 throw new UnsupportedOperationException(Msg.code(684)); 422 } 423 424 @Override 425 public Map<String, NamingSystem> getNSUrlMap() { 426 throw new UnsupportedOperationException(Msg.code(2111)); 427 } 428 429 @Override 430 public ILoggingService getLogger() { 431 return null; 432 } 433 434 @Override 435 public void setLogger(ILoggingService logger) { 436 throw new UnsupportedOperationException(Msg.code(687)); 437 } 438 439 @Override 440 public boolean supportsSystem(String system) { 441 return myValidationSupportContext.getRootValidationSupport().isCodeSystemSupported(myValidationSupportContext, system); 442 } 443 444 @Override 445 public TranslationServices translator() { 446 throw new UnsupportedOperationException(Msg.code(688)); 447 } 448 449 @Override 450 public ValueSetExpander.ValueSetExpansionOutcome expandVS(ValueSet source, boolean cacheOk, boolean heiarchical, boolean incompleteOk) { 451 return null; 452 } 453 454 @Override 455 public ValidationResult validateCode(ValidationOptions theOptions, String system, String version, String code, String display) { 456 ConceptValidationOptions validationOptions = convertConceptValidationOptions(theOptions); 457 return doValidation(null, validationOptions, system, code, display); 458 } 459 460 @Override 461 public ValidationResult validateCode(ValidationOptions theOptions, String theSystem, String version, String theCode, String display, ValueSet theValueSet) { 462 IBaseResource convertedVs = null; 463 464 try { 465 if (theValueSet != null) { 466 convertedVs = myVersionCanonicalizer.valueSetFromValidatorCanonical(theValueSet); 467 } 468 } catch (FHIRException e) { 469 throw new InternalErrorException(Msg.code(689) + e); 470 } 471 472 ConceptValidationOptions validationOptions = convertConceptValidationOptions(theOptions); 473 474 return doValidation(convertedVs, validationOptions, theSystem, theCode, display); 475 } 476 477 @Override 478 public ValidationResult validateCode(ValidationOptions theOptions, String code, org.hl7.fhir.r5.model.ValueSet theValueSet) { 479 IBaseResource convertedVs = null; 480 try { 481 if (theValueSet != null) { 482 convertedVs = myVersionCanonicalizer.valueSetFromValidatorCanonical(theValueSet); 483 } 484 } catch (FHIRException e) { 485 throw new InternalErrorException(Msg.code(690) + e); 486 } 487 488 ConceptValidationOptions validationOptions = convertConceptValidationOptions(theOptions).setInferSystem(true); 489 490 return doValidation(convertedVs, validationOptions, null, code, null); 491 } 492 493 @Override 494 public ValidationResult validateCode(ValidationOptions theOptions, org.hl7.fhir.r5.model.Coding theCoding, org.hl7.fhir.r5.model.ValueSet theValueSet) { 495 IBaseResource convertedVs = null; 496 497 try { 498 if (theValueSet != null) { 499 convertedVs = myVersionCanonicalizer.valueSetFromValidatorCanonical(theValueSet); 500 } 501 } catch (FHIRException e) { 502 throw new InternalErrorException(Msg.code(691) + e); 503 } 504 505 ConceptValidationOptions validationOptions = convertConceptValidationOptions(theOptions); 506 String system = theCoding.getSystem(); 507 String code = theCoding.getCode(); 508 String display = theCoding.getDisplay(); 509 510 return doValidation(convertedVs, validationOptions, system, code, display); 511 } 512 513 @Override 514 public ValidationResult validateCode(ValidationOptions options, Coding code, ValueSet vs, ValidationContextCarrier ctxt) { 515 return validateCode(options, code, vs); 516 } 517 518 @Override 519 public void validateCodeBatch(ValidationOptions options, List<? extends CodingValidationRequest> codes, ValueSet vs) { 520 for (CodingValidationRequest next : codes) { 521 ValidationResult outcome = validateCode(options, next.getCoding(), vs); 522 next.setResult(outcome); 523 } 524 } 525 526 @Nonnull 527 private ValidationResult doValidation(IBaseResource theValueSet, ConceptValidationOptions theValidationOptions, String theSystem, String theCode, String theDisplay) { 528 IValidationSupport.CodeValidationResult result; 529 if (theValueSet != null) { 530 result = myValidationSupportContext.getRootValidationSupport().validateCodeInValueSet(myValidationSupportContext, theValidationOptions, theSystem, theCode, theDisplay, theValueSet); 531 } else { 532 result = myValidationSupportContext.getRootValidationSupport().validateCode(myValidationSupportContext, theValidationOptions, theSystem, theCode, theDisplay, null); 533 } 534 return convertValidationResult(theSystem, result); 535 } 536 537 @Override 538 public ValidationResult validateCode(ValidationOptions theOptions, org.hl7.fhir.r5.model.CodeableConcept code, org.hl7.fhir.r5.model.ValueSet theVs) { 539 List<ValidationResult> validationResultsOk = new ArrayList<>(); 540 for (Coding next : code.getCoding()) { 541 ValidationResult retVal = validateCode(theOptions, next, theVs); 542 if (retVal.isOk()) { 543 if (myValidationSupportContext.isEnabledValidationForCodingsLogicalAnd()) { 544 validationResultsOk.add(retVal); 545 } else { 546 return retVal; 547 } 548 } 549 } 550 551 if (code.getCoding().size() > 0 && validationResultsOk.size() == code.getCoding().size()) { 552 return validationResultsOk.get(0); 553 } 554 555 return new ValidationResult(ValidationMessage.IssueSeverity.ERROR, null); 556 } 557 558 public void invalidateCaches() { 559 myFetchResourceCache.invalidateAll(); 560 } 561 562 @Override 563 public <T extends Resource> List<T> fetchResourcesByType(Class<T> theClass) { 564 if (theClass.equals(StructureDefinition.class)) { 565 return (List<T>) allStructures(); 566 } 567 throw new UnsupportedOperationException(Msg.code(650) + "Unable to fetch resources of type: " + theClass); 568 } 569 570 private static class ResourceKey { 571 private final int myHashCode; 572 private final String myResourceName; 573 private final String myUri; 574 575 private ResourceKey(String theResourceName, String theUri) { 576 myResourceName = theResourceName; 577 myUri = theUri; 578 myHashCode = new HashCodeBuilder(17, 37) 579 .append(myResourceName) 580 .append(myUri) 581 .toHashCode(); 582 } 583 584 @Override 585 public boolean equals(Object theO) { 586 if (this == theO) { 587 return true; 588 } 589 590 if (theO == null || getClass() != theO.getClass()) { 591 return false; 592 } 593 594 ResourceKey that = (ResourceKey) theO; 595 596 return new EqualsBuilder() 597 .append(myResourceName, that.myResourceName) 598 .append(myUri, that.myUri) 599 .isEquals(); 600 } 601 602 public String getResourceName() { 603 return myResourceName; 604 } 605 606 public String getUri() { 607 return myUri; 608 } 609 610 @Override 611 public int hashCode() { 612 return myHashCode; 613 } 614 } 615 616 public static ConceptValidationOptions convertConceptValidationOptions(ValidationOptions theOptions) { 617 ConceptValidationOptions retVal = new ConceptValidationOptions(); 618 if (theOptions.isGuessSystem()) { 619 retVal = retVal.setInferSystem(true); 620 } 621 return retVal; 622 } 623 624 @Nonnull 625 public static VersionSpecificWorkerContextWrapper newVersionSpecificWorkerContextWrapper(IValidationSupport theValidationSupport) { 626 VersionCanonicalizer versionCanonicalizer = new VersionCanonicalizer(theValidationSupport.getFhirContext()); 627 return new VersionSpecificWorkerContextWrapper(new ValidationSupportContext(theValidationSupport), versionCanonicalizer); 628 } 629} 630 631 632