001package org.hl7.fhir.r5.utils.client; 002 003import okhttp3.Headers; 004import okhttp3.internal.http2.Header; 005import org.hl7.fhir.exceptions.FHIRException; 006import org.hl7.fhir.r5.model.Bundle; 007import org.hl7.fhir.r5.model.OperationOutcome; 008import org.hl7.fhir.r5.model.Resource; 009import org.hl7.fhir.r5.utils.client.EFhirClientException; 010 011/* 012 Copyright (c) 2011+, HL7, Inc. 013 All rights reserved. 014 015 Redistribution and use in source and binary forms, with or without modification, 016 are permitted provided that the following conditions are met: 017 018 * Redistributions of source code must retain the above copyright notice, this 019 list of conditions and the following disclaimer. 020 * Redistributions in binary form must reproduce the above copyright notice, 021 this list of conditions and the following disclaimer in the documentation 022 and/or other materials provided with the distribution. 023 * Neither the name of HL7 nor the names of its contributors may be used to 024 endorse or promote products derived from this software without specific 025 prior written permission. 026 027 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 028 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 029 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 030 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 031 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 032 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 033 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 034 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 035 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 036 POSSIBILITY OF SUCH DAMAGE. 037 038*/ 039 040import org.hl7.fhir.r5.model.*; 041import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent; 042import org.hl7.fhir.r5.utils.client.network.ByteUtils; 043import org.hl7.fhir.r5.utils.client.network.Client; 044import org.hl7.fhir.r5.utils.client.network.ResourceRequest; 045import org.hl7.fhir.utilities.FHIRBaseToolingClient; 046import org.hl7.fhir.utilities.ToolingClientLogger; 047import org.hl7.fhir.utilities.Utilities; 048import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager; 049import org.slf4j.Logger; 050import org.slf4j.LoggerFactory; 051 052import java.io.IOException; 053import java.net.URI; 054import java.net.URISyntaxException; 055import java.util.*; 056import java.util.stream.Collectors; 057import java.util.stream.Stream; 058 059/** 060 * Very Simple RESTful client. This is purely for use in the standalone 061 * tools jar packages. It doesn't support many features, only what the tools 062 * need. 063 * <p> 064 * To use, initialize class and set base service URI as follows: 065 * 066 * <pre><code> 067 * FHIRSimpleClient fhirClient = new FHIRSimpleClient(); 068 * fhirClient.initialize("http://my.fhir.domain/myServiceRoot"); 069 * </code></pre> 070 * <p> 071 * Default Accept and Content-Type headers are application/fhir+xml and application/fhir+json. 072 * <p> 073 * These can be changed by invoking the following setter functions: 074 * 075 * <pre><code> 076 * setPreferredResourceFormat() 077 * setPreferredFeedFormat() 078 * </code></pre> 079 * <p> 080 * TODO Review all sad paths. 081 * 082 * @author Claude Nanjo 083 */ 084public class FHIRToolingClient extends FHIRBaseToolingClient { 085 086 private static final Logger logger = LoggerFactory.getLogger(FHIRToolingClient.class); 087 088 089 public static final String DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ssK"; 090 public static final String DATE_FORMAT = "yyyy-MM-dd"; 091 public static final String hostKey = "http.proxyHost"; 092 public static final String portKey = "http.proxyPort"; 093 094 private String base; 095 private ResourceAddress resourceAddress; 096 private ResourceFormat preferredResourceFormat; 097 private int maxResultSetSize = -1;//_count 098 private CapabilityStatement capabilities; 099 private Client client = new Client(); 100 private ArrayList<Header> headers = new ArrayList<>(); 101 private String username; 102 private String password; 103 private String userAgent; 104 105 106 private String acceptLang; 107 private String contentLang; 108 109 110 private int useCount; 111 112 113 //Pass endpoint for client - URI 114 public FHIRToolingClient(String baseServiceUrl, String userAgent) throws URISyntaxException { 115 preferredResourceFormat = ResourceFormat.RESOURCE_JSON; 116 this.userAgent = userAgent; 117 initialize(baseServiceUrl); 118 } 119 120 public void initialize(String baseServiceUrl) throws URISyntaxException { 121 base = baseServiceUrl; 122 client.setBase(base); 123 resourceAddress = new ResourceAddress(baseServiceUrl); 124 this.maxResultSetSize = -1; 125 } 126 127 public Client getClient() { 128 return client; 129 } 130 131 public void setClient(Client client) { 132 this.client = client; 133 } 134 135 public String getPreferredResourceFormat() { 136 return preferredResourceFormat.getHeader(); 137 } 138 139 public void setPreferredResourceFormat(ResourceFormat resourceFormat) { 140 preferredResourceFormat = resourceFormat; 141 } 142 143 public int getMaximumRecordCount() { 144 return maxResultSetSize; 145 } 146 147 public void setMaximumRecordCount(int maxResultSetSize) { 148 this.maxResultSetSize = maxResultSetSize; 149 } 150 151 private List<ResourceFormat> getResourceFormatsWithPreferredFirst() { 152 return Stream.concat( 153 Arrays.stream(new ResourceFormat[]{preferredResourceFormat}), 154 Arrays.stream(ResourceFormat.values()).filter(a -> a != preferredResourceFormat) 155 ).collect(Collectors.toList()); 156 } 157 158 private <T extends Resource> T getCapabilities(URI resourceUri, String message, String exceptionMessage) throws FHIRException { 159 final List<ResourceFormat> resourceFormats = getResourceFormatsWithPreferredFirst(); 160 161 for (ResourceFormat attemptedResourceFormat : resourceFormats) { 162 try { 163 T output = (T) client.issueGetResourceRequest(resourceUri, 164 withVer(preferredResourceFormat.getHeader(), "5.0"), 165 generateHeaders(false), 166 message, 167 timeoutNormal).getReference(); 168 if (attemptedResourceFormat != preferredResourceFormat) { 169 setPreferredResourceFormat(attemptedResourceFormat); 170 } 171 return output; 172 } catch (Exception e) { 173 logger.warn("Failed attempt to fetch " + resourceUri, e); 174 } 175 } 176 throw new FHIRException(exceptionMessage); 177 } 178 179 public TerminologyCapabilities getTerminologyCapabilities() { 180 TerminologyCapabilities capabilities = null; 181 182 try { 183 capabilities = getCapabilities(resourceAddress.resolveMetadataTxCaps(), 184 "TerminologyCapabilities", 185 "Error fetching the server's terminology capabilities"); 186 } catch (ClassCastException e) { 187 throw new FHIRException("Unexpected response format for Terminology Capability metadata", e); 188 } 189 return capabilities; 190 } 191 192 public CapabilityStatement getCapabilitiesStatement() { 193 CapabilityStatement capabilityStatement = null; 194 195 capabilityStatement = getCapabilities(resourceAddress.resolveMetadataUri(false), 196 197 "CapabilitiesStatement", "Error fetching the server's conformance statement"); 198 return capabilityStatement; 199 } 200 201 public CapabilityStatement getCapabilitiesStatementQuick() throws EFhirClientException { 202 if (capabilities != null) return capabilities; 203 204 capabilities = getCapabilities(resourceAddress.resolveMetadataUri(true), 205 206 "CapabilitiesStatement-Quick", 207 "Error fetching the server's capability statement"); 208 209 return capabilities; 210 } 211 212 public Resource read(String resourceClass, String id) {// TODO Change this to AddressableResource 213 recordUse(); 214 ResourceRequest<Resource> result = null; 215 try { 216 result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id), 217 withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(false), "Read " + resourceClass + "/" + id, 218 timeoutNormal); 219 if (result.isUnsuccessfulRequest()) { 220 throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), 221 (OperationOutcome) result.getPayload()); 222 } 223 } catch (Exception e) { 224 throw new FHIRException(e); 225 } 226 return result.getPayload(); 227 } 228 229 230 public <T extends Resource> T read(Class<T> resourceClass, String id) {//TODO Change this to AddressableResource 231 recordUse(); 232 ResourceRequest<T> result = null; 233 try { 234 result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id), 235 withVer(getPreferredResourceFormat(), "4.0"), 236 generateHeaders(false), 237 "Read " + resourceClass.getName() + "/" + id, 238 timeoutNormal); 239 if (result.isUnsuccessfulRequest()) { 240 throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); 241 } 242 } catch (Exception e) { 243 throw new FHIRException(e); 244 } 245 return result.getPayload(); 246 } 247 248 public <T extends Resource> T vread(Class<T> resourceClass, String id, String version) { 249 recordUse(); 250 ResourceRequest<T> result = null; 251 try { 252 result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndIdAndVersion(resourceClass, id, version), 253 withVer(getPreferredResourceFormat(), "4.0"), 254 generateHeaders(false), 255 "VRead " + resourceClass.getName() + "/" + id + "/?_history/" + version, 256 timeoutNormal); 257 if (result.isUnsuccessfulRequest()) { 258 throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); 259 } 260 } catch (Exception e) { 261 throw new FHIRException("Error trying to read this version of the resource", e); 262 } 263 return result.getPayload(); 264 } 265 266 public <T extends Resource> T getCanonical(Class<T> resourceClass, String canonicalURL) { 267 recordUse(); 268 ResourceRequest<T> result = null; 269 try { 270 result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndCanonical(resourceClass, canonicalURL), 271 withVer(getPreferredResourceFormat(), "4.0"), 272 generateHeaders(false), 273 "Read " + resourceClass.getName() + "?url=" + canonicalURL, 274 timeoutNormal); 275 if (result.isUnsuccessfulRequest()) { 276 throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); 277 } 278 } catch (Exception e) { 279 handleException(0, "An error has occurred while trying to read this version of the resource", e); 280 } 281 Bundle bnd = (Bundle) result.getPayload(); 282 if (bnd.getEntry().size() == 0) 283 throw new EFhirClientException(0, "No matching resource found for canonical URL '" + canonicalURL + "'"); 284 if (bnd.getEntry().size() > 1) 285 throw new EFhirClientException(0, "Multiple matching resources found for canonical URL '" + canonicalURL + "'"); 286 return (T) bnd.getEntry().get(0).getResource(); 287 } 288 289 public Resource update(Resource resource) { 290 recordUse(); 291 org.hl7.fhir.r5.utils.client.network.ResourceRequest<Resource> result = null; 292 try { 293 result = client.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resource.getClass(), resource.getId()), 294 ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat()), false), 295 withVer(getPreferredResourceFormat(), "4.0"), 296 generateHeaders(true), 297 "Update " + resource.fhirType() + "/" + resource.getId(), 298 timeoutOperation); 299 if (result.isUnsuccessfulRequest()) { 300 throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); 301 } 302 } catch (Exception e) { 303 throw new EFhirClientException(0, "An error has occurred while trying to update this resource", e); 304 } 305 // TODO oe 26.1.2015 could be made nicer if only OperationOutcome locationheader is returned with an operationOutcome would be returned (and not the resource also) we make another read 306 try { 307 OperationOutcome operationOutcome = (OperationOutcome) result.getPayload(); 308 ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier = ResourceAddress.parseCreateLocation(result.getLocation()); 309 return this.vread(resource.getClass(), resVersionedIdentifier.getId(), resVersionedIdentifier.getVersionId()); 310 } catch (ClassCastException e) { 311 // if we fall throught we have the correct type already in the create 312 } 313 314 return result.getPayload(); 315 } 316 317 public <T extends Resource> T update(Class<T> resourceClass, T resource, String id) { 318 recordUse(); 319 ResourceRequest<T> result = null; 320 try { 321 result = client.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id), 322 ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat()), false), 323 withVer(getPreferredResourceFormat(), "4.0"), 324 generateHeaders(true), 325 "Update " + resource.fhirType() + "/" + id, 326 timeoutOperation); 327 if (result.isUnsuccessfulRequest()) { 328 throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); 329 } 330 } catch (Exception e) { 331 throw new EFhirClientException(0, "An error has occurred while trying to update this resource", e); 332 } 333 // TODO oe 26.1.2015 could be made nicer if only OperationOutcome locationheader is returned with an operationOutcome would be returned (and not the resource also) we make another read 334 try { 335 OperationOutcome operationOutcome = (OperationOutcome) result.getPayload(); 336 ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier = ResourceAddress.parseCreateLocation(result.getLocation()); 337 return this.vread(resourceClass, resVersionedIdentifier.getId(), resVersionedIdentifier.getVersionId()); 338 } catch (ClassCastException e) { 339 // if we fall through we have the correct type already in the create 340 } 341 342 return result.getPayload(); 343 } 344 345 public <T extends Resource> Parameters operateType(Class<T> resourceClass, String name, Parameters params) { 346 recordUse(); 347 boolean complex = false; 348 for (ParametersParameterComponent p : params.getParameter()) 349 complex = complex || !(p.getValue() instanceof PrimitiveType); 350 String ps = ""; 351 try { 352 if (!complex) 353 for (ParametersParameterComponent p : params.getParameter()) 354 if (p.getValue() instanceof PrimitiveType) 355 ps += p.getName() + "=" + Utilities.encodeUri(((PrimitiveType) p.getValue()).asStringValue()) + "&"; 356 ResourceRequest<T> result; 357 URI url = resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps); 358 if (complex) { 359 byte[] body = ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()), true); 360 result = client.issuePostRequest(url, body, withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(true), 361 "POST " + resourceClass.getName() + "/$" + name, timeoutLong); 362 } else { 363 result = client.issueGetResourceRequest(url, withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(false), "GET " + resourceClass.getName() + "/$" + name, timeoutLong); 364 } 365 if (result.isUnsuccessfulRequest()) { 366 throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); 367 } 368 if (result.getPayload() instanceof Parameters) { 369 return (Parameters) result.getPayload(); 370 } else { 371 Parameters p_out = new Parameters(); 372 p_out.addParameter().setName("return").setResource(result.getPayload()); 373 return p_out; 374 } 375 } catch (Exception e) { 376 handleException(0, "Error performing tx5 operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e); 377 } 378 return null; 379 } 380 381 public Bundle transaction(Bundle batch) { 382 recordUse(); 383 Bundle transactionResult = null; 384 try { 385 transactionResult = client.postBatchRequest(resourceAddress.getBaseServiceUri(), ByteUtils.resourceToByteArray(batch, false, isJson(getPreferredResourceFormat()), false), withVer(getPreferredResourceFormat(), "4.0"), 386 generateHeaders(true), 387 "transaction", timeoutOperation + (timeoutEntry * batch.getEntry().size())); 388 } catch (Exception e) { 389 handleException(0, "An error occurred trying to process this transaction request", e); 390 } 391 return transactionResult; 392 } 393 394 @SuppressWarnings("unchecked") 395 public <T extends Resource> OperationOutcome validate(Class<T> resourceClass, T resource, String id) { 396 recordUse(); 397 ResourceRequest<T> result = null; 398 try { 399 result = client.issuePostRequest(resourceAddress.resolveValidateUri(resourceClass, id), 400 ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat()), false), 401 withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(true), 402 "POST " + resourceClass.getName() + (id != null ? "/" + id : "") + "/$validate", timeoutLong); 403 if (result.isUnsuccessfulRequest()) { 404 throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); 405 } 406 } catch (Exception e) { 407 handleException(0, "An error has occurred while trying to validate this resource", e); 408 } 409 return (OperationOutcome) result.getPayload(); 410 } 411 412 /** 413 * Helper method to prevent nesting of previously thrown EFhirClientExceptions 414 * 415 * @param e 416 * @throws EFhirClientException 417 */ 418 protected void handleException(int code, String message, Exception e) throws EFhirClientException { 419 if (e instanceof EFhirClientException) { 420 throw (EFhirClientException) e; 421 } else { 422 throw new EFhirClientException(code, message, e); 423 } 424 } 425 426 /** 427 * Helper method to determine whether desired resource representation 428 * is Json or XML. 429 * 430 * @param format 431 * @return 432 */ 433 protected boolean isJson(String format) { 434 boolean isJson = false; 435 if (format.toLowerCase().contains("json")) { 436 isJson = true; 437 } 438 return isJson; 439 } 440 441 public Bundle fetchFeed(String url) { 442 recordUse(); 443 Bundle feed = null; 444 try { 445 feed = client.issueGetFeedRequest(new URI(url), getPreferredResourceFormat()); 446 } catch (Exception e) { 447 handleException(0, "An error has occurred while trying to retrieve history since last update", e); 448 } 449 return feed; 450 } 451 452 public ValueSet expandValueset(ValueSet source, Parameters expParams) { 453 recordUse(); 454 Parameters p = expParams == null ? new Parameters() : expParams.copy(); 455 p.addParameter().setName("valueSet").setResource(source); 456 org.hl7.fhir.r5.utils.client.network.ResourceRequest<Resource> result = null; 457 try { 458 result = client.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand"), 459 ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat()), true), 460 withVer(getPreferredResourceFormat(), "4.0"), 461 generateHeaders(true), 462 "ValueSet/$expand?url=" + source.getUrl(), 463 timeoutExpand); 464 } catch (IOException e) { 465 throw new FHIRException(e); 466 } 467 if (result.isUnsuccessfulRequest()) { 468 throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); 469 } 470 return result == null ? null : (ValueSet) result.getPayload(); 471 } 472 473 public Parameters lookupCode(Map<String, String> params) { 474 recordUse(); 475 org.hl7.fhir.r5.utils.client.network.ResourceRequest<Resource> result = null; 476 try { 477 result = client.issueGetResourceRequest(resourceAddress.resolveOperationUri(CodeSystem.class, "lookup", params), 478 withVer(getPreferredResourceFormat(), "4.0"), 479 generateHeaders(false), 480 "CodeSystem/$lookup", 481 timeoutNormal); 482 } catch (IOException e) { 483 e.printStackTrace(); 484 } 485 if (result.isUnsuccessfulRequest()) { 486 throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); 487 } 488 return (Parameters) result.getPayload(); 489 } 490 491 public Parameters lookupCode(Parameters p) { 492 recordUse(); 493 org.hl7.fhir.r5.utils.client.network.ResourceRequest<Resource> result = null; 494 try { 495 result = client.issuePostRequest(resourceAddress.resolveOperationUri(CodeSystem.class, "lookup"), 496 ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat()), true), 497 withVer(getPreferredResourceFormat(), "4.0"), 498 generateHeaders(true), 499 "CodeSystem/$lookup", 500 timeoutNormal); 501 } catch (IOException e) { 502 e.printStackTrace(); 503 } 504 if (result.isUnsuccessfulRequest()) { 505 throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); 506 } 507 return (Parameters) result.getPayload(); 508 } 509 510 public Parameters translate(Parameters p) { 511 recordUse(); 512 org.hl7.fhir.r5.utils.client.network.ResourceRequest<Resource> result = null; 513 try { 514 result = client.issuePostRequest(resourceAddress.resolveOperationUri(ConceptMap.class, "translate"), 515 ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat()), true), 516 withVer(getPreferredResourceFormat(), "4.0"), 517 generateHeaders(true), 518 "ConceptMap/$translate", 519 timeoutNormal); 520 } catch (IOException e) { 521 e.printStackTrace(); 522 } 523 if (result.isUnsuccessfulRequest()) { 524 throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); 525 } 526 return (Parameters) result.getPayload(); 527 } 528 529 public String getAddress() { 530 return base; 531 } 532 533 public ConceptMap initializeClosure(String name) { 534 recordUse(); 535 Parameters params = new Parameters(); 536 params.addParameter().setName("name").setValue(new StringType(name)); 537 ResourceRequest<Resource> result = null; 538 try { 539 result = client.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()), 540 ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()), true), 541 withVer(getPreferredResourceFormat(), "4.0"), 542 generateHeaders(true), 543 "Closure?name=" + name, 544 timeoutNormal); 545 if (result.isUnsuccessfulRequest()) { 546 throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); 547 } 548 } catch (IOException e) { 549 e.printStackTrace(); 550 } 551 return result == null ? null : (ConceptMap) result.getPayload(); 552 } 553 554 public ConceptMap updateClosure(String name, Coding coding) { 555 recordUse(); 556 Parameters params = new Parameters(); 557 params.addParameter().setName("name").setValue(new StringType(name)); 558 params.addParameter().setName("concept").setValue(coding); 559 org.hl7.fhir.r5.utils.client.network.ResourceRequest<Resource> result = null; 560 try { 561 result = client.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()), 562 ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()), true), 563 withVer(getPreferredResourceFormat(), "4.0"), 564 generateHeaders(true), 565 "UpdateClosure?name=" + name, 566 timeoutOperation); 567 if (result.isUnsuccessfulRequest()) { 568 throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); 569 } 570 } catch (IOException e) { 571 e.printStackTrace(); 572 } 573 return result == null ? null : (ConceptMap) result.getPayload(); 574 } 575 576 public String getUsername() { 577 return username; 578 } 579 580 public void setUsername(String username) { 581 this.username = username; 582 } 583 584 public String getPassword() { 585 return password; 586 } 587 588 public void setPassword(String password) { 589 this.password = password; 590 } 591 592 public long getTimeout() { 593 return client.getTimeout(); 594 } 595 596 public void setTimeout(long timeout) { 597 client.setTimeout(timeout); 598 } 599 600 public ToolingClientLogger getLogger() { 601 return client.getLogger(); 602 } 603 604 public void setLogger(ToolingClientLogger logger) { 605 client.setLogger(logger); 606 } 607 608 public int getRetryCount() { 609 return client.getRetryCount(); 610 } 611 612 public void setRetryCount(int retryCount) { 613 client.setRetryCount(retryCount); 614 } 615 616 public void setClientHeaders(ArrayList<Header> headers) { 617 this.headers = headers; 618 } 619 620 private Headers generateHeaders(boolean hasBody) { 621 Headers.Builder builder = new Headers.Builder(); 622 // Add basic auth header if it exists 623 if (basicAuthHeaderExists()) { 624 builder.add(getAuthorizationHeader().toString()); 625 } 626 // Add any other headers 627 if(this.headers != null) { 628 this.headers.forEach(header -> builder.add(header.toString())); 629 } 630 if (!Utilities.noString(userAgent)) { 631 builder.add("User-Agent: "+userAgent); 632 } 633 634 if (!Utilities.noString(acceptLang)) { 635 builder.add("Accept-Language: "+acceptLang); 636 } 637 638 if (hasBody && !Utilities.noString(contentLang)) { 639 builder.add("Content-Language: "+contentLang); 640 } 641 642 return builder.build(); 643 } 644 645 public boolean basicAuthHeaderExists() { 646 return (username != null) && (password != null); 647 } 648 649 public Header getAuthorizationHeader() { 650 String usernamePassword = username + ":" + password; 651 String base64usernamePassword = Base64.getEncoder().encodeToString(usernamePassword.getBytes()); 652 return new Header("Authorization", "Basic " + base64usernamePassword); 653 } 654 655 public String getUserAgent() { 656 return userAgent; 657 } 658 659 public void setUserAgent(String userAgent) { 660 this.userAgent = userAgent; 661 } 662 663 public String getServerVersion() { 664 if (capabilities == null) { 665 try { 666 getCapabilitiesStatementQuick(); 667 } catch (Throwable e) { 668 //#TODO This is creepy. Shouldn't we report this at some level? 669 } 670 } 671 return capabilities == null ? null : capabilities.getSoftware().getVersion(); 672 } 673 674 public void setAcceptLanguage(String lang) { 675 this.acceptLang = lang; 676 } 677 678 public void setContentLanguage(String lang) { 679 this.contentLang = lang; 680 } 681 682 public Bundle search(String type, String criteria) { 683 recordUse(); 684 return fetchFeed(Utilities.pathURL(base, type+criteria)); 685 } 686 687 public <T extends Resource> T fetchResource(Class<T> resourceClass, String id) { 688 recordUse(); 689 org.hl7.fhir.r5.utils.client.network.ResourceRequest<Resource> result = null; 690 try { 691 result = client.issueGetResourceRequest(resourceAddress.resolveGetResource(resourceClass, id), 692 withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(false), resourceClass.getName()+"/"+id, timeoutNormal); 693 } catch (IOException e) { 694 throw new FHIRException(e); 695 } 696 if (result.isUnsuccessfulRequest()) { 697 throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), 698 (OperationOutcome) result.getPayload()); 699 } 700 return (T) result.getPayload(); 701 } 702 703 private void recordUse() { 704 useCount++; 705 } 706 707 public int getUseCount() { 708 return useCount; 709 } 710 711} 712