001package org.hl7.fhir.common.hapi.validation.support; 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.DefaultProfileValidationSupport; 007import ca.uhn.fhir.context.support.IValidationSupport; 008import ca.uhn.fhir.context.support.LookupCodeRequest; 009import ca.uhn.fhir.context.support.TranslateConceptResults; 010import ca.uhn.fhir.context.support.ValidationSupportContext; 011import ca.uhn.fhir.i18n.Msg; 012import ca.uhn.fhir.rest.api.SummaryEnum; 013import ca.uhn.fhir.rest.client.api.IGenericClient; 014import ca.uhn.fhir.rest.gclient.IQuery; 015import ca.uhn.fhir.util.BundleUtil; 016import ca.uhn.fhir.util.ParametersUtil; 017import jakarta.annotation.Nonnull; 018import jakarta.annotation.Nullable; 019import org.apache.commons.lang3.StringUtils; 020import org.apache.commons.lang3.Validate; 021import org.hl7.fhir.instance.model.api.IBaseBundle; 022import org.hl7.fhir.instance.model.api.IBaseDatatype; 023import org.hl7.fhir.instance.model.api.IBaseParameters; 024import org.hl7.fhir.instance.model.api.IBaseResource; 025import org.hl7.fhir.r4.model.Base; 026import org.hl7.fhir.r4.model.CodeSystem; 027import org.hl7.fhir.r4.model.CodeType; 028import org.hl7.fhir.r4.model.Coding; 029import org.hl7.fhir.r4.model.Parameters; 030import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent; 031import org.hl7.fhir.r4.model.Property; 032import org.hl7.fhir.r4.model.StringType; 033import org.hl7.fhir.r4.model.Type; 034import org.hl7.fhir.r4.model.ValueSet; 035import org.slf4j.Logger; 036import org.slf4j.LoggerFactory; 037 038import java.util.ArrayList; 039import java.util.List; 040import java.util.Objects; 041 042import static org.apache.commons.lang3.StringUtils.isBlank; 043import static org.apache.commons.lang3.StringUtils.isNotBlank; 044 045/** 046 * This class is an implementation of {@link IValidationSupport} that fetches validation codes 047 * from a remote FHIR based terminology server. It will invoke the FHIR 048 * <a href="http://hl7.org/fhir/valueset-operation-validate-code.html">ValueSet/$validate-code</a> 049 * operation in order to validate codes. 050 */ 051public class RemoteTerminologyServiceValidationSupport extends BaseValidationSupport implements IValidationSupport { 052 private static final Logger ourLog = LoggerFactory.getLogger(RemoteTerminologyServiceValidationSupport.class); 053 054 private String myBaseUrl; 055 private final List<Object> myClientInterceptors = new ArrayList<>(); 056 057 /** 058 * Constructor 059 * 060 * @param theFhirContext The FhirContext object to use 061 */ 062 public RemoteTerminologyServiceValidationSupport(FhirContext theFhirContext) { 063 super(theFhirContext); 064 } 065 066 public RemoteTerminologyServiceValidationSupport(FhirContext theFhirContext, String theBaseUrl) { 067 super(theFhirContext); 068 myBaseUrl = theBaseUrl; 069 } 070 071 @Override 072 public String getName() { 073 return getFhirContext().getVersion().getVersion() + " Remote Terminology Service Validation Support"; 074 } 075 076 @Override 077 public CodeValidationResult validateCode( 078 ValidationSupportContext theValidationSupportContext, 079 ConceptValidationOptions theOptions, 080 String theCodeSystem, 081 String theCode, 082 String theDisplay, 083 String theValueSetUrl) { 084 return invokeRemoteValidateCode(theCodeSystem, theCode, theDisplay, theValueSetUrl, null); 085 } 086 087 @Override 088 public CodeValidationResult validateCodeInValueSet( 089 ValidationSupportContext theValidationSupportContext, 090 ConceptValidationOptions theOptions, 091 String theCodeSystem, 092 String theCode, 093 String theDisplay, 094 @Nonnull IBaseResource theValueSet) { 095 096 IBaseResource valueSet = theValueSet; 097 098 // some external validators require the system when the code is passed 099 // so let's try to get it from the VS if is not present 100 String codeSystem = theCodeSystem; 101 if (isNotBlank(theCode) && isBlank(codeSystem)) { 102 codeSystem = extractCodeSystemForCode((ValueSet) theValueSet, theCode); 103 } 104 105 // Remote terminology services shouldn't be used to validate codes with an implied system 106 if (isBlank(codeSystem)) { 107 return null; 108 } 109 110 String valueSetUrl = DefaultProfileValidationSupport.getConformanceResourceUrl(myCtx, valueSet); 111 if (isNotBlank(valueSetUrl)) { 112 valueSet = null; 113 } else { 114 valueSetUrl = null; 115 } 116 return invokeRemoteValidateCode(codeSystem, theCode, theDisplay, valueSetUrl, valueSet); 117 } 118 119 /** 120 * Try to obtain the codeSystem of the received code from the received ValueSet 121 */ 122 private String extractCodeSystemForCode(ValueSet theValueSet, String theCode) { 123 if (theValueSet.getCompose() == null 124 || theValueSet.getCompose().getInclude() == null 125 || theValueSet.getCompose().getInclude().isEmpty()) { 126 return null; 127 } 128 129 if (theValueSet.getCompose().getInclude().size() == 1) { 130 ValueSet.ConceptSetComponent include = 131 theValueSet.getCompose().getInclude().iterator().next(); 132 return getVersionedCodeSystem(include); 133 } 134 135 // when component has more than one include, their codeSystem(s) could be different, so we need to make sure 136 // that we are picking up the system for the include filter to which the code corresponds 137 for (ValueSet.ConceptSetComponent include : theValueSet.getCompose().getInclude()) { 138 if (include.hasSystem()) { 139 for (ValueSet.ConceptReferenceComponent concept : include.getConcept()) { 140 if (concept.hasCodeElement() && concept.getCode().equals(theCode)) { 141 return getVersionedCodeSystem(include); 142 } 143 } 144 } 145 } 146 147 // at this point codeSystem couldn't be extracted for a multi-include ValueSet. Just on case it was 148 // because the format was not well handled, let's allow to watch the VS by an easy logging change 149 ourLog.trace("CodeSystem couldn't be extracted for code: {} for ValueSet: {}", theCode, theValueSet.getId()); 150 return null; 151 } 152 153 private String getVersionedCodeSystem(ValueSet.ConceptSetComponent theComponent) { 154 String codeSystem = theComponent.getSystem(); 155 if (!codeSystem.contains("|") && theComponent.hasVersion()) { 156 codeSystem += "|" + theComponent.getVersion(); 157 } 158 return codeSystem; 159 } 160 161 @Override 162 public IBaseResource fetchCodeSystem(String theSystem) { 163 // callers of this want the whole resource. 164 return fetchCodeSystem(theSystem, SummaryEnum.FALSE); 165 } 166 167 /** 168 * Fetch the code system, possibly a summary. 169 * @param theSystem the canonical url 170 * @param theSummaryParam to force a summary mode - or null to allow server default. 171 * @return the CodeSystem 172 */ 173 @Nullable 174 private IBaseResource fetchCodeSystem(String theSystem, @Nullable SummaryEnum theSummaryParam) { 175 IGenericClient client = provideClient(); 176 Class<? extends IBaseBundle> bundleType = 177 myCtx.getResourceDefinition("Bundle").getImplementingClass(IBaseBundle.class); 178 IQuery<IBaseBundle> codeSystemQuery = client.search() 179 .forResource("CodeSystem") 180 .where(CodeSystem.URL.matches().value(theSystem)); 181 182 if (theSummaryParam != null) { 183 codeSystemQuery.summaryMode(theSummaryParam); 184 } 185 186 IBaseBundle results = codeSystemQuery.returnBundle(bundleType).execute(); 187 List<IBaseResource> resultsList = BundleUtil.toListOfResources(myCtx, results); 188 if (!resultsList.isEmpty()) { 189 return resultsList.get(0); 190 } 191 192 return null; 193 } 194 195 @Override 196 public LookupCodeResult lookupCode( 197 ValidationSupportContext theValidationSupportContext, @Nonnull LookupCodeRequest theLookupCodeRequest) { 198 final String code = theLookupCodeRequest.getCode(); 199 final String system = theLookupCodeRequest.getSystem(); 200 final String displayLanguage = theLookupCodeRequest.getDisplayLanguage(); 201 Validate.notBlank(code, "theCode must be provided"); 202 203 IGenericClient client = provideClient(); 204 FhirContext fhirContext = client.getFhirContext(); 205 FhirVersionEnum fhirVersion = fhirContext.getVersion().getVersion(); 206 207 switch (fhirVersion) { 208 case DSTU3: 209 case R4: 210 IBaseParameters params = ParametersUtil.newInstance(fhirContext); 211 ParametersUtil.addParameterToParametersString(fhirContext, params, "code", code); 212 if (!StringUtils.isEmpty(system)) { 213 ParametersUtil.addParameterToParametersString(fhirContext, params, "system", system); 214 } 215 if (!StringUtils.isEmpty(displayLanguage)) { 216 ParametersUtil.addParameterToParametersString(fhirContext, params, "language", displayLanguage); 217 } 218 for (String propertyName : theLookupCodeRequest.getPropertyNames()) { 219 ParametersUtil.addParameterToParametersString(fhirContext, params, "property", propertyName); 220 } 221 Class<? extends IBaseResource> codeSystemClass = 222 myCtx.getResourceDefinition("CodeSystem").getImplementingClass(); 223 IBaseParameters outcome = client.operation() 224 .onType(codeSystemClass) 225 .named("$lookup") 226 .withParameters(params) 227 .useHttpGet() 228 .execute(); 229 if (outcome != null && !outcome.isEmpty()) { 230 switch (fhirVersion) { 231 case DSTU3: 232 return generateLookupCodeResultDSTU3( 233 code, system, (org.hl7.fhir.dstu3.model.Parameters) outcome); 234 case R4: 235 return generateLookupCodeResultR4(code, system, (Parameters) outcome); 236 } 237 } 238 break; 239 default: 240 throw new UnsupportedOperationException(Msg.code(710) + "Unsupported FHIR version '" 241 + fhirVersion.getFhirVersionString() + "'. Only DSTU3 and R4 are supported."); 242 } 243 return null; 244 } 245 246 private LookupCodeResult generateLookupCodeResultDSTU3( 247 String theCode, String theSystem, org.hl7.fhir.dstu3.model.Parameters outcomeDSTU3) { 248 // NOTE: I wanted to put all of this logic into the IValidationSupport Class, but it would've required adding 249 // several new dependencies on version-specific libraries and that is explicitly forbidden (see comment in 250 // POM). 251 LookupCodeResult result = new LookupCodeResult(); 252 result.setSearchedForCode(theCode); 253 result.setSearchedForSystem(theSystem); 254 result.setFound(true); 255 for (org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent parameterComponent : 256 outcomeDSTU3.getParameter()) { 257 String parameterTypeAsString = Objects.toString(parameterComponent.getValue(), null); 258 switch (parameterComponent.getName()) { 259 case "property": 260 org.hl7.fhir.dstu3.model.Property part = parameterComponent.getChildByName("part"); 261 // The assumption here is that we may only have 2 elements in this part, and if so, these 2 will be 262 // saved 263 if (part == null || part.getValues().size() < 2) { 264 continue; 265 } 266 BaseConceptProperty conceptProperty = createBaseConceptPropertyDstu3(part.getValues()); 267 if (conceptProperty != null) { 268 result.getProperties().add(conceptProperty); 269 } 270 break; 271 case "designation": 272 ConceptDesignation conceptDesignation = createConceptDesignationDstu3(parameterComponent); 273 result.getDesignations().add(conceptDesignation); 274 break; 275 case "name": 276 result.setCodeSystemDisplayName(parameterTypeAsString); 277 break; 278 case "version": 279 result.setCodeSystemVersion(parameterTypeAsString); 280 break; 281 case "display": 282 result.setCodeDisplay(parameterTypeAsString); 283 break; 284 case "abstract": 285 result.setCodeIsAbstract(Boolean.parseBoolean(parameterTypeAsString)); 286 break; 287 } 288 } 289 return result; 290 } 291 292 private static BaseConceptProperty createBaseConceptPropertyDstu3(List<org.hl7.fhir.dstu3.model.Base> theValues) { 293 org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent part1 = 294 (org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent) theValues.get(0); 295 String propertyName = ((org.hl7.fhir.dstu3.model.CodeType) part1.getValue()).getValue(); 296 297 BaseConceptProperty conceptProperty = null; 298 org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent part2 = 299 (org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent) theValues.get(1); 300 301 org.hl7.fhir.dstu3.model.Type value = part2.getValue(); 302 if (value == null) { 303 return conceptProperty; 304 } 305 String fhirType = value.fhirType(); 306 switch (fhirType) { 307 case TYPE_STRING: 308 org.hl7.fhir.dstu3.model.StringType stringType = (org.hl7.fhir.dstu3.model.StringType) part2.getValue(); 309 conceptProperty = new StringConceptProperty(propertyName, stringType.getValue()); 310 break; 311 case TYPE_CODING: 312 org.hl7.fhir.dstu3.model.Coding coding = (org.hl7.fhir.dstu3.model.Coding) part2.getValue(); 313 conceptProperty = new CodingConceptProperty( 314 propertyName, coding.getSystem(), coding.getCode(), coding.getDisplay()); 315 break; 316 // TODO: add other property types as per FHIR spec https://github.com/hapifhir/hapi-fhir/issues/5699 317 default: 318 // other types will not fail for Remote Terminology 319 conceptProperty = new StringConceptProperty(propertyName, value.toString()); 320 } 321 return conceptProperty; 322 } 323 324 public static BaseConceptProperty createConceptProperty(final String theName, final IBaseDatatype theValue) { 325 if (theValue instanceof Type) { 326 return createConceptPropertyR4(theName, (Type) theValue); 327 } 328 if (theValue instanceof org.hl7.fhir.dstu3.model.Type) { 329 return createConceptPropertyDstu3(theName, (org.hl7.fhir.dstu3.model.Type) theValue); 330 } 331 return null; 332 } 333 334 private static BaseConceptProperty createConceptPropertyDstu3( 335 final String theName, final org.hl7.fhir.dstu3.model.Type theValue) { 336 if (theValue == null) { 337 return null; 338 } 339 BaseConceptProperty conceptProperty; 340 String fhirType = theValue.fhirType(); 341 switch (fhirType) { 342 case IValidationSupport.TYPE_STRING: 343 org.hl7.fhir.dstu3.model.StringType stringType = (org.hl7.fhir.dstu3.model.StringType) theValue; 344 conceptProperty = new StringConceptProperty(theName, stringType.getValue()); 345 break; 346 case IValidationSupport.TYPE_CODING: 347 org.hl7.fhir.dstu3.model.Coding coding = (org.hl7.fhir.dstu3.model.Coding) theValue; 348 conceptProperty = 349 new CodingConceptProperty(theName, coding.getSystem(), coding.getCode(), coding.getDisplay()); 350 break; 351 // TODO: add other property types as per FHIR spec https://github.com/hapifhir/hapi-fhir/issues/5699 352 default: 353 // other types will not fail for Remote Terminology 354 conceptProperty = new StringConceptProperty(theName, theValue.toString()); 355 } 356 return conceptProperty; 357 } 358 359 private ConceptDesignation createConceptDesignationDstu3( 360 org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent theParameterComponent) { 361 ConceptDesignation conceptDesignation = new ConceptDesignation(); 362 for (org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent designationComponent : 363 theParameterComponent.getPart()) { 364 org.hl7.fhir.dstu3.model.Type designationComponentValue = designationComponent.getValue(); 365 if (designationComponentValue == null) { 366 continue; 367 } 368 switch (designationComponent.getName()) { 369 case "language": 370 conceptDesignation.setLanguage(designationComponentValue.toString()); 371 break; 372 case "use": 373 org.hl7.fhir.dstu3.model.Coding coding = 374 (org.hl7.fhir.dstu3.model.Coding) designationComponentValue; 375 conceptDesignation.setUseSystem(coding.getSystem()); 376 conceptDesignation.setUseCode(coding.getCode()); 377 conceptDesignation.setUseDisplay(coding.getDisplay()); 378 break; 379 case "value": 380 conceptDesignation.setValue(designationComponent.getValue().toString()); 381 break; 382 } 383 } 384 return conceptDesignation; 385 } 386 387 private LookupCodeResult generateLookupCodeResultR4(String theCode, String theSystem, Parameters outcomeR4) { 388 // NOTE: I wanted to put all of this logic into the IValidationSupport Class, but it would've required adding 389 // several new dependencies on version-specific libraries and that is explicitly forbidden (see comment in 390 // POM). 391 LookupCodeResult result = new LookupCodeResult(); 392 result.setSearchedForCode(theCode); 393 result.setSearchedForSystem(theSystem); 394 result.setFound(true); 395 for (ParametersParameterComponent parameterComponent : outcomeR4.getParameter()) { 396 String parameterTypeAsString = Objects.toString(parameterComponent.getValue(), null); 397 switch (parameterComponent.getName()) { 398 case "property": 399 Property part = parameterComponent.getChildByName("part"); 400 // The assumption here is that we may only have 2 elements in this part, and if so, these 2 will be 401 // saved 402 if (part == null || part.getValues().size() < 2) { 403 continue; 404 } 405 BaseConceptProperty conceptProperty = createBaseConceptPropertyR4(part.getValues()); 406 if (conceptProperty != null) { 407 result.getProperties().add(conceptProperty); 408 } 409 break; 410 case "designation": 411 ConceptDesignation conceptDesignation = createConceptDesignationR4(parameterComponent); 412 result.getDesignations().add(conceptDesignation); 413 break; 414 case "name": 415 result.setCodeSystemDisplayName(parameterTypeAsString); 416 break; 417 case "version": 418 result.setCodeSystemVersion(parameterTypeAsString); 419 break; 420 case "display": 421 result.setCodeDisplay(parameterTypeAsString); 422 break; 423 case "abstract": 424 result.setCodeIsAbstract(Boolean.parseBoolean(parameterTypeAsString)); 425 break; 426 } 427 } 428 return result; 429 } 430 431 private static BaseConceptProperty createBaseConceptPropertyR4(List<Base> values) { 432 ParametersParameterComponent part1 = (ParametersParameterComponent) values.get(0); 433 String propertyName = ((CodeType) part1.getValue()).getValue(); 434 435 ParametersParameterComponent part2 = (ParametersParameterComponent) values.get(1); 436 437 Type value = part2.getValue(); 438 if (value == null) { 439 return null; 440 } 441 BaseConceptProperty conceptProperty; 442 String fhirType = value.fhirType(); 443 switch (fhirType) { 444 case IValidationSupport.TYPE_STRING: 445 StringType stringType = (StringType) part2.getValue(); 446 conceptProperty = new StringConceptProperty(propertyName, stringType.getValue()); 447 break; 448 case IValidationSupport.TYPE_CODING: 449 Coding coding = (Coding) part2.getValue(); 450 conceptProperty = new CodingConceptProperty( 451 propertyName, coding.getSystem(), coding.getCode(), coding.getDisplay()); 452 break; 453 // TODO: add other property types as per FHIR spec https://github.com/hapifhir/hapi-fhir/issues/5699 454 default: 455 // other types will not fail for Remote Terminology 456 conceptProperty = new StringConceptProperty(propertyName, value.toString()); 457 } 458 return conceptProperty; 459 } 460 461 private static BaseConceptProperty createConceptPropertyR4(final String theName, final Type theValue) { 462 BaseConceptProperty conceptProperty; 463 464 String fhirType = theValue.fhirType(); 465 switch (fhirType) { 466 case IValidationSupport.TYPE_STRING: 467 StringType stringType = (StringType) theValue; 468 conceptProperty = new StringConceptProperty(theName, stringType.getValue()); 469 break; 470 case IValidationSupport.TYPE_CODING: 471 Coding coding = (Coding) theValue; 472 conceptProperty = 473 new CodingConceptProperty(theName, coding.getSystem(), coding.getCode(), coding.getDisplay()); 474 break; 475 // TODO: add other property types as per FHIR spec https://github.com/hapifhir/hapi-fhir/issues/5699 476 default: 477 // other types will not fail for Remote Terminology 478 conceptProperty = new StringConceptProperty(theName, theValue.toString()); 479 } 480 return conceptProperty; 481 } 482 483 private ConceptDesignation createConceptDesignationR4(ParametersParameterComponent theParameterComponent) { 484 ConceptDesignation conceptDesignation = new ConceptDesignation(); 485 for (ParametersParameterComponent designationComponent : theParameterComponent.getPart()) { 486 Type designationComponentValue = designationComponent.getValue(); 487 if (designationComponentValue == null) { 488 continue; 489 } 490 switch (designationComponent.getName()) { 491 case "language": 492 conceptDesignation.setLanguage(designationComponentValue.toString()); 493 break; 494 case "use": 495 Coding coding = (Coding) designationComponentValue; 496 conceptDesignation.setUseSystem(coding.getSystem()); 497 conceptDesignation.setUseCode(coding.getCode()); 498 conceptDesignation.setUseDisplay(coding.getDisplay()); 499 break; 500 case "value": 501 conceptDesignation.setValue(designationComponentValue.toString()); 502 break; 503 } 504 } 505 return conceptDesignation; 506 } 507 508 @Override 509 public IBaseResource fetchValueSet(String theValueSetUrl) { 510 // force the remote server to send the whole resource. 511 SummaryEnum summaryParam = SummaryEnum.FALSE; 512 return fetchValueSet(theValueSetUrl, summaryParam); 513 } 514 515 /** 516 * Search for a ValueSet by canonical url via IGenericClient. 517 * 518 * @param theValueSetUrl the canonical url of the ValueSet 519 * @param theSummaryParam force a summary mode - null allows server default 520 * @return the ValueSet or null if none match the url 521 */ 522 @Nullable 523 private IBaseResource fetchValueSet(String theValueSetUrl, SummaryEnum theSummaryParam) { 524 IGenericClient client = provideClient(); 525 Class<? extends IBaseBundle> bundleType = 526 myCtx.getResourceDefinition("Bundle").getImplementingClass(IBaseBundle.class); 527 528 IQuery<IBaseBundle> valueSetQuery = client.search() 529 .forResource("ValueSet") 530 .where(CodeSystem.URL.matches().value(theValueSetUrl)); 531 532 if (theSummaryParam != null) { 533 valueSetQuery.summaryMode(theSummaryParam); 534 } 535 536 IBaseBundle results = valueSetQuery.returnBundle(bundleType).execute(); 537 538 List<IBaseResource> resultsList = BundleUtil.toListOfResources(myCtx, results); 539 if (!resultsList.isEmpty()) { 540 return resultsList.get(0); 541 } 542 543 return null; 544 } 545 546 @Override 547 public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) { 548 // a summary is ok if we are just checking the presence. 549 SummaryEnum summaryParam = null; 550 551 return fetchCodeSystem(theSystem, summaryParam) != null; 552 } 553 554 @Override 555 public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) { 556 // a summary is ok if we are just checking the presence. 557 SummaryEnum summaryParam = null; 558 559 return fetchValueSet(theValueSetUrl, summaryParam) != null; 560 } 561 562 @Override 563 public TranslateConceptResults translateConcept(TranslateCodeRequest theRequest) { 564 IGenericClient client = provideClient(); 565 FhirContext fhirContext = client.getFhirContext(); 566 567 IBaseParameters params = RemoteTerminologyUtil.buildTranslateInputParameters(fhirContext, theRequest); 568 569 IBaseParameters outcome = client.operation() 570 .onType("ConceptMap") 571 .named("$translate") 572 .withParameters(params) 573 .execute(); 574 575 return RemoteTerminologyUtil.translateOutcomeToResults(fhirContext, outcome); 576 } 577 578 private IGenericClient provideClient() { 579 IGenericClient retVal = myCtx.newRestfulGenericClient(myBaseUrl); 580 for (Object next : myClientInterceptors) { 581 retVal.registerInterceptor(next); 582 } 583 return retVal; 584 } 585 586 protected CodeValidationResult invokeRemoteValidateCode( 587 String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl, IBaseResource theValueSet) { 588 if (isBlank(theCode)) { 589 return null; 590 } 591 592 IGenericClient client = provideClient(); 593 594 IBaseParameters input = 595 buildValidateCodeInputParameters(theCodeSystem, theCode, theDisplay, theValueSetUrl, theValueSet); 596 597 String resourceType = "ValueSet"; 598 if (theValueSet == null && theValueSetUrl == null) { 599 resourceType = "CodeSystem"; 600 } 601 602 IBaseParameters output = client.operation() 603 .onType(resourceType) 604 .named("validate-code") 605 .withParameters(input) 606 .execute(); 607 608 List<String> resultValues = ParametersUtil.getNamedParameterValuesAsString(getFhirContext(), output, "result"); 609 if (resultValues.isEmpty() || isBlank(resultValues.get(0))) { 610 return null; 611 } 612 Validate.isTrue(resultValues.size() == 1, "Response contained %d 'result' values", resultValues.size()); 613 614 boolean success = "true".equalsIgnoreCase(resultValues.get(0)); 615 616 CodeValidationResult retVal = new CodeValidationResult(); 617 if (success) { 618 619 retVal.setCode(theCode); 620 List<String> displayValues = 621 ParametersUtil.getNamedParameterValuesAsString(getFhirContext(), output, "display"); 622 if (!displayValues.isEmpty()) { 623 retVal.setDisplay(displayValues.get(0)); 624 } 625 626 } else { 627 628 retVal.setSeverity(IssueSeverity.ERROR); 629 List<String> messageValues = 630 ParametersUtil.getNamedParameterValuesAsString(getFhirContext(), output, "message"); 631 if (!messageValues.isEmpty()) { 632 retVal.setMessage(messageValues.get(0)); 633 } 634 } 635 return retVal; 636 } 637 638 protected IBaseParameters buildValidateCodeInputParameters( 639 String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl, IBaseResource theValueSet) { 640 IBaseParameters params = ParametersUtil.newInstance(getFhirContext()); 641 642 if (theValueSet == null && theValueSetUrl == null) { 643 ParametersUtil.addParameterToParametersUri(getFhirContext(), params, "url", theCodeSystem); 644 ParametersUtil.addParameterToParametersString(getFhirContext(), params, "code", theCode); 645 if (isNotBlank(theDisplay)) { 646 ParametersUtil.addParameterToParametersString(getFhirContext(), params, "display", theDisplay); 647 } 648 return params; 649 } 650 651 if (isNotBlank(theValueSetUrl)) { 652 ParametersUtil.addParameterToParametersUri(getFhirContext(), params, "url", theValueSetUrl); 653 } 654 ParametersUtil.addParameterToParametersString(getFhirContext(), params, "code", theCode); 655 if (isNotBlank(theCodeSystem)) { 656 ParametersUtil.addParameterToParametersUri(getFhirContext(), params, "system", theCodeSystem); 657 } 658 if (isNotBlank(theDisplay)) { 659 ParametersUtil.addParameterToParametersString(getFhirContext(), params, "display", theDisplay); 660 } 661 if (theValueSet != null) { 662 ParametersUtil.addParameterToParameters(getFhirContext(), params, "valueSet", theValueSet); 663 } 664 return params; 665 } 666 667 /** 668 * Sets the FHIR Terminology Server base URL 669 * 670 * @param theBaseUrl The base URL, e.g. "<a href="https://hapi.fhir.org/baseR4">...</a>" 671 */ 672 public void setBaseUrl(String theBaseUrl) { 673 Validate.notBlank(theBaseUrl, "theBaseUrl must be provided"); 674 myBaseUrl = theBaseUrl; 675 } 676 677 /** 678 * Adds an interceptor that will be registered to all clients. 679 * <p> 680 * Note that this method is not thread-safe and should only be called prior to this module 681 * being used. 682 * </p> 683 * 684 * @param theClientInterceptor The interceptor (must not be null) 685 */ 686 public void addClientInterceptor(@Nonnull Object theClientInterceptor) { 687 Validate.notNull(theClientInterceptor, "theClientInterceptor must not be null"); 688 myClientInterceptors.add(theClientInterceptor); 689 } 690}