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