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