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