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 class ConceptDesignation { 463 464 private String myLanguage; 465 private String myUseSystem; 466 private String myUseCode; 467 private String myUseDisplay; 468 private String myValue; 469 470 public String getLanguage() { 471 return myLanguage; 472 } 473 474 public ConceptDesignation setLanguage(String theLanguage) { 475 myLanguage = theLanguage; 476 return this; 477 } 478 479 public String getUseSystem() { 480 return myUseSystem; 481 } 482 483 public ConceptDesignation setUseSystem(String theUseSystem) { 484 myUseSystem = theUseSystem; 485 return this; 486 } 487 488 public String getUseCode() { 489 return myUseCode; 490 } 491 492 public ConceptDesignation setUseCode(String theUseCode) { 493 myUseCode = theUseCode; 494 return this; 495 } 496 497 public String getUseDisplay() { 498 return myUseDisplay; 499 } 500 501 public ConceptDesignation setUseDisplay(String theUseDisplay) { 502 myUseDisplay = theUseDisplay; 503 return this; 504 } 505 506 public String getValue() { 507 return myValue; 508 } 509 510 public ConceptDesignation setValue(String theValue) { 511 myValue = theValue; 512 return this; 513 } 514 } 515 516 abstract class BaseConceptProperty { 517 private final String myPropertyName; 518 519 /** 520 * Constructor 521 */ 522 protected BaseConceptProperty(String thePropertyName) { 523 myPropertyName = thePropertyName; 524 } 525 526 public String getPropertyName() { 527 return myPropertyName; 528 } 529 530 public abstract String getType(); 531 } 532 533 String TYPE_STRING = "string"; 534 String TYPE_CODING = "Coding"; 535 536 class StringConceptProperty extends BaseConceptProperty { 537 private final String myValue; 538 539 /** 540 * Constructor 541 * 542 * @param theName The name 543 */ 544 public StringConceptProperty(String theName, String theValue) { 545 super(theName); 546 myValue = theValue; 547 } 548 549 public String getValue() { 550 return myValue; 551 } 552 553 public String getType() { 554 return TYPE_STRING; 555 } 556 } 557 558 class CodingConceptProperty extends BaseConceptProperty { 559 private final String myCode; 560 private final String myCodeSystem; 561 private final String myDisplay; 562 563 /** 564 * Constructor 565 * 566 * @param theName The name 567 */ 568 public CodingConceptProperty(String theName, String theCodeSystem, String theCode, String theDisplay) { 569 super(theName); 570 myCodeSystem = theCodeSystem; 571 myCode = theCode; 572 myDisplay = theDisplay; 573 } 574 575 public String getCode() { 576 return myCode; 577 } 578 579 public String getCodeSystem() { 580 return myCodeSystem; 581 } 582 583 public String getDisplay() { 584 return myDisplay; 585 } 586 587 public String getType() { 588 return TYPE_CODING; 589 } 590 } 591 592 class CodeValidationResult { 593 public static final String SOURCE_DETAILS = "sourceDetails"; 594 public static final String RESULT = "result"; 595 public static final String MESSAGE = "message"; 596 public static final String DISPLAY = "display"; 597 598 private String myCode; 599 private String myMessage; 600 private IssueSeverity mySeverity; 601 private String myCodeSystemName; 602 private String myCodeSystemVersion; 603 private List<BaseConceptProperty> myProperties; 604 private String myDisplay; 605 private String mySourceDetails; 606 607 public CodeValidationResult() { 608 super(); 609 } 610 611 /** 612 * This field may contain information about what the source of the 613 * validation information was. 614 */ 615 public String getSourceDetails() { 616 return mySourceDetails; 617 } 618 619 /** 620 * This field may contain information about what the source of the 621 * validation information was. 622 */ 623 public CodeValidationResult setSourceDetails(String theSourceDetails) { 624 mySourceDetails = theSourceDetails; 625 return this; 626 } 627 628 public String getDisplay() { 629 return myDisplay; 630 } 631 632 public CodeValidationResult setDisplay(String theDisplay) { 633 myDisplay = theDisplay; 634 return this; 635 } 636 637 public String getCode() { 638 return myCode; 639 } 640 641 public CodeValidationResult setCode(String theCode) { 642 myCode = theCode; 643 return this; 644 } 645 646 String getCodeSystemName() { 647 return myCodeSystemName; 648 } 649 650 public CodeValidationResult setCodeSystemName(String theCodeSystemName) { 651 myCodeSystemName = theCodeSystemName; 652 return this; 653 } 654 655 public String getCodeSystemVersion() { 656 return myCodeSystemVersion; 657 } 658 659 public CodeValidationResult setCodeSystemVersion(String theCodeSystemVersion) { 660 myCodeSystemVersion = theCodeSystemVersion; 661 return this; 662 } 663 664 public String getMessage() { 665 return myMessage; 666 } 667 668 public CodeValidationResult setMessage(String theMessage) { 669 myMessage = theMessage; 670 return this; 671 } 672 673 public List<BaseConceptProperty> getProperties() { 674 return myProperties; 675 } 676 677 public void setProperties(List<BaseConceptProperty> theProperties) { 678 myProperties = theProperties; 679 } 680 681 public IssueSeverity getSeverity() { 682 return mySeverity; 683 } 684 685 public CodeValidationResult setSeverity(IssueSeverity theSeverity) { 686 mySeverity = theSeverity; 687 return this; 688 } 689 690 public boolean isOk() { 691 return isNotBlank(myCode); 692 } 693 694 public LookupCodeResult asLookupCodeResult(String theSearchedForSystem, String theSearchedForCode) { 695 LookupCodeResult retVal = new LookupCodeResult(); 696 retVal.setSearchedForSystem(theSearchedForSystem); 697 retVal.setSearchedForCode(theSearchedForCode); 698 if (isOk()) { 699 retVal.setFound(true); 700 retVal.setCodeDisplay(myDisplay); 701 retVal.setCodeSystemDisplayName(getCodeSystemName()); 702 retVal.setCodeSystemVersion(getCodeSystemVersion()); 703 } 704 return retVal; 705 } 706 707 /** 708 * Convenience method that returns {@link #getSeverity()} as an IssueSeverity code string 709 */ 710 public String getSeverityCode() { 711 String retVal = null; 712 if (getSeverity() != null) { 713 retVal = getSeverity().name().toLowerCase(); 714 } 715 return retVal; 716 } 717 718 /** 719 * Sets an issue severity as a string code. Value must be the name of 720 * one of the enum values in {@link IssueSeverity}. Value is case-insensitive. 721 */ 722 public CodeValidationResult setSeverityCode(@Nonnull String theIssueSeverity) { 723 setSeverity(IssueSeverity.valueOf(theIssueSeverity.toUpperCase())); 724 return this; 725 } 726 727 public IBaseParameters toParameters(FhirContext theContext) { 728 IBaseParameters retVal = ParametersUtil.newInstance(theContext); 729 730 ParametersUtil.addParameterToParametersBoolean(theContext, retVal, RESULT, isOk()); 731 if (isNotBlank(getMessage())) { 732 ParametersUtil.addParameterToParametersString(theContext, retVal, MESSAGE, getMessage()); 733 } 734 if (isNotBlank(getDisplay())) { 735 ParametersUtil.addParameterToParametersString(theContext, retVal, DISPLAY, getDisplay()); 736 } 737 if (isNotBlank(getSourceDetails())) { 738 ParametersUtil.addParameterToParametersString(theContext, retVal, SOURCE_DETAILS, getSourceDetails()); 739 } 740 741 return retVal; 742 } 743 } 744 745 class ValueSetExpansionOutcome { 746 747 private final IBaseResource myValueSet; 748 private final String myError; 749 750 public ValueSetExpansionOutcome(String theError) { 751 myValueSet = null; 752 myError = theError; 753 } 754 755 public ValueSetExpansionOutcome(IBaseResource theValueSet) { 756 myValueSet = theValueSet; 757 myError = null; 758 } 759 760 public String getError() { 761 return myError; 762 } 763 764 public IBaseResource getValueSet() { 765 return myValueSet; 766 } 767 } 768 769 class LookupCodeResult { 770 771 private String myCodeDisplay; 772 private boolean myCodeIsAbstract; 773 private String myCodeSystemDisplayName; 774 private String myCodeSystemVersion; 775 private boolean myFound; 776 private String mySearchedForCode; 777 private String mySearchedForSystem; 778 private List<BaseConceptProperty> myProperties; 779 private List<ConceptDesignation> myDesignations; 780 private String myErrorMessage; 781 782 /** 783 * Constructor 784 */ 785 public LookupCodeResult() { 786 super(); 787 } 788 789 public List<BaseConceptProperty> getProperties() { 790 if (myProperties == null) { 791 myProperties = new ArrayList<>(); 792 } 793 return myProperties; 794 } 795 796 public void setProperties(List<BaseConceptProperty> theProperties) { 797 myProperties = theProperties; 798 } 799 800 @Nonnull 801 public List<ConceptDesignation> getDesignations() { 802 if (myDesignations == null) { 803 myDesignations = new ArrayList<>(); 804 } 805 return myDesignations; 806 } 807 808 public String getCodeDisplay() { 809 return myCodeDisplay; 810 } 811 812 public void setCodeDisplay(String theCodeDisplay) { 813 myCodeDisplay = theCodeDisplay; 814 } 815 816 public String getCodeSystemDisplayName() { 817 return myCodeSystemDisplayName; 818 } 819 820 public void setCodeSystemDisplayName(String theCodeSystemDisplayName) { 821 myCodeSystemDisplayName = theCodeSystemDisplayName; 822 } 823 824 public String getCodeSystemVersion() { 825 return myCodeSystemVersion; 826 } 827 828 public void setCodeSystemVersion(String theCodeSystemVersion) { 829 myCodeSystemVersion = theCodeSystemVersion; 830 } 831 832 public String getSearchedForCode() { 833 return mySearchedForCode; 834 } 835 836 public LookupCodeResult setSearchedForCode(String theSearchedForCode) { 837 mySearchedForCode = theSearchedForCode; 838 return this; 839 } 840 841 public String getSearchedForSystem() { 842 return mySearchedForSystem; 843 } 844 845 public LookupCodeResult setSearchedForSystem(String theSearchedForSystem) { 846 mySearchedForSystem = theSearchedForSystem; 847 return this; 848 } 849 850 public boolean isCodeIsAbstract() { 851 return myCodeIsAbstract; 852 } 853 854 public void setCodeIsAbstract(boolean theCodeIsAbstract) { 855 myCodeIsAbstract = theCodeIsAbstract; 856 } 857 858 public boolean isFound() { 859 return myFound; 860 } 861 862 public LookupCodeResult setFound(boolean theFound) { 863 myFound = theFound; 864 return this; 865 } 866 867 public void throwNotFoundIfAppropriate() { 868 if (isFound() == false) { 869 throw new ResourceNotFoundException(Msg.code(1738) + "Unable to find code[" + getSearchedForCode() 870 + "] in system[" + getSearchedForSystem() + "]"); 871 } 872 } 873 874 public IBaseParameters toParameters( 875 FhirContext theContext, List<? extends IPrimitiveType<String>> thePropertyNames) { 876 877 IBaseParameters retVal = ParametersUtil.newInstance(theContext); 878 if (isNotBlank(getCodeSystemDisplayName())) { 879 ParametersUtil.addParameterToParametersString(theContext, retVal, "name", getCodeSystemDisplayName()); 880 } 881 if (isNotBlank(getCodeSystemVersion())) { 882 ParametersUtil.addParameterToParametersString(theContext, retVal, "version", getCodeSystemVersion()); 883 } 884 ParametersUtil.addParameterToParametersString(theContext, retVal, "display", getCodeDisplay()); 885 ParametersUtil.addParameterToParametersBoolean(theContext, retVal, "abstract", isCodeIsAbstract()); 886 887 if (myProperties != null) { 888 889 Set<String> properties = Collections.emptySet(); 890 if (thePropertyNames != null) { 891 properties = thePropertyNames.stream() 892 .map(IPrimitiveType::getValueAsString) 893 .collect(Collectors.toSet()); 894 } 895 896 for (BaseConceptProperty next : myProperties) { 897 String propertyName = next.getPropertyName(); 898 899 if (!properties.isEmpty() && !properties.contains(propertyName)) { 900 continue; 901 } 902 903 IBase property = ParametersUtil.addParameterToParameters(theContext, retVal, "property"); 904 ParametersUtil.addPartCode(theContext, property, "code", propertyName); 905 906 String propertyType = next.getType(); 907 switch (propertyType) { 908 case TYPE_STRING: 909 StringConceptProperty stringConceptProperty = (StringConceptProperty) next; 910 ParametersUtil.addPartString( 911 theContext, property, "value", stringConceptProperty.getValue()); 912 break; 913 case TYPE_CODING: 914 CodingConceptProperty codingConceptProperty = (CodingConceptProperty) next; 915 ParametersUtil.addPartCoding( 916 theContext, 917 property, 918 "value", 919 codingConceptProperty.getCodeSystem(), 920 codingConceptProperty.getCode(), 921 codingConceptProperty.getDisplay()); 922 break; 923 default: 924 throw new IllegalStateException( 925 Msg.code(1739) + "Don't know how to handle " + next.getClass()); 926 } 927 } 928 } 929 930 if (myDesignations != null) { 931 for (ConceptDesignation next : myDesignations) { 932 933 IBase property = ParametersUtil.addParameterToParameters(theContext, retVal, "designation"); 934 ParametersUtil.addPartCode(theContext, property, "language", next.getLanguage()); 935 ParametersUtil.addPartCoding( 936 theContext, property, "use", next.getUseSystem(), next.getUseCode(), next.getUseDisplay()); 937 ParametersUtil.addPartString(theContext, property, "value", next.getValue()); 938 } 939 } 940 941 return retVal; 942 } 943 944 public void setErrorMessage(String theErrorMessage) { 945 myErrorMessage = theErrorMessage; 946 } 947 948 public String getErrorMessage() { 949 return myErrorMessage; 950 } 951 952 public static LookupCodeResult notFound(String theSearchedForSystem, String theSearchedForCode) { 953 return new LookupCodeResult() 954 .setFound(false) 955 .setSearchedForSystem(theSearchedForSystem) 956 .setSearchedForCode(theSearchedForCode); 957 } 958 } 959 960 class TranslateCodeRequest { 961 private final String myTargetSystemUrl; 962 private final String myConceptMapUrl; 963 private final String myConceptMapVersion; 964 private final String mySourceValueSetUrl; 965 private final String myTargetValueSetUrl; 966 private final IIdType myResourceId; 967 private final boolean myReverse; 968 private List<IBaseCoding> myCodings; 969 970 public TranslateCodeRequest(List<IBaseCoding> theCodings, String theTargetSystemUrl) { 971 myCodings = theCodings; 972 myTargetSystemUrl = theTargetSystemUrl; 973 myConceptMapUrl = null; 974 myConceptMapVersion = null; 975 mySourceValueSetUrl = null; 976 myTargetValueSetUrl = null; 977 myResourceId = null; 978 myReverse = false; 979 } 980 981 public TranslateCodeRequest( 982 List<IBaseCoding> theCodings, 983 String theTargetSystemUrl, 984 String theConceptMapUrl, 985 String theConceptMapVersion, 986 String theSourceValueSetUrl, 987 String theTargetValueSetUrl, 988 IIdType theResourceId, 989 boolean theReverse) { 990 myCodings = theCodings; 991 myTargetSystemUrl = theTargetSystemUrl; 992 myConceptMapUrl = theConceptMapUrl; 993 myConceptMapVersion = theConceptMapVersion; 994 mySourceValueSetUrl = theSourceValueSetUrl; 995 myTargetValueSetUrl = theTargetValueSetUrl; 996 myResourceId = theResourceId; 997 myReverse = theReverse; 998 } 999 1000 @Override 1001 public boolean equals(Object theO) { 1002 if (this == theO) { 1003 return true; 1004 } 1005 1006 if (theO == null || getClass() != theO.getClass()) { 1007 return false; 1008 } 1009 1010 TranslateCodeRequest that = (TranslateCodeRequest) theO; 1011 1012 return new EqualsBuilder() 1013 .append(myCodings, that.myCodings) 1014 .append(myTargetSystemUrl, that.myTargetSystemUrl) 1015 .append(myConceptMapUrl, that.myConceptMapUrl) 1016 .append(myConceptMapVersion, that.myConceptMapVersion) 1017 .append(mySourceValueSetUrl, that.mySourceValueSetUrl) 1018 .append(myTargetValueSetUrl, that.myTargetValueSetUrl) 1019 .append(myResourceId, that.myResourceId) 1020 .append(myReverse, that.myReverse) 1021 .isEquals(); 1022 } 1023 1024 @Override 1025 public int hashCode() { 1026 return new HashCodeBuilder(17, 37) 1027 .append(myCodings) 1028 .append(myTargetSystemUrl) 1029 .append(myConceptMapUrl) 1030 .append(myConceptMapVersion) 1031 .append(mySourceValueSetUrl) 1032 .append(myTargetValueSetUrl) 1033 .append(myResourceId) 1034 .append(myReverse) 1035 .toHashCode(); 1036 } 1037 1038 public List<IBaseCoding> getCodings() { 1039 return myCodings; 1040 } 1041 1042 public String getTargetSystemUrl() { 1043 return myTargetSystemUrl; 1044 } 1045 1046 public String getConceptMapUrl() { 1047 return myConceptMapUrl; 1048 } 1049 1050 public String getConceptMapVersion() { 1051 return myConceptMapVersion; 1052 } 1053 1054 public String getSourceValueSetUrl() { 1055 return mySourceValueSetUrl; 1056 } 1057 1058 public String getTargetValueSetUrl() { 1059 return myTargetValueSetUrl; 1060 } 1061 1062 public IIdType getResourceId() { 1063 return myResourceId; 1064 } 1065 1066 public boolean isReverse() { 1067 return myReverse; 1068 } 1069 1070 @Override 1071 public String toString() { 1072 return new ToStringBuilder(this) 1073 .append("sourceValueSetUrl", mySourceValueSetUrl) 1074 .append("targetSystemUrl", myTargetSystemUrl) 1075 .append("targetValueSetUrl", myTargetValueSetUrl) 1076 .append("reverse", myReverse) 1077 .toString(); 1078 } 1079 } 1080 1081 /** 1082 * See VersionSpecificWorkerContextWrapper#validateCode in hapi-fhir-validation. 1083 * <p> 1084 * If true, validation for codings will return a positive result if all codings are valid. 1085 * If false, validation for codings will return a positive result if there is any coding that is valid. 1086 * 1087 * @return if the application has configured validation to use logical AND, as opposed to logical OR, which is the default 1088 */ 1089 default boolean isEnabledValidationForCodingsLogicalAnd() { 1090 return false; 1091 } 1092}