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