001/* 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2024 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.context.support; 021 022import ca.uhn.fhir.context.FhirContext; 023import ca.uhn.fhir.i18n.Msg; 024import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; 025import ca.uhn.fhir.util.ParametersUtil; 026import ca.uhn.fhir.util.UrlUtil; 027import jakarta.annotation.Nonnull; 028import jakarta.annotation.Nullable; 029import org.apache.commons.lang3.Validate; 030import org.apache.commons.lang3.builder.EqualsBuilder; 031import org.apache.commons.lang3.builder.HashCodeBuilder; 032import org.apache.commons.lang3.builder.ToStringBuilder; 033import org.hl7.fhir.instance.model.api.IBase; 034import org.hl7.fhir.instance.model.api.IBaseCoding; 035import org.hl7.fhir.instance.model.api.IBaseParameters; 036import org.hl7.fhir.instance.model.api.IBaseResource; 037import org.hl7.fhir.instance.model.api.IIdType; 038import org.hl7.fhir.instance.model.api.IPrimitiveType; 039 040import java.util.ArrayList; 041import java.util.Arrays; 042import java.util.Collections; 043import java.util.List; 044import java.util.Set; 045import java.util.function.Supplier; 046import java.util.stream.Collectors; 047 048import static org.apache.commons.lang3.StringUtils.defaultString; 049import static org.apache.commons.lang3.StringUtils.isNotBlank; 050 051/** 052 * This interface is a version-independent representation of the 053 * various functions that can be provided by validation and terminology 054 * services. 055 * <p> 056 * This interface is invoked directly by internal parts of the HAPI FHIR API, including the 057 * Validator and the FHIRPath evaluator. It is used to supply artifacts required for validation 058 * (e.g. StructureDefinition resources, ValueSet resources, etc.) and also to provide 059 * terminology functions such as code validation, ValueSet expansion, etc. 060 * </p> 061 * <p> 062 * Implementations are not required to implement all of the functions 063 * in this interface; in fact it is expected that most won't. Any 064 * methods which are not implemented may simply return <code>null</code> 065 * and calling code is expected to be able to handle this. Generally, a 066 * series of implementations of this interface will be joined together using 067 * the 068 * <a href="https://hapifhir.io/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.html">ValidationSupportChain</a> 069 * class. 070 * </p> 071 * <p> 072 * See <a href="https://hapifhir.io/hapi-fhir/docs/validation/validation_support_modules.html">Validation Support Modules</a> 073 * for information on how to assemble and configure implementations of this interface. See also 074 * the <code>org.hl7.fhir.common.hapi.validation.support</code> 075 * <a href="./package-summary.html">package summary</a> 076 * in the <code>hapi-fhir-validation</code> module for many implementations of this interface. 077 * </p> 078 * 079 * @since 5.0.0 080 */ 081public interface IValidationSupport { 082 String URL_PREFIX_VALUE_SET = "http://hl7.org/fhir/ValueSet/"; 083 084 /** 085 * Expands the given portion of a ValueSet 086 * 087 * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to 088 * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. 089 * @param theExpansionOptions If provided (can be <code>null</code>), contains options controlling the expansion 090 * @param theValueSetToExpand The valueset that should be expanded 091 * @return The expansion, or null 092 */ 093 @Nullable 094 default ValueSetExpansionOutcome expandValueSet( 095 ValidationSupportContext theValidationSupportContext, 096 @Nullable ValueSetExpansionOptions theExpansionOptions, 097 @Nonnull IBaseResource theValueSetToExpand) { 098 return null; 099 } 100 101 /** 102 * Expands the given portion of a ValueSet by canonical URL. 103 * 104 * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to 105 * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. 106 * @param theExpansionOptions If provided (can be <code>null</code>), contains options controlling the expansion 107 * @param theValueSetUrlToExpand The valueset that should be expanded 108 * @return The expansion, or null 109 * @throws ResourceNotFoundException If no ValueSet can be found with the given URL 110 * @since 6.0.0 111 */ 112 @Nullable 113 default ValueSetExpansionOutcome expandValueSet( 114 ValidationSupportContext theValidationSupportContext, 115 @Nullable ValueSetExpansionOptions theExpansionOptions, 116 @Nonnull String theValueSetUrlToExpand) 117 throws ResourceNotFoundException { 118 Validate.notBlank(theValueSetUrlToExpand, "theValueSetUrlToExpand must not be null or blank"); 119 IBaseResource valueSet = fetchValueSet(theValueSetUrlToExpand); 120 if (valueSet == null) { 121 throw new ResourceNotFoundException( 122 Msg.code(2024) + "Unknown ValueSet: " + UrlUtil.escapeUrlParam(theValueSetUrlToExpand)); 123 } 124 return expandValueSet(theValidationSupportContext, theExpansionOptions, valueSet); 125 } 126 127 /** 128 * Load and return all conformance resources associated with this 129 * validation support module. This method may return null if it doesn't 130 * make sense for a given module. 131 */ 132 @Nullable 133 default List<IBaseResource> fetchAllConformanceResources() { 134 return null; 135 } 136 137 /** 138 * Load and return all possible search parameters 139 * 140 * @since 6.6.0 141 */ 142 @Nullable 143 default <T extends IBaseResource> List<T> fetchAllSearchParameters() { 144 return null; 145 } 146 147 /** 148 * Load and return all possible structure definitions 149 */ 150 @Nullable 151 default <T extends IBaseResource> List<T> fetchAllStructureDefinitions() { 152 return null; 153 } 154 155 /** 156 * Load and return all possible structure definitions aside from resource definitions themselves 157 */ 158 @Nullable 159 default <T extends IBaseResource> List<T> fetchAllNonBaseStructureDefinitions() { 160 List<T> retVal = fetchAllStructureDefinitions(); 161 if (retVal != null) { 162 List<T> newList = new ArrayList<>(retVal.size()); 163 for (T next : retVal) { 164 String url = defaultString(getFhirContext().newTerser().getSinglePrimitiveValueOrNull(next, "url")); 165 if (url.startsWith("http://hl7.org/fhir/StructureDefinition/")) { 166 String lastPart = url.substring("http://hl7.org/fhir/StructureDefinition/".length()); 167 if (getFhirContext().getResourceTypes().contains(lastPart)) { 168 continue; 169 } 170 } 171 172 newList.add(next); 173 } 174 175 retVal = newList; 176 } 177 178 return retVal; 179 } 180 181 /** 182 * Fetch a code system by ID 183 * 184 * @param theSystem The code system 185 * @return The valueset (must not be null, but can be an empty ValueSet) 186 */ 187 @Nullable 188 default IBaseResource fetchCodeSystem(String theSystem) { 189 return null; 190 } 191 192 /** 193 * Loads a resource needed by the validation (a StructureDefinition, or a 194 * ValueSet) 195 * 196 * <p> 197 * Note: Since 5.3.0, {@literal theClass} can be {@literal null} 198 * </p> 199 * 200 * @param theClass The type of the resource to load, or <code>null</code> to return any resource with the given canonical URI 201 * @param theUri The resource URI 202 * @return Returns the resource, or <code>null</code> if no resource with the 203 * given URI can be found 204 */ 205 @SuppressWarnings("unchecked") 206 @Nullable 207 default <T extends IBaseResource> T fetchResource(@Nullable Class<T> theClass, String theUri) { 208 Validate.notBlank(theUri, "theUri must not be null or blank"); 209 210 if (theClass == null) { 211 Supplier<IBaseResource>[] sources = new Supplier[] { 212 () -> fetchStructureDefinition(theUri), () -> fetchValueSet(theUri), () -> fetchCodeSystem(theUri) 213 }; 214 return (T) Arrays.stream(sources) 215 .map(t -> t.get()) 216 .filter(t -> t != null) 217 .findFirst() 218 .orElse(null); 219 } 220 221 switch (getFhirContext().getResourceType(theClass)) { 222 case "StructureDefinition": 223 return theClass.cast(fetchStructureDefinition(theUri)); 224 case "ValueSet": 225 return theClass.cast(fetchValueSet(theUri)); 226 case "CodeSystem": 227 return theClass.cast(fetchCodeSystem(theUri)); 228 } 229 230 if (theUri.startsWith(URL_PREFIX_VALUE_SET)) { 231 return theClass.cast(fetchValueSet(theUri)); 232 } 233 234 return null; 235 } 236 237 @Nullable 238 default IBaseResource fetchStructureDefinition(String theUrl) { 239 return null; 240 } 241 242 /** 243 * Returns <code>true</code> if codes in the given code system can be expanded 244 * or validated 245 * 246 * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to 247 * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. 248 * @param theSystem The URI for the code system, e.g. <code>"http://loinc.org"</code> 249 * @return Returns <code>true</code> if codes in the given code system can be 250 * validated 251 */ 252 default boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) { 253 return false; 254 } 255 256 /** 257 * Returns <code>true</code> if a Remote Terminology Service is currently configured 258 * 259 * @return Returns <code>true</code> if a Remote Terminology Service is currently configured 260 */ 261 default boolean isRemoteTerminologyServiceConfigured() { 262 return false; 263 } 264 265 /** 266 * Fetch the given ValueSet by URL, or returns null if one can't be found for the given URL 267 */ 268 @Nullable 269 default IBaseResource fetchValueSet(String theValueSetUrl) { 270 return null; 271 } 272 273 /** 274 * Fetch the given binary data by key. 275 * 276 * @param binaryKey 277 * @return 278 */ 279 default byte[] fetchBinary(String binaryKey) { 280 return null; 281 } 282 283 /** 284 * Validates that the given code exists and if possible returns a display 285 * name. This method is called to check codes which are found in "example" 286 * binding fields (e.g. <code>Observation.code</code>) in the default profile. 287 * 288 * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to 289 * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. 290 * @param theOptions Provides options controlling the validation 291 * @param theCodeSystem The code system, e.g. "<code>http://loinc.org</code>" 292 * @param theCode The code, e.g. "<code>1234-5</code>" 293 * @param theDisplay The display name, if it should also be validated 294 * @return Returns a validation result object 295 */ 296 @Nullable 297 default CodeValidationResult validateCode( 298 ValidationSupportContext theValidationSupportContext, 299 ConceptValidationOptions theOptions, 300 String theCodeSystem, 301 String theCode, 302 String theDisplay, 303 String theValueSetUrl) { 304 return null; 305 } 306 307 /** 308 * Validates that the given code exists and if possible returns a display 309 * name. This method is called to check codes which are found in "example" 310 * binding fields (e.g. <code>Observation.code</code>) in the default profile. 311 * 312 * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to 313 * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. 314 * @param theCodeSystem The code system, e.g. "<code>http://loinc.org</code>" 315 * @param theCode The code, e.g. "<code>1234-5</code>" 316 * @param theDisplay The display name, if it should also be validated 317 * @param theValueSet The ValueSet to validate against. Must not be null, and must be a ValueSet resource. 318 * @return Returns a validation result object, or <code>null</code> if this validation support module can not handle this kind of request 319 */ 320 @Nullable 321 default CodeValidationResult validateCodeInValueSet( 322 ValidationSupportContext theValidationSupportContext, 323 ConceptValidationOptions theOptions, 324 String theCodeSystem, 325 String theCode, 326 String theDisplay, 327 @Nonnull IBaseResource theValueSet) { 328 return null; 329 } 330 331 /** 332 * Look up a code using the system and code value. 333 * @deprecated This method has been deprecated in HAPI FHIR 7.0.0. Use {@link IValidationSupport#lookupCode(ValidationSupportContext, LookupCodeRequest)} instead. 334 * 335 * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to 336 * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. 337 * @param theSystem The CodeSystem URL 338 * @param theCode The code 339 * @param theDisplayLanguage Used to filter out the designation by the display language. To return all designation, set this value to <code>null</code>. 340 */ 341 @Deprecated 342 @Nullable 343 default LookupCodeResult lookupCode( 344 ValidationSupportContext theValidationSupportContext, 345 String theSystem, 346 String theCode, 347 String theDisplayLanguage) { 348 return null; 349 } 350 351 /** 352 * Look up a code using the system and code value 353 * @deprecated This method has been deprecated in HAPI FHIR 7.0.0. Use {@link IValidationSupport#lookupCode(ValidationSupportContext, LookupCodeRequest)} instead. 354 * 355 * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to 356 * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. 357 * @param theSystem The CodeSystem URL 358 * @param theCode The code 359 */ 360 @Deprecated 361 @Nullable 362 default LookupCodeResult lookupCode( 363 ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) { 364 return lookupCode(theValidationSupportContext, theSystem, theCode, null); 365 } 366 367 /** 368 * Look up a code using the system, code and other parameters captured in {@link LookupCodeRequest}. 369 * @since 7.0.0 370 * 371 * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to 372 * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. 373 * @param theLookupCodeRequest The parameters used to perform the lookup, including system and code. 374 */ 375 @Nullable 376 default LookupCodeResult lookupCode( 377 ValidationSupportContext theValidationSupportContext, @Nonnull LookupCodeRequest theLookupCodeRequest) { 378 // TODO: can change to return null once the deprecated methods are removed 379 return lookupCode( 380 theValidationSupportContext, 381 theLookupCodeRequest.getSystem(), 382 theLookupCodeRequest.getCode(), 383 theLookupCodeRequest.getDisplayLanguage()); 384 } 385 386 /** 387 * Returns <code>true</code> if the given ValueSet can be validated by the given 388 * validation support module 389 * 390 * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to 391 * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. 392 * @param theValueSetUrl The ValueSet canonical URL 393 */ 394 default boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) { 395 return false; 396 } 397 398 /** 399 * Generate a snapshot from the given differential profile. 400 * 401 * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to 402 * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. 403 * @return Returns null if this module does not know how to handle this request 404 */ 405 @Nullable 406 default IBaseResource generateSnapshot( 407 ValidationSupportContext theValidationSupportContext, 408 IBaseResource theInput, 409 String theUrl, 410 String theWebUrl, 411 String theProfileName) { 412 return null; 413 } 414 415 /** 416 * Returns the FHIR Context associated with this module 417 */ 418 FhirContext getFhirContext(); 419 420 /** 421 * This method clears any temporary caches within the validation support. It is mainly intended for unit tests, 422 * but could be used in non-test scenarios as well. 423 */ 424 default void invalidateCaches() { 425 // nothing 426 } 427 428 /** 429 * Attempt to translate the given concept from one code system to another 430 */ 431 @Nullable 432 default TranslateConceptResults translateConcept(TranslateCodeRequest theRequest) { 433 return null; 434 } 435 436 /** 437 * This field is used by the Terminology Troubleshooting Log to log which validation support module was used for the operation being logged. 438 */ 439 default String getName() { 440 return "Unknown " + getFhirContext().getVersion().getVersion() + " Validation Support"; 441 } 442 443 enum IssueSeverity { 444 /** 445 * The issue caused the action to fail, and no further checking could be performed. 446 */ 447 FATAL, 448 /** 449 * The issue is sufficiently important to cause the action to fail. 450 */ 451 ERROR, 452 /** 453 * The issue is not important enough to cause the action to fail, but may cause it to be performed suboptimally or in a way that is not as desired. 454 */ 455 WARNING, 456 /** 457 * The issue has no relation to the degree of success of the action. 458 */ 459 INFORMATION 460 } 461 462 enum CodeValidationIssueCode { 463 NOT_FOUND, 464 CODE_INVALID, 465 INVALID, 466 OTHER 467 } 468 469 enum CodeValidationIssueCoding { 470 VS_INVALID, 471 NOT_FOUND, 472 NOT_IN_VS, 473 474 INVALID_CODE, 475 INVALID_DISPLAY, 476 OTHER 477 } 478 479 class CodeValidationIssue { 480 481 private final String myMessage; 482 private final IssueSeverity mySeverity; 483 private final CodeValidationIssueCode myCode; 484 private final CodeValidationIssueCoding myCoding; 485 486 public CodeValidationIssue( 487 String theMessage, 488 IssueSeverity mySeverity, 489 CodeValidationIssueCode theCode, 490 CodeValidationIssueCoding theCoding) { 491 this.myMessage = theMessage; 492 this.mySeverity = mySeverity; 493 this.myCode = theCode; 494 this.myCoding = theCoding; 495 } 496 497 public String getMessage() { 498 return myMessage; 499 } 500 501 public IssueSeverity getSeverity() { 502 return mySeverity; 503 } 504 505 public CodeValidationIssueCode getCode() { 506 return myCode; 507 } 508 509 public CodeValidationIssueCoding getCoding() { 510 return myCoding; 511 } 512 } 513 514 class ConceptDesignation { 515 516 private String myLanguage; 517 private String myUseSystem; 518 private String myUseCode; 519 private String myUseDisplay; 520 private String myValue; 521 522 public String getLanguage() { 523 return myLanguage; 524 } 525 526 public ConceptDesignation setLanguage(String theLanguage) { 527 myLanguage = theLanguage; 528 return this; 529 } 530 531 public String getUseSystem() { 532 return myUseSystem; 533 } 534 535 public ConceptDesignation setUseSystem(String theUseSystem) { 536 myUseSystem = theUseSystem; 537 return this; 538 } 539 540 public String getUseCode() { 541 return myUseCode; 542 } 543 544 public ConceptDesignation setUseCode(String theUseCode) { 545 myUseCode = theUseCode; 546 return this; 547 } 548 549 public String getUseDisplay() { 550 return myUseDisplay; 551 } 552 553 public ConceptDesignation setUseDisplay(String theUseDisplay) { 554 myUseDisplay = theUseDisplay; 555 return this; 556 } 557 558 public String getValue() { 559 return myValue; 560 } 561 562 public ConceptDesignation setValue(String theValue) { 563 myValue = theValue; 564 return this; 565 } 566 } 567 568 abstract class BaseConceptProperty { 569 private final String myPropertyName; 570 571 /** 572 * Constructor 573 */ 574 protected BaseConceptProperty(String thePropertyName) { 575 myPropertyName = thePropertyName; 576 } 577 578 public String getPropertyName() { 579 return myPropertyName; 580 } 581 582 public abstract String getType(); 583 } 584 585 // The reason these cannot be declared within an enum is because a Remote Terminology Service 586 // can support arbitrary types. We do not restrict against the types in the spec. 587 // Some of the types in the spec are not yet implemented as well. 588 // @see https://github.com/hapifhir/hapi-fhir/issues/5700 589 String TYPE_STRING = "string"; 590 String TYPE_CODING = "Coding"; 591 String TYPE_GROUP = "group"; 592 593 class StringConceptProperty extends BaseConceptProperty { 594 private final String myValue; 595 596 /** 597 * Constructor 598 * 599 * @param theName The name 600 */ 601 public StringConceptProperty(String theName, String theValue) { 602 super(theName); 603 myValue = theValue; 604 } 605 606 public String getValue() { 607 return myValue; 608 } 609 610 public String getType() { 611 return TYPE_STRING; 612 } 613 } 614 615 class CodingConceptProperty extends BaseConceptProperty { 616 private final String myCode; 617 private final String myCodeSystem; 618 private final String myDisplay; 619 620 /** 621 * Constructor 622 * 623 * @param theName The name 624 */ 625 public CodingConceptProperty(String theName, String theCodeSystem, String theCode, String theDisplay) { 626 super(theName); 627 myCodeSystem = theCodeSystem; 628 myCode = theCode; 629 myDisplay = theDisplay; 630 } 631 632 public String getCode() { 633 return myCode; 634 } 635 636 public String getCodeSystem() { 637 return myCodeSystem; 638 } 639 640 public String getDisplay() { 641 return myDisplay; 642 } 643 644 public String getType() { 645 return TYPE_CODING; 646 } 647 } 648 649 class GroupConceptProperty extends BaseConceptProperty { 650 public GroupConceptProperty(String thePropertyName) { 651 super(thePropertyName); 652 } 653 654 private List<BaseConceptProperty> subProperties; 655 656 public BaseConceptProperty addSubProperty(BaseConceptProperty theProperty) { 657 if (subProperties == null) { 658 subProperties = new ArrayList<>(); 659 } 660 subProperties.add(theProperty); 661 return this; 662 } 663 664 public List<BaseConceptProperty> getSubProperties() { 665 return subProperties != null ? subProperties : Collections.emptyList(); 666 } 667 668 @Override 669 public String getType() { 670 return TYPE_GROUP; 671 } 672 } 673 674 class CodeValidationResult { 675 public static final String SOURCE_DETAILS = "sourceDetails"; 676 public static final String RESULT = "result"; 677 public static final String MESSAGE = "message"; 678 public static final String DISPLAY = "display"; 679 680 private String myCode; 681 private String myMessage; 682 private IssueSeverity mySeverity; 683 private String myCodeSystemName; 684 private String myCodeSystemVersion; 685 private List<BaseConceptProperty> myProperties; 686 private String myDisplay; 687 private String mySourceDetails; 688 689 private List<CodeValidationIssue> myCodeValidationIssues; 690 691 public CodeValidationResult() { 692 super(); 693 } 694 695 /** 696 * This field may contain information about what the source of the 697 * validation information was. 698 */ 699 public String getSourceDetails() { 700 return mySourceDetails; 701 } 702 703 /** 704 * This field may contain information about what the source of the 705 * validation information was. 706 */ 707 public CodeValidationResult setSourceDetails(String theSourceDetails) { 708 mySourceDetails = theSourceDetails; 709 return this; 710 } 711 712 public String getDisplay() { 713 return myDisplay; 714 } 715 716 public CodeValidationResult setDisplay(String theDisplay) { 717 myDisplay = theDisplay; 718 return this; 719 } 720 721 public String getCode() { 722 return myCode; 723 } 724 725 public CodeValidationResult setCode(String theCode) { 726 myCode = theCode; 727 return this; 728 } 729 730 String getCodeSystemName() { 731 return myCodeSystemName; 732 } 733 734 public CodeValidationResult setCodeSystemName(String theCodeSystemName) { 735 myCodeSystemName = theCodeSystemName; 736 return this; 737 } 738 739 public String getCodeSystemVersion() { 740 return myCodeSystemVersion; 741 } 742 743 public CodeValidationResult setCodeSystemVersion(String theCodeSystemVersion) { 744 myCodeSystemVersion = theCodeSystemVersion; 745 return this; 746 } 747 748 public String getMessage() { 749 return myMessage; 750 } 751 752 public CodeValidationResult setMessage(String theMessage) { 753 myMessage = theMessage; 754 return this; 755 } 756 757 public List<BaseConceptProperty> getProperties() { 758 return myProperties; 759 } 760 761 public void setProperties(List<BaseConceptProperty> theProperties) { 762 myProperties = theProperties; 763 } 764 765 public IssueSeverity getSeverity() { 766 return mySeverity; 767 } 768 769 public CodeValidationResult setSeverity(IssueSeverity theSeverity) { 770 mySeverity = theSeverity; 771 return this; 772 } 773 774 public List<CodeValidationIssue> getCodeValidationIssues() { 775 if (myCodeValidationIssues == null) { 776 myCodeValidationIssues = new ArrayList<>(); 777 } 778 return myCodeValidationIssues; 779 } 780 781 public CodeValidationResult setCodeValidationIssues(List<CodeValidationIssue> theCodeValidationIssues) { 782 myCodeValidationIssues = new ArrayList<>(theCodeValidationIssues); 783 return this; 784 } 785 786 public CodeValidationResult addCodeValidationIssue(CodeValidationIssue theCodeValidationIssue) { 787 getCodeValidationIssues().add(theCodeValidationIssue); 788 return this; 789 } 790 791 public boolean isOk() { 792 return isNotBlank(myCode); 793 } 794 795 public LookupCodeResult asLookupCodeResult(String theSearchedForSystem, String theSearchedForCode) { 796 LookupCodeResult retVal = new LookupCodeResult(); 797 retVal.setSearchedForSystem(theSearchedForSystem); 798 retVal.setSearchedForCode(theSearchedForCode); 799 if (isOk()) { 800 retVal.setFound(true); 801 retVal.setCodeDisplay(myDisplay); 802 retVal.setCodeSystemDisplayName(getCodeSystemName()); 803 retVal.setCodeSystemVersion(getCodeSystemVersion()); 804 } 805 return retVal; 806 } 807 808 /** 809 * Convenience method that returns {@link #getSeverity()} as an IssueSeverity code string 810 */ 811 public String getSeverityCode() { 812 String retVal = null; 813 if (getSeverity() != null) { 814 retVal = getSeverity().name().toLowerCase(); 815 } 816 return retVal; 817 } 818 819 /** 820 * Sets an issue severity as a string code. Value must be the name of 821 * one of the enum values in {@link IssueSeverity}. Value is case-insensitive. 822 */ 823 public CodeValidationResult setSeverityCode(@Nonnull String theIssueSeverity) { 824 setSeverity(IssueSeverity.valueOf(theIssueSeverity.toUpperCase())); 825 return this; 826 } 827 828 public IBaseParameters toParameters(FhirContext theContext) { 829 IBaseParameters retVal = ParametersUtil.newInstance(theContext); 830 831 ParametersUtil.addParameterToParametersBoolean(theContext, retVal, RESULT, isOk()); 832 if (isNotBlank(getMessage())) { 833 ParametersUtil.addParameterToParametersString(theContext, retVal, MESSAGE, getMessage()); 834 } 835 if (isNotBlank(getDisplay())) { 836 ParametersUtil.addParameterToParametersString(theContext, retVal, DISPLAY, getDisplay()); 837 } 838 if (isNotBlank(getSourceDetails())) { 839 ParametersUtil.addParameterToParametersString(theContext, retVal, SOURCE_DETAILS, getSourceDetails()); 840 } 841 842 return retVal; 843 } 844 } 845 846 class ValueSetExpansionOutcome { 847 848 private final IBaseResource myValueSet; 849 private final String myError; 850 851 private boolean myErrorIsFromServer; 852 853 public ValueSetExpansionOutcome(String theError, boolean theErrorIsFromServer) { 854 myValueSet = null; 855 myError = theError; 856 myErrorIsFromServer = theErrorIsFromServer; 857 } 858 859 public ValueSetExpansionOutcome(IBaseResource theValueSet) { 860 myValueSet = theValueSet; 861 myError = null; 862 myErrorIsFromServer = false; 863 } 864 865 public String getError() { 866 return myError; 867 } 868 869 public IBaseResource getValueSet() { 870 return myValueSet; 871 } 872 873 public boolean getErrorIsFromServer() { 874 return myErrorIsFromServer; 875 } 876 } 877 878 class LookupCodeResult { 879 880 private String myCodeDisplay; 881 private boolean myCodeIsAbstract; 882 private String myCodeSystemDisplayName; 883 private String myCodeSystemVersion; 884 private boolean myFound; 885 private String mySearchedForCode; 886 private String mySearchedForSystem; 887 private List<BaseConceptProperty> myProperties; 888 private List<ConceptDesignation> myDesignations; 889 private String myErrorMessage; 890 891 /** 892 * Constructor 893 */ 894 public LookupCodeResult() { 895 super(); 896 } 897 898 public List<BaseConceptProperty> getProperties() { 899 if (myProperties == null) { 900 myProperties = new ArrayList<>(); 901 } 902 return myProperties; 903 } 904 905 public void setProperties(List<BaseConceptProperty> theProperties) { 906 myProperties = theProperties; 907 } 908 909 @Nonnull 910 public List<ConceptDesignation> getDesignations() { 911 if (myDesignations == null) { 912 myDesignations = new ArrayList<>(); 913 } 914 return myDesignations; 915 } 916 917 public String getCodeDisplay() { 918 return myCodeDisplay; 919 } 920 921 public void setCodeDisplay(String theCodeDisplay) { 922 myCodeDisplay = theCodeDisplay; 923 } 924 925 public String getCodeSystemDisplayName() { 926 return myCodeSystemDisplayName; 927 } 928 929 public void setCodeSystemDisplayName(String theCodeSystemDisplayName) { 930 myCodeSystemDisplayName = theCodeSystemDisplayName; 931 } 932 933 public String getCodeSystemVersion() { 934 return myCodeSystemVersion; 935 } 936 937 public void setCodeSystemVersion(String theCodeSystemVersion) { 938 myCodeSystemVersion = theCodeSystemVersion; 939 } 940 941 public String getSearchedForCode() { 942 return mySearchedForCode; 943 } 944 945 public LookupCodeResult setSearchedForCode(String theSearchedForCode) { 946 mySearchedForCode = theSearchedForCode; 947 return this; 948 } 949 950 public String getSearchedForSystem() { 951 return mySearchedForSystem; 952 } 953 954 public LookupCodeResult setSearchedForSystem(String theSearchedForSystem) { 955 mySearchedForSystem = theSearchedForSystem; 956 return this; 957 } 958 959 public boolean isCodeIsAbstract() { 960 return myCodeIsAbstract; 961 } 962 963 public void setCodeIsAbstract(boolean theCodeIsAbstract) { 964 myCodeIsAbstract = theCodeIsAbstract; 965 } 966 967 public boolean isFound() { 968 return myFound; 969 } 970 971 public LookupCodeResult setFound(boolean theFound) { 972 myFound = theFound; 973 return this; 974 } 975 976 public void throwNotFoundIfAppropriate() { 977 if (isFound() == false) { 978 throw new ResourceNotFoundException(Msg.code(1738) + "Unable to find code[" + getSearchedForCode() 979 + "] in system[" + getSearchedForSystem() + "]"); 980 } 981 } 982 983 /** 984 * Converts the current LookupCodeResult instance into a IBaseParameters instance which is returned 985 * to the client of the $lookup operation. 986 * @param theContext the FHIR context used for running the operation 987 * @param thePropertyNamesToFilter the properties which are passed as parameter to filter the result. 988 * @return the output for the lookup operation. 989 */ 990 public IBaseParameters toParameters( 991 FhirContext theContext, List<? extends IPrimitiveType<String>> thePropertyNamesToFilter) { 992 993 IBaseParameters retVal = ParametersUtil.newInstance(theContext); 994 if (isNotBlank(getCodeSystemDisplayName())) { 995 ParametersUtil.addParameterToParametersString(theContext, retVal, "name", getCodeSystemDisplayName()); 996 } 997 if (isNotBlank(getCodeSystemVersion())) { 998 ParametersUtil.addParameterToParametersString(theContext, retVal, "version", getCodeSystemVersion()); 999 } 1000 ParametersUtil.addParameterToParametersString(theContext, retVal, "display", getCodeDisplay()); 1001 ParametersUtil.addParameterToParametersBoolean(theContext, retVal, "abstract", isCodeIsAbstract()); 1002 1003 if (myProperties != null) { 1004 1005 final List<BaseConceptProperty> propertiesToReturn; 1006 if (thePropertyNamesToFilter != null && !thePropertyNamesToFilter.isEmpty()) { 1007 // TODO MM: The logic to filter of properties could actually be moved to the lookupCode provider. 1008 // That is where the rest of the lookupCode input parameter handling is done. 1009 // This was left as is for now but can be done with next opportunity. 1010 Set<String> propertyNameList = thePropertyNamesToFilter.stream() 1011 .map(IPrimitiveType::getValueAsString) 1012 .collect(Collectors.toSet()); 1013 propertiesToReturn = myProperties.stream() 1014 .filter(p -> propertyNameList.contains(p.getPropertyName())) 1015 .collect(Collectors.toList()); 1016 } else { 1017 propertiesToReturn = myProperties; 1018 } 1019 1020 for (BaseConceptProperty next : propertiesToReturn) { 1021 IBase property = ParametersUtil.addParameterToParameters(theContext, retVal, "property"); 1022 populateProperty(theContext, property, next); 1023 } 1024 } 1025 1026 if (myDesignations != null) { 1027 for (ConceptDesignation next : myDesignations) { 1028 IBase property = ParametersUtil.addParameterToParameters(theContext, retVal, "designation"); 1029 ParametersUtil.addPartCode(theContext, property, "language", next.getLanguage()); 1030 ParametersUtil.addPartCoding( 1031 theContext, property, "use", next.getUseSystem(), next.getUseCode(), next.getUseDisplay()); 1032 ParametersUtil.addPartString(theContext, property, "value", next.getValue()); 1033 } 1034 } 1035 1036 return retVal; 1037 } 1038 1039 private void populateProperty( 1040 FhirContext theContext, IBase theProperty, BaseConceptProperty theConceptProperty) { 1041 ParametersUtil.addPartCode(theContext, theProperty, "code", theConceptProperty.getPropertyName()); 1042 String propertyType = theConceptProperty.getType(); 1043 switch (propertyType) { 1044 case TYPE_STRING: 1045 StringConceptProperty stringConceptProperty = (StringConceptProperty) theConceptProperty; 1046 ParametersUtil.addPartString(theContext, theProperty, "value", stringConceptProperty.getValue()); 1047 break; 1048 case TYPE_CODING: 1049 CodingConceptProperty codingConceptProperty = (CodingConceptProperty) theConceptProperty; 1050 ParametersUtil.addPartCoding( 1051 theContext, 1052 theProperty, 1053 "value", 1054 codingConceptProperty.getCodeSystem(), 1055 codingConceptProperty.getCode(), 1056 codingConceptProperty.getDisplay()); 1057 break; 1058 case TYPE_GROUP: 1059 GroupConceptProperty groupConceptProperty = (GroupConceptProperty) theConceptProperty; 1060 if (groupConceptProperty.getSubProperties().isEmpty()) { 1061 break; 1062 } 1063 groupConceptProperty.getSubProperties().forEach(p -> { 1064 IBase subProperty = ParametersUtil.addPart(theContext, theProperty, "subproperty", null); 1065 populateProperty(theContext, subProperty, p); 1066 }); 1067 break; 1068 default: 1069 throw new IllegalStateException( 1070 Msg.code(1739) + "Don't know how to handle " + theConceptProperty.getClass()); 1071 } 1072 } 1073 1074 public LookupCodeResult setErrorMessage(String theErrorMessage) { 1075 myErrorMessage = theErrorMessage; 1076 return this; 1077 } 1078 1079 public String getErrorMessage() { 1080 return myErrorMessage; 1081 } 1082 1083 public static LookupCodeResult notFound(String theSearchedForSystem, String theSearchedForCode) { 1084 return new LookupCodeResult() 1085 .setFound(false) 1086 .setSearchedForSystem(theSearchedForSystem) 1087 .setSearchedForCode(theSearchedForCode); 1088 } 1089 } 1090 1091 class TranslateCodeRequest { 1092 private final String myTargetSystemUrl; 1093 private final String myConceptMapUrl; 1094 private final String myConceptMapVersion; 1095 private final String mySourceValueSetUrl; 1096 private final String myTargetValueSetUrl; 1097 private final IIdType myResourceId; 1098 private final boolean myReverse; 1099 private List<IBaseCoding> myCodings; 1100 1101 public TranslateCodeRequest(List<IBaseCoding> theCodings, String theTargetSystemUrl) { 1102 myCodings = theCodings; 1103 myTargetSystemUrl = theTargetSystemUrl; 1104 myConceptMapUrl = null; 1105 myConceptMapVersion = null; 1106 mySourceValueSetUrl = null; 1107 myTargetValueSetUrl = null; 1108 myResourceId = null; 1109 myReverse = false; 1110 } 1111 1112 public TranslateCodeRequest( 1113 List<IBaseCoding> theCodings, 1114 String theTargetSystemUrl, 1115 String theConceptMapUrl, 1116 String theConceptMapVersion, 1117 String theSourceValueSetUrl, 1118 String theTargetValueSetUrl, 1119 IIdType theResourceId, 1120 boolean theReverse) { 1121 myCodings = theCodings; 1122 myTargetSystemUrl = theTargetSystemUrl; 1123 myConceptMapUrl = theConceptMapUrl; 1124 myConceptMapVersion = theConceptMapVersion; 1125 mySourceValueSetUrl = theSourceValueSetUrl; 1126 myTargetValueSetUrl = theTargetValueSetUrl; 1127 myResourceId = theResourceId; 1128 myReverse = theReverse; 1129 } 1130 1131 @Override 1132 public boolean equals(Object theO) { 1133 if (this == theO) { 1134 return true; 1135 } 1136 1137 if (theO == null || getClass() != theO.getClass()) { 1138 return false; 1139 } 1140 1141 TranslateCodeRequest that = (TranslateCodeRequest) theO; 1142 1143 return new EqualsBuilder() 1144 .append(myCodings, that.myCodings) 1145 .append(myTargetSystemUrl, that.myTargetSystemUrl) 1146 .append(myConceptMapUrl, that.myConceptMapUrl) 1147 .append(myConceptMapVersion, that.myConceptMapVersion) 1148 .append(mySourceValueSetUrl, that.mySourceValueSetUrl) 1149 .append(myTargetValueSetUrl, that.myTargetValueSetUrl) 1150 .append(myResourceId, that.myResourceId) 1151 .append(myReverse, that.myReverse) 1152 .isEquals(); 1153 } 1154 1155 @Override 1156 public int hashCode() { 1157 return new HashCodeBuilder(17, 37) 1158 .append(myCodings) 1159 .append(myTargetSystemUrl) 1160 .append(myConceptMapUrl) 1161 .append(myConceptMapVersion) 1162 .append(mySourceValueSetUrl) 1163 .append(myTargetValueSetUrl) 1164 .append(myResourceId) 1165 .append(myReverse) 1166 .toHashCode(); 1167 } 1168 1169 public List<IBaseCoding> getCodings() { 1170 return myCodings; 1171 } 1172 1173 public String getTargetSystemUrl() { 1174 return myTargetSystemUrl; 1175 } 1176 1177 public String getConceptMapUrl() { 1178 return myConceptMapUrl; 1179 } 1180 1181 public String getConceptMapVersion() { 1182 return myConceptMapVersion; 1183 } 1184 1185 public String getSourceValueSetUrl() { 1186 return mySourceValueSetUrl; 1187 } 1188 1189 public String getTargetValueSetUrl() { 1190 return myTargetValueSetUrl; 1191 } 1192 1193 public IIdType getResourceId() { 1194 return myResourceId; 1195 } 1196 1197 public boolean isReverse() { 1198 return myReverse; 1199 } 1200 1201 @Override 1202 public String toString() { 1203 return new ToStringBuilder(this) 1204 .append("sourceValueSetUrl", mySourceValueSetUrl) 1205 .append("targetSystemUrl", myTargetSystemUrl) 1206 .append("targetValueSetUrl", myTargetValueSetUrl) 1207 .append("reverse", myReverse) 1208 .toString(); 1209 } 1210 } 1211 1212 /** 1213 * <p 1214 * Warning: This method's behaviour and naming is preserved for backwards compatibility, BUT the actual naming and 1215 * function are not aligned. 1216 * </p 1217 * <p> 1218 * See VersionSpecificWorkerContextWrapper#validateCode in hapi-fhir-validation, and the refer to the values below 1219 * for the behaviour associated with each value. 1220 * </p> 1221 * <p> 1222 * <ul> 1223 * <li>If <code>false</code> (default setting) the validation for codings will return a positive result only if 1224 * ALL codings are valid.</li> 1225 * <li>If <code>true</code> the validation for codings will return a positive result if ANY codings are valid. 1226 * </li> 1227 * </ul> 1228 * </p> 1229 * @return true or false depending on the desired coding validation behaviour. 1230 */ 1231 default boolean isEnabledValidationForCodingsLogicalAnd() { 1232 return false; 1233 } 1234}