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