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 //Pass endpoint for client - URI 107 public FHIRToolingClient(String baseServiceUrl, String userAgent) throws URISyntaxException { 108 preferredResourceFormat = ResourceFormat.RESOURCE_JSON; 109 this.userAgent = userAgent; 110 initialize(baseServiceUrl); 111 } 112 113 public void initialize(String baseServiceUrl) throws URISyntaxException { 114 base = baseServiceUrl; 115 resourceAddress = new ResourceAddress(baseServiceUrl); 116 this.maxResultSetSize = -1; 117 } 118 119 public Client getClient() { 120 return client; 121 } 122 123 public void setClient(Client client) { 124 this.client = client; 125 } 126 127 public String getPreferredResourceFormat() { 128 return preferredResourceFormat.getHeader(); 129 } 130 131 public void setPreferredResourceFormat(ResourceFormat resourceFormat) { 132 preferredResourceFormat = resourceFormat; 133 } 134 135 public int getMaximumRecordCount() { 136 return maxResultSetSize; 137 } 138 139 public void setMaximumRecordCount(int maxResultSetSize) { 140 this.maxResultSetSize = maxResultSetSize; 141 } 142 143 private List<ResourceFormat> getResourceFormatsWithPreferredFirst() { 144 return Stream.concat( 145 Arrays.stream(new ResourceFormat[]{preferredResourceFormat}), 146 Arrays.stream(ResourceFormat.values()).filter(a -> a != preferredResourceFormat) 147 ).collect(Collectors.toList()); 148 } 149 150 private <T extends Resource> T getCapabilities(URI resourceUri, String message, String exceptionMessage) throws FHIRException { 151 final List<ResourceFormat> resourceFormats = getResourceFormatsWithPreferredFirst(); 152 153 for (ResourceFormat attemptedResourceFormat : resourceFormats) { 154 try { 155 T output = (T) client.issueGetResourceRequest(resourceUri, 156 preferredResourceFormat.getHeader(), 157 generateHeaders(), 158 message, 159 TIMEOUT_NORMAL).getReference(); 160 if (attemptedResourceFormat != preferredResourceFormat) { 161 setPreferredResourceFormat(attemptedResourceFormat); 162 } 163 return output; 164 } catch (Exception e) { 165 logger.warn("Failed attempt to fetch " + resourceUri, e); 166 } 167 } 168 throw new FHIRException(exceptionMessage); 169 } 170 171 public TerminologyCapabilities getTerminologyCapabilities() { 172 TerminologyCapabilities capabilities = null; 173 174 try { 175 capabilities = getCapabilities(resourceAddress.resolveMetadataTxCaps(), 176 "TerminologyCapabilities", 177 "Error fetching the server's terminology capabilities"); 178 } catch (ClassCastException e) { 179 throw new FHIRException("Unexpected response format for Terminology Capability metadata", e); 180 } 181 return capabilities; 182 } 183 184 public CapabilityStatement getCapabilitiesStatement() { 185 CapabilityStatement capabilityStatement = null; 186 187 capabilityStatement = getCapabilities(resourceAddress.resolveMetadataUri(false), 188 189 "CapabilitiesStatement", "Error fetching the server's conformance statement"); 190 return capabilityStatement; 191 } 192 193 public CapabilityStatement getCapabilitiesStatementQuick() throws EFhirClientException { 194 if (capabilities != null) return capabilities; 195 196 capabilities = getCapabilities(resourceAddress.resolveMetadataUri(true), 197 198 "CapabilitiesStatement-Quick", 199 "Error fetching the server's capability statement"); 200 201 return capabilities; 202 } 203 204 public <T extends Resource> T read(Class<T> resourceClass, String id) {//TODO Change this to AddressableResource 205 ResourceRequest<T> result = null; 206 try { 207 result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id), 208 getPreferredResourceFormat(), 209 generateHeaders(), 210 "Read " + resourceClass.getName() + "/" + id, 211 TIMEOUT_NORMAL); 212 if (result.isUnsuccessfulRequest()) { 213 throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); 214 } 215 } catch (Exception e) { 216 throw new FHIRException(e); 217 } 218 return result.getPayload(); 219 } 220 221 public <T extends Resource> T vread(Class<T> resourceClass, String id, String version) { 222 ResourceRequest<T> result = null; 223 try { 224 result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndIdAndVersion(resourceClass, id, version), 225 getPreferredResourceFormat(), 226 generateHeaders(), 227 "VRead " + resourceClass.getName() + "/" + id + "/?_history/" + version, 228 TIMEOUT_NORMAL); 229 if (result.isUnsuccessfulRequest()) { 230 throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); 231 } 232 } catch (Exception e) { 233 throw new FHIRException("Error trying to read this version of the resource", e); 234 } 235 return result.getPayload(); 236 } 237 238 public <T extends Resource> T getCanonical(Class<T> resourceClass, String canonicalURL) { 239 ResourceRequest<T> result = null; 240 try { 241 result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndCanonical(resourceClass, canonicalURL), 242 getPreferredResourceFormat(), 243 generateHeaders(), 244 "Read " + resourceClass.getName() + "?url=" + canonicalURL, 245 TIMEOUT_NORMAL); 246 if (result.isUnsuccessfulRequest()) { 247 throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); 248 } 249 } catch (Exception e) { 250 handleException("An error has occurred while trying to read this version of the resource", e); 251 } 252 Bundle bnd = (Bundle) result.getPayload(); 253 if (bnd.getEntry().size() == 0) 254 throw new EFhirClientException("No matching resource found for canonical URL '" + canonicalURL + "'"); 255 if (bnd.getEntry().size() > 1) 256 throw new EFhirClientException("Multiple matching resources found for canonical URL '" + canonicalURL + "'"); 257 return (T) bnd.getEntry().get(0).getResource(); 258 } 259 260 public Resource update(Resource resource) { 261 org.hl7.fhir.r5.utils.client.network.ResourceRequest<Resource> result = null; 262 try { 263 result = client.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resource.getClass(), resource.getId()), 264 ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat())), 265 getPreferredResourceFormat(), 266 generateHeaders(), 267 "Update " + resource.fhirType() + "/" + resource.getId(), 268 TIMEOUT_OPERATION); 269 if (result.isUnsuccessfulRequest()) { 270 throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); 271 } 272 } catch (Exception e) { 273 throw new EFhirClientException("An error has occurred while trying to update this resource", e); 274 } 275 // 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 276 try { 277 OperationOutcome operationOutcome = (OperationOutcome) result.getPayload(); 278 ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier = ResourceAddress.parseCreateLocation(result.getLocation()); 279 return this.vread(resource.getClass(), resVersionedIdentifier.getId(), resVersionedIdentifier.getVersionId()); 280 } catch (ClassCastException e) { 281 // if we fall throught we have the correct type already in the create 282 } 283 284 return result.getPayload(); 285 } 286 287 public <T extends Resource> T update(Class<T> resourceClass, T resource, String id) { 288 ResourceRequest<T> result = null; 289 try { 290 result = client.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id), 291 ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat())), 292 getPreferredResourceFormat(), 293 generateHeaders(), 294 "Update " + resource.fhirType() + "/" + id, 295 TIMEOUT_OPERATION); 296 if (result.isUnsuccessfulRequest()) { 297 throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); 298 } 299 } catch (Exception e) { 300 throw new EFhirClientException("An error has occurred while trying to update this resource", e); 301 } 302 // 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 303 try { 304 OperationOutcome operationOutcome = (OperationOutcome) result.getPayload(); 305 ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier = ResourceAddress.parseCreateLocation(result.getLocation()); 306 return this.vread(resourceClass, resVersionedIdentifier.getId(), resVersionedIdentifier.getVersionId()); 307 } catch (ClassCastException e) { 308 // if we fall through we have the correct type already in the create 309 } 310 311 return result.getPayload(); 312 } 313 314 public <T extends Resource> Parameters operateType(Class<T> resourceClass, String name, Parameters params) { 315 boolean complex = false; 316 for (ParametersParameterComponent p : params.getParameter()) 317 complex = complex || !(p.getValue() instanceof PrimitiveType); 318 String ps = ""; 319 try { 320 if (!complex) 321 for (ParametersParameterComponent p : params.getParameter()) 322 if (p.getValue() instanceof PrimitiveType) 323 ps += p.getName() + "=" + Utilities.encodeUri(((PrimitiveType) p.getValue()).asStringValue()) + "&"; 324 ResourceRequest<T> result; 325 URI url = resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps); 326 if (complex) { 327 byte[] body = ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat())); 328 result = client.issuePostRequest(url, body, getPreferredResourceFormat(), generateHeaders(), 329 "POST " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG); 330 } else { 331 result = client.issueGetResourceRequest(url, getPreferredResourceFormat(), generateHeaders(), "GET " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG); 332 } 333 if (result.isUnsuccessfulRequest()) { 334 throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); 335 } 336 if (result.getPayload() instanceof Parameters) { 337 return (Parameters) result.getPayload(); 338 } else { 339 Parameters p_out = new Parameters(); 340 p_out.addParameter().setName("return").setResource(result.getPayload()); 341 return p_out; 342 } 343 } catch (Exception e) { 344 handleException("Error performing tx5 operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e); 345 } 346 return null; 347 } 348 349 public Bundle transaction(Bundle batch) { 350 Bundle transactionResult = null; 351 try { 352 transactionResult = client.postBatchRequest(resourceAddress.getBaseServiceUri(), ByteUtils.resourceToByteArray(batch, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), 353 generateHeaders(), 354 "transaction", TIMEOUT_OPERATION + (TIMEOUT_ENTRY * batch.getEntry().size())); 355 } catch (Exception e) { 356 handleException("An error occurred trying to process this transaction request", e); 357 } 358 return transactionResult; 359 } 360 361 @SuppressWarnings("unchecked") 362 public <T extends Resource> OperationOutcome validate(Class<T> resourceClass, T resource, String id) { 363 ResourceRequest<T> result = null; 364 try { 365 result = client.issuePostRequest(resourceAddress.resolveValidateUri(resourceClass, id), 366 ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat())), 367 getPreferredResourceFormat(), generateHeaders(), 368 "POST " + resourceClass.getName() + (id != null ? "/" + id : "") + "/$validate", TIMEOUT_OPERATION_LONG); 369 if (result.isUnsuccessfulRequest()) { 370 throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); 371 } 372 } catch (Exception e) { 373 handleException("An error has occurred while trying to validate this resource", e); 374 } 375 return (OperationOutcome) result.getPayload(); 376 } 377 378 /** 379 * Helper method to prevent nesting of previously thrown EFhirClientExceptions 380 * 381 * @param e 382 * @throws EFhirClientException 383 */ 384 protected void handleException(String message, Exception e) throws EFhirClientException { 385 if (e instanceof EFhirClientException) { 386 throw (EFhirClientException) e; 387 } else { 388 throw new EFhirClientException(message, e); 389 } 390 } 391 392 /** 393 * Helper method to determine whether desired resource representation 394 * is Json or XML. 395 * 396 * @param format 397 * @return 398 */ 399 protected boolean isJson(String format) { 400 boolean isJson = false; 401 if (format.toLowerCase().contains("json")) { 402 isJson = true; 403 } 404 return isJson; 405 } 406 407 public Bundle fetchFeed(String url) { 408 Bundle feed = null; 409 try { 410 feed = client.issueGetFeedRequest(new URI(url), getPreferredResourceFormat()); 411 } catch (Exception e) { 412 handleException("An error has occurred while trying to retrieve history since last update", e); 413 } 414 return feed; 415 } 416 417 public ValueSet expandValueset(ValueSet source, Parameters expParams) { 418 Parameters p = expParams == null ? new Parameters() : expParams.copy(); 419 p.addParameter().setName("valueSet").setResource(source); 420 org.hl7.fhir.r5.utils.client.network.ResourceRequest<Resource> result = null; 421 try { 422 result = client.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand"), 423 ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat())), 424 getPreferredResourceFormat(), 425 generateHeaders(), 426 "ValueSet/$expand?url=" + source.getUrl(), 427 TIMEOUT_OPERATION_EXPAND); 428 if (result.isUnsuccessfulRequest()) { 429 throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); 430 } 431 } catch (IOException e) { 432 e.printStackTrace(); 433 } 434 return result == null ? null : (ValueSet) result.getPayload(); 435 } 436 437 438 public Parameters lookupCode(Map<String, String> params) { 439 org.hl7.fhir.r5.utils.client.network.ResourceRequest<Resource> result = null; 440 try { 441 result = client.issueGetResourceRequest(resourceAddress.resolveOperationUri(CodeSystem.class, "lookup", params), 442 getPreferredResourceFormat(), 443 generateHeaders(), 444 "CodeSystem/$lookup", 445 TIMEOUT_NORMAL); 446 } catch (IOException e) { 447 e.printStackTrace(); 448 } 449 if (result.isUnsuccessfulRequest()) { 450 throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); 451 } 452 return (Parameters) result.getPayload(); 453 } 454 455 public ValueSet expandValueset(ValueSet source, Parameters expParams, Map<String, String> params) { 456 Parameters p = expParams == null ? new Parameters() : expParams.copy(); 457 p.addParameter().setName("valueSet").setResource(source); 458 for (String n : params.keySet()) { 459 p.addParameter().setName(n).setValue(new StringType(params.get(n))); 460 } 461 org.hl7.fhir.r5.utils.client.network.ResourceRequest<Resource> result = null; 462 try { 463 464 result = client.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand", params), 465 ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat())), 466 getPreferredResourceFormat(), 467 generateHeaders(), 468 "ValueSet/$expand?url=" + source.getUrl(), 469 TIMEOUT_OPERATION_EXPAND); 470 if (result.isUnsuccessfulRequest()) { 471 throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); 472 } 473 } catch (IOException e) { 474 e.printStackTrace(); 475 } 476 return result == null ? null : (ValueSet) result.getPayload(); 477 } 478 479 public String getAddress() { 480 return base; 481 } 482 483 public ConceptMap initializeClosure(String name) { 484 Parameters params = new Parameters(); 485 params.addParameter().setName("name").setValue(new StringType(name)); 486 ResourceRequest<Resource> result = null; 487 try { 488 result = client.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()), 489 ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat())), 490 getPreferredResourceFormat(), 491 generateHeaders(), 492 "Closure?name=" + name, 493 TIMEOUT_NORMAL); 494 if (result.isUnsuccessfulRequest()) { 495 throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); 496 } 497 } catch (IOException e) { 498 e.printStackTrace(); 499 } 500 return result == null ? null : (ConceptMap) result.getPayload(); 501 } 502 503 public ConceptMap updateClosure(String name, Coding coding) { 504 Parameters params = new Parameters(); 505 params.addParameter().setName("name").setValue(new StringType(name)); 506 params.addParameter().setName("concept").setValue(coding); 507 org.hl7.fhir.r5.utils.client.network.ResourceRequest<Resource> result = null; 508 try { 509 result = client.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()), 510 ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat())), 511 getPreferredResourceFormat(), 512 generateHeaders(), 513 "UpdateClosure?name=" + name, 514 TIMEOUT_OPERATION); 515 if (result.isUnsuccessfulRequest()) { 516 throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload()); 517 } 518 } catch (IOException e) { 519 e.printStackTrace(); 520 } 521 return result == null ? null : (ConceptMap) result.getPayload(); 522 } 523 524 public String getUsername() { 525 return username; 526 } 527 528 public void setUsername(String username) { 529 this.username = username; 530 } 531 532 public String getPassword() { 533 return password; 534 } 535 536 public void setPassword(String password) { 537 this.password = password; 538 } 539 540 public long getTimeout() { 541 return client.getTimeout(); 542 } 543 544 public void setTimeout(long timeout) { 545 client.setTimeout(timeout); 546 } 547 548 public ToolingClientLogger getLogger() { 549 return client.getLogger(); 550 } 551 552 public void setLogger(ToolingClientLogger logger) { 553 client.setLogger(logger); 554 } 555 556 public int getRetryCount() { 557 return client.getRetryCount(); 558 } 559 560 public void setRetryCount(int retryCount) { 561 client.setRetryCount(retryCount); 562 } 563 564 public void setClientHeaders(ArrayList<Header> headers) { 565 this.headers = headers; 566 } 567 568 private Headers generateHeaders() { 569 Headers.Builder builder = new Headers.Builder(); 570 // Add basic auth header if it exists 571 if (basicAuthHeaderExists()) { 572 builder.add(getAuthorizationHeader().toString()); 573 } 574 // Add any other headers 575 if(this.headers != null) { 576 this.headers.forEach(header -> builder.add(header.toString())); 577 } 578 if (!Utilities.noString(userAgent)) { 579 builder.add("User-Agent: "+userAgent); 580 } 581 return builder.build(); 582 } 583 584 public boolean basicAuthHeaderExists() { 585 return (username != null) && (password != null); 586 } 587 588 public Header getAuthorizationHeader() { 589 String usernamePassword = username + ":" + password; 590 String base64usernamePassword = Base64.getEncoder().encodeToString(usernamePassword.getBytes()); 591 return new Header("Authorization", "Basic " + base64usernamePassword); 592 } 593 594 public String getUserAgent() { 595 return userAgent; 596 } 597 598 public void setUserAgent(String userAgent) { 599 this.userAgent = userAgent; 600 } 601 602 public String getServerVersion() { 603 if (capabilities == null) { 604 try { 605 getCapabilitiesStatementQuick(); 606 } catch (Throwable e) { 607 //FIXME This is creepy. Shouldn't we report this at some level? 608 } 609 } 610 return capabilities == null ? null : capabilities.getSoftware().getVersion(); 611 } 612 613 614} 615