github.com/phrase/openapi@v0.0.0-20240514140800-49e8a106740e/openapi-generator/templates/java/libraries/okhttp-gson/ApiClient.mustache (about) 1 {{>licenseInfo}} 2 3 package {{invokerPackage}}; 4 5 import okhttp3.*; 6 import okhttp3.internal.http.HttpMethod; 7 import okhttp3.internal.tls.OkHostnameVerifier; 8 import okhttp3.logging.HttpLoggingInterceptor; 9 import okhttp3.logging.HttpLoggingInterceptor.Level; 10 import okio.BufferedSink; 11 import okio.Okio; 12 {{#joda}} 13 import org.joda.time.DateTime; 14 import org.joda.time.LocalDate; 15 import org.joda.time.format.DateTimeFormatter; 16 {{/joda}} 17 {{#threetenbp}} 18 import org.threeten.bp.LocalDate; 19 import org.threeten.bp.OffsetDateTime; 20 import org.threeten.bp.format.DateTimeFormatter; 21 {{/threetenbp}} 22 {{#hasOAuthMethods}} 23 import org.apache.oltu.oauth2.client.request.OAuthClientRequest.TokenRequestBuilder; 24 import org.apache.oltu.oauth2.common.message.types.GrantType; 25 {{/hasOAuthMethods}} 26 27 import javax.net.ssl.*; 28 import java.io.File; 29 import java.io.IOException; 30 import java.io.InputStream; 31 import java.io.UnsupportedEncodingException; 32 import java.lang.reflect.Type; 33 import java.net.URI; 34 import java.net.URLConnection; 35 import java.net.URLEncoder; 36 import java.security.GeneralSecurityException; 37 import java.security.KeyStore; 38 import java.security.SecureRandom; 39 import java.security.cert.Certificate; 40 import java.security.cert.CertificateException; 41 import java.security.cert.CertificateFactory; 42 import java.security.cert.X509Certificate; 43 import java.text.DateFormat; 44 {{#java8}} 45 import java.time.LocalDate; 46 import java.time.OffsetDateTime; 47 import java.time.format.DateTimeFormatter; 48 {{/java8}} 49 import java.util.*; 50 import java.util.Map.Entry; 51 import java.util.concurrent.TimeUnit; 52 import java.util.regex.Matcher; 53 import java.util.regex.Pattern; 54 55 import {{invokerPackage}}.auth.Authentication; 56 import {{invokerPackage}}.auth.HttpBasicAuth; 57 import {{invokerPackage}}.auth.HttpBearerAuth; 58 import {{invokerPackage}}.auth.ApiKeyAuth; 59 {{#hasOAuthMethods}} 60 import {{invokerPackage}}.auth.OAuth; 61 import {{invokerPackage}}.auth.RetryingOAuth; 62 import {{invokerPackage}}.auth.OAuthFlow; 63 {{/hasOAuthMethods}} 64 65 public class ApiClient { 66 67 private String basePath = "{{{basePath}}}"; 68 private boolean debugging = false; 69 private Map<String, String> defaultHeaderMap = new HashMap<String, String>(); 70 private Map<String, String> defaultCookieMap = new HashMap<String, String>(); 71 private String tempFolderPath = null; 72 73 private Map<String, Authentication> authentications; 74 75 private DateFormat dateFormat; 76 private DateFormat datetimeFormat; 77 private boolean lenientDatetimeFormat; 78 private int dateLength; 79 80 private InputStream sslCaCert; 81 private boolean verifyingSsl; 82 private KeyManager[] keyManagers; 83 84 private OkHttpClient httpClient; 85 private JSON json; 86 87 private HttpLoggingInterceptor loggingInterceptor; 88 89 /* 90 * Basic constructor for ApiClient 91 */ 92 public ApiClient() { 93 init(); 94 initHttpClient(); 95 96 // Setup authentications (key: authentication name, value: authentication).{{#authMethods}}{{#isBasic}}{{#isBasicBasic}} 97 authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{^isBasicBasic}} 98 authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}"));{{/isBasicBasic}}{{/isBasic}}{{#isApiKey}} 99 authentications.put("{{name}}", new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{#isKeyInQuery}}"query"{{/isKeyInQuery}}{{#isKeyInCookie}}"cookie"{{/isKeyInCookie}}, "{{keyParamName}}"));{{/isApiKey}}{{#isOAuth}} 100 authentications.put("{{name}}", new OAuth());{{/isOAuth}}{{/authMethods}} 101 // Prevent the authentications from being modified. 102 authentications = Collections.unmodifiableMap(authentications); 103 } 104 105 {{#hasOAuthMethods}} 106 {{#oauthMethods}} 107 {{#-first}} 108 /* 109 * Constructor for ApiClient to support access token retry on 401/403 configured with client ID 110 */ 111 public ApiClient(String clientId) { 112 this(clientId, null, null); 113 } 114 115 /* 116 * Constructor for ApiClient to support access token retry on 401/403 configured with client ID and additional parameters 117 */ 118 public ApiClient(String clientId, Map<String, String> parameters) { 119 this(clientId, null, parameters); 120 } 121 122 /* 123 * Constructor for ApiClient to support access token retry on 401/403 configured with client ID, secret, and additional parameters 124 */ 125 public ApiClient(String clientId, String clientSecret, Map<String, String> parameters) { 126 this(null, clientId, clientSecret, parameters); 127 } 128 129 /* 130 * Constructor for ApiClient to support access token retry on 401/403 configured with base path, client ID, secret, and additional parameters 131 */ 132 public ApiClient(String basePath, String clientId, String clientSecret, Map<String, String> parameters) { 133 init(); 134 if (basePath != null) { 135 this.basePath = basePath; 136 } 137 138 {{#hasOAuthMethods}} 139 String tokenUrl = "{{tokenUrl}}"; 140 if (!"".equals(tokenUrl) && !URI.create(tokenUrl).isAbsolute()) { 141 URI uri = URI.create(getBasePath()); 142 tokenUrl = uri.getScheme() + ":" + 143 (uri.getAuthority() != null ? "//" + uri.getAuthority() : "") + 144 tokenUrl; 145 if (!URI.create(tokenUrl).isAbsolute()) { 146 throw new IllegalArgumentException("OAuth2 token URL must be an absolute URL"); 147 } 148 } 149 RetryingOAuth retryingOAuth = new RetryingOAuth(tokenUrl, clientId, OAuthFlow.{{flow}}, clientSecret, parameters); 150 authentications.put( 151 "{{name}}", 152 retryingOAuth 153 ); 154 initHttpClient(Collections.<Interceptor>singletonList(retryingOAuth)); 155 {{/hasOAuthMethods}} 156 // Setup authentications (key: authentication name, value: authentication).{{#authMethods}}{{#isBasic}}{{#isBasicBasic}} 157 authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{^isBasicBasic}} 158 authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}"));{{/isBasicBasic}}{{/isBasic}}{{#isApiKey}} 159 authentications.put("{{name}}", new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{#isKeyInQuery}}"query"{{/isKeyInQuery}}{{#isKeyInCookie}}"cookie"{{/isKeyInCookie}}, "{{keyParamName}}"));{{/isApiKey}}{{/authMethods}} 160 161 // Prevent the authentications from being modified. 162 authentications = Collections.unmodifiableMap(authentications); 163 } 164 165 {{/-first}} 166 {{/oauthMethods}} 167 {{/hasOAuthMethods}} 168 private void initHttpClient() { 169 initHttpClient(Collections.<Interceptor>emptyList()); 170 } 171 172 private void initHttpClient(List<Interceptor> interceptors) { 173 OkHttpClient.Builder builder = new OkHttpClient.Builder(); 174 builder.addNetworkInterceptor(getProgressInterceptor()); 175 for (Interceptor interceptor: interceptors) { 176 builder.addInterceptor(interceptor); 177 } 178 {{#useGzipFeature}} 179 // Enable gzip request compression 180 builder.addInterceptor(new GzipRequestInterceptor()); 181 {{/useGzipFeature}} 182 183 httpClient = builder.build(); 184 } 185 186 private void init() { 187 verifyingSsl = true; 188 189 json = new JSON(); 190 191 // Set default User-Agent. 192 setUserAgent("{{#httpUserAgent}}{{{.}}}{{/httpUserAgent}}{{^httpUserAgent}}OpenAPI-Generator/{{{artifactVersion}}}/java{{/httpUserAgent}}"); 193 194 authentications = new HashMap<String, Authentication>(); 195 } 196 197 /** 198 * Get base path 199 * 200 * @return Base path 201 */ 202 public String getBasePath() { 203 return basePath; 204 } 205 206 /** 207 * Set base path 208 * 209 * @param basePath Base path of the URL (e.g {{{basePath}}} 210 * @return An instance of OkHttpClient 211 */ 212 public ApiClient setBasePath(String basePath) { 213 this.basePath = basePath; 214 return this; 215 } 216 217 /** 218 * Get HTTP client 219 * 220 * @return An instance of OkHttpClient 221 */ 222 public OkHttpClient getHttpClient() { 223 return httpClient; 224 } 225 226 /** 227 * Set HTTP client, which must never be null. 228 * 229 * @param newHttpClient An instance of OkHttpClient 230 * @return Api Client 231 * @throws NullPointerException when newHttpClient is null 232 */ 233 public ApiClient setHttpClient(OkHttpClient newHttpClient) { 234 this.httpClient = Objects.requireNonNull(newHttpClient, "HttpClient must not be null!"); 235 return this; 236 } 237 238 /** 239 * Get JSON 240 * 241 * @return JSON object 242 */ 243 public JSON getJSON() { 244 return json; 245 } 246 247 /** 248 * Set JSON 249 * 250 * @param json JSON object 251 * @return Api client 252 */ 253 public ApiClient setJSON(JSON json) { 254 this.json = json; 255 return this; 256 } 257 258 /** 259 * True if isVerifyingSsl flag is on 260 * 261 * @return True if isVerifySsl flag is on 262 */ 263 public boolean isVerifyingSsl() { 264 return verifyingSsl; 265 } 266 267 /** 268 * Configure whether to verify certificate and hostname when making https requests. 269 * Default to true. 270 * NOTE: Do NOT set to false in production code, otherwise you would face multiple types of cryptographic attacks. 271 * 272 * @param verifyingSsl True to verify TLS/SSL connection 273 * @return ApiClient 274 */ 275 public ApiClient setVerifyingSsl(boolean verifyingSsl) { 276 this.verifyingSsl = verifyingSsl; 277 applySslSettings(); 278 return this; 279 } 280 281 /** 282 * Get SSL CA cert. 283 * 284 * @return Input stream to the SSL CA cert 285 */ 286 public InputStream getSslCaCert() { 287 return sslCaCert; 288 } 289 290 /** 291 * Configure the CA certificate to be trusted when making https requests. 292 * Use null to reset to default. 293 * 294 * @param sslCaCert input stream for SSL CA cert 295 * @return ApiClient 296 */ 297 public ApiClient setSslCaCert(InputStream sslCaCert) { 298 this.sslCaCert = sslCaCert; 299 applySslSettings(); 300 return this; 301 } 302 303 public KeyManager[] getKeyManagers() { 304 return keyManagers; 305 } 306 307 /** 308 * Configure client keys to use for authorization in an SSL session. 309 * Use null to reset to default. 310 * 311 * @param managers The KeyManagers to use 312 * @return ApiClient 313 */ 314 public ApiClient setKeyManagers(KeyManager[] managers) { 315 this.keyManagers = managers; 316 applySslSettings(); 317 return this; 318 } 319 320 public DateFormat getDateFormat() { 321 return dateFormat; 322 } 323 324 public ApiClient setDateFormat(DateFormat dateFormat) { 325 this.json.setDateFormat(dateFormat); 326 return this; 327 } 328 329 public ApiClient setSqlDateFormat(DateFormat dateFormat) { 330 this.json.setSqlDateFormat(dateFormat); 331 return this; 332 } 333 334 {{#joda}} 335 public ApiClient setDateTimeFormat(DateTimeFormatter dateFormat) { 336 this.json.setDateTimeFormat(dateFormat); 337 return this; 338 } 339 340 public ApiClient setLocalDateFormat(DateTimeFormatter dateFormat) { 341 this.json.setLocalDateFormat(dateFormat); 342 return this; 343 } 344 345 {{/joda}} 346 {{#jsr310}} 347 public ApiClient setOffsetDateTimeFormat(DateTimeFormatter dateFormat) { 348 this.json.setOffsetDateTimeFormat(dateFormat); 349 return this; 350 } 351 352 public ApiClient setLocalDateFormat(DateTimeFormatter dateFormat) { 353 this.json.setLocalDateFormat(dateFormat); 354 return this; 355 } 356 357 {{/jsr310}} 358 public ApiClient setLenientOnJson(boolean lenientOnJson) { 359 this.json.setLenientOnJson(lenientOnJson); 360 return this; 361 } 362 363 /** 364 * Get authentications (key: authentication name, value: authentication). 365 * 366 * @return Map of authentication objects 367 */ 368 public Map<String, Authentication> getAuthentications() { 369 return authentications; 370 } 371 372 /** 373 * Get authentication for the given name. 374 * 375 * @param authName The authentication name 376 * @return The authentication, null if not found 377 */ 378 public Authentication getAuthentication(String authName) { 379 return authentications.get(authName); 380 } 381 382 /** 383 * Helper method to set username for the first HTTP basic authentication. 384 * 385 * @param username Username 386 */ 387 public void setUsername(String username) { 388 for (Authentication auth : authentications.values()) { 389 if (auth instanceof HttpBasicAuth) { 390 ((HttpBasicAuth) auth).setUsername(username); 391 return; 392 } 393 } 394 throw new RuntimeException("No HTTP basic authentication configured!"); 395 } 396 397 /** 398 * Helper method to set password for the first HTTP basic authentication. 399 * 400 * @param password Password 401 */ 402 public void setPassword(String password) { 403 for (Authentication auth : authentications.values()) { 404 if (auth instanceof HttpBasicAuth) { 405 ((HttpBasicAuth) auth).setPassword(password); 406 return; 407 } 408 } 409 throw new RuntimeException("No HTTP basic authentication configured!"); 410 } 411 412 /** 413 * Helper method to set API key value for the first API key authentication. 414 * 415 * @param apiKey API key 416 */ 417 public void setApiKey(String apiKey) { 418 for (Authentication auth : authentications.values()) { 419 if (auth instanceof ApiKeyAuth) { 420 ((ApiKeyAuth) auth).setApiKey(apiKey); 421 return; 422 } 423 } 424 throw new RuntimeException("No API key authentication configured!"); 425 } 426 427 /** 428 * Helper method to set API key prefix for the first API key authentication. 429 * 430 * @param apiKeyPrefix API key prefix 431 */ 432 public void setApiKeyPrefix(String apiKeyPrefix) { 433 for (Authentication auth : authentications.values()) { 434 if (auth instanceof ApiKeyAuth) { 435 ((ApiKeyAuth) auth).setApiKeyPrefix(apiKeyPrefix); 436 return; 437 } 438 } 439 throw new RuntimeException("No API key authentication configured!"); 440 } 441 442 /** 443 * Helper method to set access token for the first OAuth2 authentication. 444 * 445 * @param accessToken Access token 446 */ 447 public void setAccessToken(String accessToken) { 448 {{#hasOAuthMethods}} 449 for (Authentication auth : authentications.values()) { 450 if (auth instanceof OAuth) { 451 ((OAuth) auth).setAccessToken(accessToken); 452 return; 453 } 454 } 455 {{/hasOAuthMethods}} 456 throw new RuntimeException("No OAuth2 authentication configured!"); 457 } 458 459 /** 460 * Set the User-Agent header's value (by adding to the default header map). 461 * 462 * @param userAgent HTTP request's user agent 463 * @return ApiClient 464 */ 465 public ApiClient setUserAgent(String userAgent) { 466 addDefaultHeader("User-Agent", userAgent); 467 return this; 468 } 469 470 /** 471 * Add a default header. 472 * 473 * @param key The header's key 474 * @param value The header's value 475 * @return ApiClient 476 */ 477 public ApiClient addDefaultHeader(String key, String value) { 478 defaultHeaderMap.put(key, value); 479 return this; 480 } 481 482 /** 483 * Add a default cookie. 484 * 485 * @param key The cookie's key 486 * @param value The cookie's value 487 * @return ApiClient 488 */ 489 public ApiClient addDefaultCookie(String key, String value) { 490 defaultCookieMap.put(key, value); 491 return this; 492 } 493 494 /** 495 * Check that whether debugging is enabled for this API client. 496 * 497 * @return True if debugging is enabled, false otherwise. 498 */ 499 public boolean isDebugging() { 500 return debugging; 501 } 502 503 /** 504 * Enable/disable debugging for this API client. 505 * 506 * @param debugging To enable (true) or disable (false) debugging 507 * @return ApiClient 508 */ 509 public ApiClient setDebugging(boolean debugging) { 510 if (debugging != this.debugging) { 511 if (debugging) { 512 loggingInterceptor = new HttpLoggingInterceptor(); 513 loggingInterceptor.setLevel(Level.BODY); 514 httpClient = httpClient.newBuilder().addInterceptor(loggingInterceptor).build(); 515 } else { 516 httpClient.interceptors().remove(loggingInterceptor); 517 loggingInterceptor = null; 518 } 519 } 520 this.debugging = debugging; 521 return this; 522 } 523 524 /** 525 * The path of temporary folder used to store downloaded files from endpoints 526 * with file response. The default value is <code>null</code>, i.e. using 527 * the system's default tempopary folder. 528 * 529 * @see <a href="https://docs.oracle.com/javase/7/docs/api/java/io/File.html#createTempFile">createTempFile</a> 530 * @return Temporary folder path 531 */ 532 public String getTempFolderPath() { 533 return tempFolderPath; 534 } 535 536 /** 537 * Set the temporary folder path (for downloading files) 538 * 539 * @param tempFolderPath Temporary folder path 540 * @return ApiClient 541 */ 542 public ApiClient setTempFolderPath(String tempFolderPath) { 543 this.tempFolderPath = tempFolderPath; 544 return this; 545 } 546 547 /** 548 * Get connection timeout (in milliseconds). 549 * 550 * @return Timeout in milliseconds 551 */ 552 public int getConnectTimeout() { 553 return httpClient.connectTimeoutMillis(); 554 } 555 556 /** 557 * Sets the connect timeout (in milliseconds). 558 * A value of 0 means no timeout, otherwise values must be between 1 and 559 * {@link Integer#MAX_VALUE}. 560 * 561 * @param connectionTimeout connection timeout in milliseconds 562 * @return Api client 563 */ 564 public ApiClient setConnectTimeout(int connectionTimeout) { 565 httpClient = httpClient.newBuilder().connectTimeout(connectionTimeout, TimeUnit.MILLISECONDS).build(); 566 return this; 567 } 568 569 /** 570 * Get read timeout (in milliseconds). 571 * 572 * @return Timeout in milliseconds 573 */ 574 public int getReadTimeout() { 575 return httpClient.readTimeoutMillis(); 576 } 577 578 /** 579 * Sets the read timeout (in milliseconds). 580 * A value of 0 means no timeout, otherwise values must be between 1 and 581 * {@link Integer#MAX_VALUE}. 582 * 583 * @param readTimeout read timeout in milliseconds 584 * @return Api client 585 */ 586 public ApiClient setReadTimeout(int readTimeout) { 587 httpClient = httpClient.newBuilder().readTimeout(readTimeout, TimeUnit.MILLISECONDS).build(); 588 return this; 589 } 590 591 /** 592 * Get write timeout (in milliseconds). 593 * 594 * @return Timeout in milliseconds 595 */ 596 public int getWriteTimeout() { 597 return httpClient.writeTimeoutMillis(); 598 } 599 600 /** 601 * Sets the write timeout (in milliseconds). 602 * A value of 0 means no timeout, otherwise values must be between 1 and 603 * {@link Integer#MAX_VALUE}. 604 * 605 * @param writeTimeout connection timeout in milliseconds 606 * @return Api client 607 */ 608 public ApiClient setWriteTimeout(int writeTimeout) { 609 httpClient = httpClient.newBuilder().writeTimeout(writeTimeout, TimeUnit.MILLISECONDS).build(); 610 return this; 611 } 612 613 {{#hasOAuthMethods}} 614 /** 615 * Helper method to configure the token endpoint of the first oauth found in the apiAuthorizations (there should be only one) 616 * 617 * @return Token request builder 618 */ 619 public TokenRequestBuilder getTokenEndPoint() { 620 for (Authentication apiAuth : authentications.values()) { 621 if (apiAuth instanceof RetryingOAuth) { 622 RetryingOAuth retryingOAuth = (RetryingOAuth) apiAuth; 623 return retryingOAuth.getTokenRequestBuilder(); 624 } 625 } 626 return null; 627 } 628 {{/hasOAuthMethods}} 629 630 /** 631 * Format the given parameter object into string. 632 * 633 * @param param Parameter 634 * @return String representation of the parameter 635 */ 636 public String parameterToString(Object param) { 637 if (param == null) { 638 return ""; 639 } else if (param instanceof Date {{#joda}}|| param instanceof DateTime || param instanceof LocalDate{{/joda}}{{#jsr310}}|| param instanceof OffsetDateTime || param instanceof LocalDate{{/jsr310}}) { 640 //Serialize to json string and remove the " enclosing characters 641 String jsonStr = json.serialize(param); 642 return jsonStr.substring(1, jsonStr.length() - 1); 643 } else if (param instanceof Collection) { 644 StringBuilder b = new StringBuilder(); 645 for (Object o : (Collection) param) { 646 if (b.length() > 0) { 647 b.append(","); 648 } 649 b.append(String.valueOf(o)); 650 } 651 return b.toString(); 652 } else { 653 return String.valueOf(param); 654 } 655 } 656 657 public List<Pair> mappedParameterToPairs(String name, Object parameter){ 658 List<Pair> params = new ArrayList<Pair>(); 659 660 if(parameter instanceof Map){ 661 Map<String, Object> mappedParameter = (Map<String, Object>) parameter; 662 663 for(Map.Entry<String, Object> entry : mappedParameter.entrySet()){ 664 String key = name + "[" + entry.getKey() + "]"; 665 Object value = entry.getValue(); 666 667 {{! TODO check why nested params ordering is different }} 668 params.addAll(parsedMappedParams(key, value, new ArrayList<Pair>())); 669 } 670 671 } 672 673 674 return params; 675 } 676 677 678 /** 679 * Formats the specified query parameter to a list containing a single {@code Pair} object. 680 * 681 * Note that {@code value} must not be a collection. 682 * 683 * @param name The name of the parameter. 684 * @param value The value of the parameter. 685 * @return A list containing a single {@code Pair} object. 686 */ 687 public List<Pair> parameterToPair(String name, Object value) { 688 List<Pair> params = new ArrayList<Pair>(); 689 690 // preconditions 691 if (name == null || name.isEmpty() || value == null || value instanceof Collection) { 692 return params; 693 } 694 695 params.add(new Pair(name, parameterToString(value))); 696 return params; 697 } 698 699 /** 700 * Formats the specified collection query parameters to a list of {@code Pair} objects. 701 * 702 * Note that the values of each of the returned Pair objects are percent-encoded. 703 * 704 * @param collectionFormat The collection format of the parameter. 705 * @param name The name of the parameter. 706 * @param value The value of the parameter. 707 * @return A list of {@code Pair} objects. 708 */ 709 public List<Pair> parameterToPairs(String collectionFormat, String name, Collection value) { 710 List<Pair> params = new ArrayList<Pair>(); 711 712 // preconditions 713 if (name == null || name.isEmpty() || value == null || value.isEmpty()) { 714 return params; 715 } 716 717 // create the params based on the collection format 718 if ("multi".equals(collectionFormat)) { 719 for (Object item : value) { 720 params.add(new Pair(name, escapeString(parameterToString(item)))); 721 } 722 return params; 723 } 724 725 // collectionFormat is assumed to be "csv" by default 726 String delimiter = ","; 727 728 // escape all delimiters except commas, which are URI reserved 729 // characters 730 if ("ssv".equals(collectionFormat)) { 731 delimiter = escapeString(" "); 732 } else if ("tsv".equals(collectionFormat)) { 733 delimiter = escapeString("\t"); 734 } else if ("pipes".equals(collectionFormat)) { 735 delimiter = escapeString("|"); 736 } 737 738 StringBuilder sb = new StringBuilder(); 739 for (Object item : value) { 740 sb.append(delimiter); 741 sb.append(escapeString(parameterToString(item))); 742 } 743 744 params.add(new Pair(name, sb.substring(delimiter.length()))); 745 746 return params; 747 } 748 749 /** 750 * Formats the specified collection path parameter to a string value. 751 * 752 * @param collectionFormat The collection format of the parameter. 753 * @param value The value of the parameter. 754 * @return String representation of the parameter 755 */ 756 public String collectionPathParameterToString(String collectionFormat, Collection value) { 757 // create the value based on the collection format 758 if ("multi".equals(collectionFormat)) { 759 // not valid for path params 760 return parameterToString(value); 761 } 762 763 // collectionFormat is assumed to be "csv" by default 764 String delimiter = ","; 765 766 if ("ssv".equals(collectionFormat)) { 767 delimiter = " "; 768 } else if ("tsv".equals(collectionFormat)) { 769 delimiter = "\t"; 770 } else if ("pipes".equals(collectionFormat)) { 771 delimiter = "|"; 772 } 773 774 StringBuilder sb = new StringBuilder() ; 775 for (Object item : value) { 776 sb.append(delimiter); 777 sb.append(parameterToString(item)); 778 } 779 780 return sb.substring(delimiter.length()); 781 } 782 783 /** 784 * Sanitize filename by removing path. 785 * e.g. ../../sun.gif becomes sun.gif 786 * 787 * @param filename The filename to be sanitized 788 * @return The sanitized filename 789 */ 790 public String sanitizeFilename(String filename) { 791 return filename.replaceAll(".*[/\\\\]", ""); 792 } 793 794 /** 795 * Check if the given MIME is a JSON MIME. 796 * JSON MIME examples: 797 * application/json 798 * application/json; charset=UTF8 799 * APPLICATION/JSON 800 * application/vnd.company+json 801 * "* / *" is also default to JSON 802 * @param mime MIME (Multipurpose Internet Mail Extensions) 803 * @return True if the given MIME is JSON, false otherwise. 804 */ 805 public boolean isJsonMime(String mime) { 806 String jsonMime = "(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$"; 807 return mime != null && (mime.matches(jsonMime) || mime.equals("*/*")); 808 } 809 810 /** 811 * Select the Accept header's value from the given accepts array: 812 * if JSON exists in the given array, use it; 813 * otherwise use all of them (joining into a string) 814 * 815 * @param accepts The accepts array to select from 816 * @return The Accept header to use. If the given array is empty, 817 * null will be returned (not to set the Accept header explicitly). 818 */ 819 public String selectHeaderAccept(String[] accepts) { 820 if (accepts.length == 0) { 821 return null; 822 } 823 for (String accept : accepts) { 824 if (isJsonMime(accept)) { 825 return accept; 826 } 827 } 828 return StringUtil.join(accepts, ","); 829 } 830 831 /** 832 * Select the Content-Type header's value from the given array: 833 * if JSON exists in the given array, use it; 834 * otherwise use the first one of the array. 835 * 836 * @param contentTypes The Content-Type array to select from 837 * @return The Content-Type header to use. If the given array is empty, 838 * or matches "any", JSON will be used. 839 */ 840 public String selectHeaderContentType(String[] contentTypes) { 841 if (contentTypes.length == 0 || contentTypes[0].equals("*/*")) { 842 return "application/json"; 843 } 844 for (String contentType : contentTypes) { 845 if (isJsonMime(contentType)) { 846 return contentType; 847 } 848 } 849 return contentTypes[0]; 850 } 851 852 /** 853 * Escape the given string to be used as URL query value. 854 * 855 * @param str String to be escaped 856 * @return Escaped string 857 */ 858 public String escapeString(String str) { 859 try { 860 return URLEncoder.encode(str, "utf8").replaceAll("\\+", "%20"); 861 } catch (UnsupportedEncodingException e) { 862 return str; 863 } 864 } 865 866 /** 867 * Deserialize response body to Java object, according to the return type and 868 * the Content-Type response header. 869 * 870 * @param <T> Type 871 * @param response HTTP response 872 * @param returnType The type of the Java object 873 * @return The deserialized Java object 874 * @throws ApiException If fail to deserialize response body, i.e. cannot read response body 875 * or the Content-Type of the response is not supported. 876 */ 877 @SuppressWarnings("unchecked") 878 public <T> T deserialize(Response response, Type returnType) throws ApiException { 879 if (response == null || returnType == null) { 880 return null; 881 } 882 883 if ("byte[]".equals(returnType.toString())) { 884 // Handle binary response (byte array). 885 try { 886 return (T) response.body().bytes(); 887 } catch (IOException e) { 888 throw new ApiException(e); 889 } 890 } else if (returnType.equals(File.class)) { 891 // Handle file downloading. 892 return (T) downloadFileFromResponse(response); 893 } 894 895 String respBody; 896 try { 897 if (response.body() != null) 898 respBody = response.body().string(); 899 else 900 respBody = null; 901 } catch (IOException e) { 902 throw new ApiException(e); 903 } 904 905 if (respBody == null || "".equals(respBody)) { 906 return null; 907 } 908 909 String contentType = response.headers().get("Content-Type"); 910 if (contentType == null) { 911 // ensuring a default content type 912 contentType = "application/json"; 913 } 914 if (isJsonMime(contentType)) { 915 return json.deserialize(respBody, returnType); 916 } else if (returnType.equals(String.class)) { 917 // Expecting string, return the raw response body. 918 return (T) respBody; 919 } else { 920 throw new ApiException( 921 "Content type \"" + contentType + "\" is not supported for type: " + returnType, 922 response.code(), 923 response.headers().toMultimap(), 924 respBody); 925 } 926 } 927 928 /** 929 * Serialize the given Java object into request body according to the object's 930 * class and the request Content-Type. 931 * 932 * @param obj The Java object 933 * @param contentType The request Content-Type 934 * @return The serialized request body 935 * @throws ApiException If fail to serialize the given object 936 */ 937 public RequestBody serialize(Object obj, String contentType) throws ApiException { 938 if (obj instanceof byte[]) { 939 // Binary (byte array) body parameter support. 940 return RequestBody.create(MediaType.parse(contentType), (byte[]) obj); 941 } else if (obj instanceof File) { 942 // File body parameter support. 943 return RequestBody.create(MediaType.parse(contentType), (File) obj); 944 } else if (isJsonMime(contentType)) { 945 String content; 946 if (obj != null) { 947 content = json.serialize(obj); 948 } else { 949 content = null; 950 } 951 return RequestBody.create(MediaType.parse(contentType), content); 952 } else { 953 throw new ApiException("Content type \"" + contentType + "\" is not supported"); 954 } 955 } 956 957 /** 958 * Download file from the given response. 959 * 960 * @param response An instance of the Response object 961 * @throws ApiException If fail to read file content from response and write to disk 962 * @return Downloaded file 963 */ 964 public File downloadFileFromResponse(Response response) throws ApiException { 965 try { 966 File file = prepareDownloadFile(response); 967 BufferedSink sink = Okio.buffer(Okio.sink(file)); 968 sink.writeAll(response.body().source()); 969 sink.close(); 970 return file; 971 } catch (IOException e) { 972 throw new ApiException(e); 973 } 974 } 975 976 /** 977 * Prepare file for download 978 * 979 * @param response An instance of the Response object 980 * @return Prepared file for the download 981 * @throws IOException If fail to prepare file for download 982 */ 983 public File prepareDownloadFile(Response response) throws IOException { 984 String filename = null; 985 String contentDisposition = response.header("Content-Disposition"); 986 if (contentDisposition != null && !"".equals(contentDisposition)) { 987 // Get filename from the Content-Disposition header. 988 Pattern pattern = Pattern.compile("filename=['\"]?([^'\"\\s]+)['\"]?"); 989 Matcher matcher = pattern.matcher(contentDisposition); 990 if (matcher.find()) { 991 filename = sanitizeFilename(matcher.group(1)); 992 } 993 } 994 995 String prefix = null; 996 String suffix = null; 997 if (filename == null) { 998 prefix = "download-"; 999 suffix = ""; 1000 } else { 1001 int pos = filename.lastIndexOf("."); 1002 if (pos == -1) { 1003 prefix = filename + "-"; 1004 } else { 1005 prefix = filename.substring(0, pos) + "-"; 1006 suffix = filename.substring(pos); 1007 } 1008 // File.createTempFile requires the prefix to be at least three characters long 1009 if (prefix.length() < 3) 1010 prefix = "download-"; 1011 } 1012 1013 if (tempFolderPath == null) 1014 return File.createTempFile(prefix, suffix); 1015 else 1016 return File.createTempFile(prefix, suffix, new File(tempFolderPath)); 1017 } 1018 1019 /** 1020 * {@link #execute(Call, Type)} 1021 * 1022 * @param <T> Type 1023 * @param call An instance of the Call object 1024 * @return ApiResponse<T> 1025 * @throws ApiException If fail to execute the call 1026 */ 1027 public <T> ApiResponse<T> execute(Call call) throws ApiException { 1028 return execute(call, null); 1029 } 1030 1031 /** 1032 * Execute HTTP call and deserialize the HTTP response body into the given return type. 1033 * 1034 * @param returnType The return type used to deserialize HTTP response body 1035 * @param <T> The return type corresponding to (same with) returnType 1036 * @param call Call 1037 * @return ApiResponse object containing response status, headers and 1038 * data, which is a Java object deserialized from response body and would be null 1039 * when returnType is null. 1040 * @throws ApiException If fail to execute the call 1041 */ 1042 public <T> ApiResponse<T> execute(Call call, Type returnType) throws ApiException { 1043 try { 1044 Response response = call.execute(); 1045 T data = handleResponse(response, returnType); 1046 return new ApiResponse<T>(response.code(), response.headers().toMultimap(), data); 1047 } catch (IOException e) { 1048 throw new ApiException(e); 1049 } 1050 } 1051 1052 /** 1053 * {@link #executeAsync(Call, Type, ApiCallback)} 1054 * 1055 * @param <T> Type 1056 * @param call An instance of the Call object 1057 * @param callback ApiCallback<T> 1058 */ 1059 public <T> void executeAsync(Call call, ApiCallback<T> callback) { 1060 executeAsync(call, null, callback); 1061 } 1062 1063 /** 1064 * Execute HTTP call asynchronously. 1065 * 1066 * @param <T> Type 1067 * @param call The callback to be executed when the API call finishes 1068 * @param returnType Return type 1069 * @param callback ApiCallback 1070 * @see #execute(Call, Type) 1071 */ 1072 @SuppressWarnings("unchecked") 1073 public <T> void executeAsync(Call call, final Type returnType, final ApiCallback<T> callback) { 1074 call.enqueue(new Callback() { 1075 @Override 1076 public void onFailure(Call call, IOException e) { 1077 callback.onFailure(new ApiException(e), 0, null); 1078 } 1079 1080 @Override 1081 public void onResponse(Call call, Response response) throws IOException { 1082 T result; 1083 try { 1084 result = (T) handleResponse(response, returnType); 1085 } catch (ApiException e) { 1086 callback.onFailure(e, response.code(), response.headers().toMultimap()); 1087 return; 1088 } 1089 callback.onSuccess(result, response.code(), response.headers().toMultimap()); 1090 } 1091 }); 1092 } 1093 1094 /** 1095 * Handle the given response, return the deserialized object when the response is successful. 1096 * 1097 * @param <T> Type 1098 * @param response Response 1099 * @param returnType Return type 1100 * @return Type 1101 * @throws ApiException If the response has an unsuccessful status code or 1102 * fail to deserialize the response body 1103 */ 1104 public <T> T handleResponse(Response response, Type returnType) throws ApiException { 1105 if (response.isSuccessful()) { 1106 if (returnType == null || response.code() == 204) { 1107 // returning null if the returnType is not defined, 1108 // or the status code is 204 (No Content) 1109 if (response.body() != null) { 1110 try { 1111 response.body().close(); 1112 } catch (Exception e) { 1113 throw new ApiException(response.message(), e, response.code(), response.headers().toMultimap()); 1114 } 1115 } 1116 return null; 1117 } else { 1118 return deserialize(response, returnType); 1119 } 1120 } else { 1121 String respBody = null; 1122 if (response.body() != null) { 1123 try { 1124 respBody = response.body().string(); 1125 } catch (IOException e) { 1126 throw new ApiException(response.message(), e, response.code(), response.headers().toMultimap()); 1127 } 1128 } 1129 throw new ApiException(response.message(), response.code(), response.headers().toMultimap(), respBody); 1130 } 1131 } 1132 1133 /** 1134 * Build HTTP call with the given options. 1135 * 1136 * @param path The sub-path of the HTTP URL 1137 * @param method The request method, one of "GET", "HEAD", "OPTIONS", "POST", "PUT", "PATCH" and "DELETE" 1138 * @param queryParams The query parameters 1139 * @param collectionQueryParams The collection query parameters 1140 * @param body The request body object 1141 * @param headerParams The header parameters 1142 * @param cookieParams The cookie parameters 1143 * @param formParams The form parameters 1144 * @param authNames The authentications to apply 1145 * @param callback Callback for upload/download progress 1146 * @return The HTTP call 1147 * @throws ApiException If fail to serialize the request body object 1148 */ 1149 public Call buildCall(String path, String method, List<Pair> queryParams, List<Pair> collectionQueryParams, Object body, Map<String, String> headerParams, Map<String, String> cookieParams, Map<String, Object> formParams, String[] authNames, ApiCallback callback) throws ApiException { 1150 Request request = buildRequest(path, method, queryParams, collectionQueryParams, body, headerParams, cookieParams, formParams, authNames, callback); 1151 1152 return httpClient.newCall(request); 1153 } 1154 1155 /** 1156 * Build an HTTP request with the given options. 1157 * 1158 * @param path The sub-path of the HTTP URL 1159 * @param method The request method, one of "GET", "HEAD", "OPTIONS", "POST", "PUT", "PATCH" and "DELETE" 1160 * @param queryParams The query parameters 1161 * @param collectionQueryParams The collection query parameters 1162 * @param body The request body object 1163 * @param headerParams The header parameters 1164 * @param cookieParams The cookie parameters 1165 * @param formParams The form parameters 1166 * @param authNames The authentications to apply 1167 * @param callback Callback for upload/download progress 1168 * @return The HTTP request 1169 * @throws ApiException If fail to serialize the request body object 1170 */ 1171 public Request buildRequest(String path, String method, List<Pair> queryParams, List<Pair> collectionQueryParams, Object body, Map<String, String> headerParams, Map<String, String> cookieParams, Map<String, Object> formParams, String[] authNames, ApiCallback callback) throws ApiException { 1172 updateParamsForAuth(authNames, queryParams, headerParams, cookieParams); 1173 1174 final String url = buildUrl(path, queryParams, collectionQueryParams); 1175 final Request.Builder reqBuilder = new Request.Builder().url(url); 1176 processHeaderParams(headerParams, reqBuilder); 1177 processCookieParams(cookieParams, reqBuilder); 1178 1179 String contentType = (String) headerParams.get("Content-Type"); 1180 // ensuring a default content type 1181 if (contentType == null) { 1182 contentType = "application/json"; 1183 } 1184 1185 RequestBody reqBody; 1186 if (!HttpMethod.permitsRequestBody(method)) { 1187 reqBody = null; 1188 } else if ("application/x-www-form-urlencoded".equals(contentType)) { 1189 reqBody = buildRequestBodyFormEncoding(formParams); 1190 } else if ("multipart/form-data".equals(contentType)) { 1191 reqBody = buildRequestBodyMultipart(formParams); 1192 } else if (body == null) { 1193 if ("DELETE".equals(method)) { 1194 // allow calling DELETE without sending a request body 1195 reqBody = null; 1196 } else { 1197 // use an empty request body (for POST, PUT and PATCH) 1198 reqBody = RequestBody.create(MediaType.parse(contentType), ""); 1199 } 1200 } else { 1201 reqBody = serialize(body, contentType); 1202 } 1203 1204 // Associate callback with request (if not null) so interceptor can 1205 // access it when creating ProgressResponseBody 1206 reqBuilder.tag(callback); 1207 1208 Request request = null; 1209 1210 if (callback != null && reqBody != null) { 1211 ProgressRequestBody progressRequestBody = new ProgressRequestBody(reqBody, callback); 1212 request = reqBuilder.method(method, progressRequestBody).build(); 1213 } else { 1214 request = reqBuilder.method(method, reqBody).build(); 1215 } 1216 1217 return request; 1218 } 1219 1220 /** 1221 * Build full URL by concatenating base path, the given sub path and query parameters. 1222 * 1223 * @param path The sub path 1224 * @param queryParams The query parameters 1225 * @param collectionQueryParams The collection query parameters 1226 * @return The full URL 1227 */ 1228 public String buildUrl(String path, List<Pair> queryParams, List<Pair> collectionQueryParams) { 1229 final StringBuilder url = new StringBuilder(); 1230 url.append(basePath).append(path); 1231 1232 if (queryParams != null && !queryParams.isEmpty()) { 1233 // support (constant) query string in `path`, e.g. "/posts?draft=1" 1234 String prefix = path.contains("?") ? "&" : "?"; 1235 for (Pair param : queryParams) { 1236 if (param.getValue() != null) { 1237 if (prefix != null) { 1238 url.append(prefix); 1239 prefix = null; 1240 } else { 1241 url.append("&"); 1242 } 1243 String value = parameterToString(param.getValue()); 1244 url.append(escapeString(param.getName())).append("=").append(escapeString(value)); 1245 } 1246 } 1247 } 1248 1249 if (collectionQueryParams != null && !collectionQueryParams.isEmpty()) { 1250 String prefix = url.toString().contains("?") ? "&" : "?"; 1251 for (Pair param : collectionQueryParams) { 1252 if (param.getValue() != null) { 1253 if (prefix != null) { 1254 url.append(prefix); 1255 prefix = null; 1256 } else { 1257 url.append("&"); 1258 } 1259 String value = parameterToString(param.getValue()); 1260 // collection query parameter value already escaped as part of parameterToPairs 1261 url.append(escapeString(param.getName())).append("=").append(value); 1262 } 1263 } 1264 } 1265 1266 return url.toString(); 1267 } 1268 1269 /** 1270 * Set header parameters to the request builder, including default headers. 1271 * 1272 * @param headerParams Header parameters in the form of Map 1273 * @param reqBuilder Request.Builder 1274 */ 1275 public void processHeaderParams(Map<String, String> headerParams, Request.Builder reqBuilder) { 1276 for (Entry<String, String> param : headerParams.entrySet()) { 1277 reqBuilder.header(param.getKey(), parameterToString(param.getValue())); 1278 } 1279 for (Entry<String, String> header : defaultHeaderMap.entrySet()) { 1280 if (!headerParams.containsKey(header.getKey())) { 1281 reqBuilder.header(header.getKey(), parameterToString(header.getValue())); 1282 } 1283 } 1284 } 1285 1286 /** 1287 * Set cookie parameters to the request builder, including default cookies. 1288 * 1289 * @param cookieParams Cookie parameters in the form of Map 1290 * @param reqBuilder Request.Builder 1291 */ 1292 public void processCookieParams(Map<String, String> cookieParams, Request.Builder reqBuilder) { 1293 for (Entry<String, String> param : cookieParams.entrySet()) { 1294 reqBuilder.addHeader("Cookie", String.format("%s=%s", param.getKey(), param.getValue())); 1295 } 1296 for (Entry<String, String> param : defaultCookieMap.entrySet()) { 1297 if (!cookieParams.containsKey(param.getKey())) { 1298 reqBuilder.addHeader("Cookie", String.format("%s=%s", param.getKey(), param.getValue())); 1299 } 1300 } 1301 } 1302 1303 /** 1304 * Update query and header parameters based on authentication settings. 1305 * 1306 * @param authNames The authentications to apply 1307 * @param queryParams List of query parameters 1308 * @param headerParams Map of header parameters 1309 * @param cookieParams Map of cookie parameters 1310 */ 1311 public void updateParamsForAuth(String[] authNames, List<Pair> queryParams, Map<String, String> headerParams, Map<String, String> cookieParams) { 1312 for (String authName : authNames) { 1313 Authentication auth = authentications.get(authName); 1314 if (auth == null) { 1315 throw new RuntimeException("Authentication undefined: " + authName); 1316 } 1317 auth.applyToParams(queryParams, headerParams, cookieParams); 1318 } 1319 } 1320 1321 /** 1322 * Build a form-encoding request body with the given form parameters. 1323 * 1324 * @param formParams Form parameters in the form of Map 1325 * @return RequestBody 1326 */ 1327 public RequestBody buildRequestBodyFormEncoding(Map<String, Object> formParams) { 1328 okhttp3.FormBody.Builder formBuilder = new okhttp3.FormBody.Builder(); 1329 for (Entry<String, Object> param : formParams.entrySet()) { 1330 formBuilder.add(param.getKey(), parameterToString(param.getValue())); 1331 } 1332 return formBuilder.build(); 1333 } 1334 1335 /** 1336 * Build a multipart (file uploading) request body with the given form parameters, 1337 * which could contain text fields and file fields. 1338 * 1339 * @param formParams Form parameters in the form of Map 1340 * @return RequestBody 1341 */ 1342 public RequestBody buildRequestBodyMultipart(Map<String, Object> formParams) { 1343 MultipartBody.Builder mpBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM); 1344 for (Entry<String, Object> param : formParams.entrySet()) { 1345 if (param.getValue() instanceof File) { 1346 File file = (File) param.getValue(); 1347 Headers partHeaders = Headers.of("Content-Disposition", "form-data; name=\"" + param.getKey() + "\"; filename=\"" + file.getName() + "\""); 1348 MediaType mediaType = MediaType.parse(guessContentTypeFromFile(file)); 1349 mpBuilder.addPart(partHeaders, RequestBody.create(mediaType, file)); 1350 } else { 1351 Headers partHeaders = Headers.of("Content-Disposition", "form-data; name=\"" + param.getKey() + "\""); 1352 mpBuilder.addPart(partHeaders, RequestBody.create(null, parameterToString(param.getValue()))); 1353 } 1354 } 1355 return mpBuilder.build(); 1356 } 1357 1358 /** 1359 * Guess Content-Type header from the given file (defaults to "application/octet-stream"). 1360 * 1361 * @param file The given file 1362 * @return The guessed Content-Type 1363 */ 1364 public String guessContentTypeFromFile(File file) { 1365 String contentType = URLConnection.guessContentTypeFromName(file.getName()); 1366 if (contentType == null) { 1367 return "application/octet-stream"; 1368 } else { 1369 return contentType; 1370 } 1371 } 1372 1373 /** 1374 * Get network interceptor to add it to the httpClient to track download progress for 1375 * async requests. 1376 */ 1377 private Interceptor getProgressInterceptor() { 1378 return new Interceptor() { 1379 @Override 1380 public Response intercept(Interceptor.Chain chain) throws IOException { 1381 final Request request = chain.request(); 1382 final Response originalResponse = chain.proceed(request); 1383 if (request.tag() instanceof ApiCallback) { 1384 final ApiCallback callback = (ApiCallback) request.tag(); 1385 return originalResponse.newBuilder() 1386 .body(new ProgressResponseBody(originalResponse.body(), callback)) 1387 .build(); 1388 } 1389 return originalResponse; 1390 } 1391 }; 1392 } 1393 1394 /** 1395 * Apply SSL related settings to httpClient according to the current values of 1396 * verifyingSsl and sslCaCert. 1397 */ 1398 private void applySslSettings() { 1399 try { 1400 TrustManager[] trustManagers; 1401 HostnameVerifier hostnameVerifier; 1402 if (!verifyingSsl) { 1403 trustManagers = new TrustManager[]{ 1404 new X509TrustManager() { 1405 @Override 1406 public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { 1407 } 1408 1409 @Override 1410 public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { 1411 } 1412 1413 @Override 1414 public java.security.cert.X509Certificate[] getAcceptedIssuers() { 1415 return new java.security.cert.X509Certificate[]{}; 1416 } 1417 } 1418 }; 1419 hostnameVerifier = new HostnameVerifier() { 1420 @Override 1421 public boolean verify(String hostname, SSLSession session) { 1422 return true; 1423 } 1424 }; 1425 } else { 1426 TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 1427 1428 if (sslCaCert == null) { 1429 trustManagerFactory.init((KeyStore) null); 1430 } else { 1431 char[] password = null; // Any password will work. 1432 CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); 1433 Collection<? extends Certificate> certificates = certificateFactory.generateCertificates(sslCaCert); 1434 if (certificates.isEmpty()) { 1435 throw new IllegalArgumentException("expected non-empty set of trusted certificates"); 1436 } 1437 KeyStore caKeyStore = newEmptyKeyStore(password); 1438 int index = 0; 1439 for (Certificate certificate : certificates) { 1440 String certificateAlias = "ca" + Integer.toString(index++); 1441 caKeyStore.setCertificateEntry(certificateAlias, certificate); 1442 } 1443 trustManagerFactory.init(caKeyStore); 1444 } 1445 trustManagers = trustManagerFactory.getTrustManagers(); 1446 hostnameVerifier = OkHostnameVerifier.INSTANCE; 1447 } 1448 1449 SSLContext sslContext = SSLContext.getInstance("TLS"); 1450 sslContext.init(keyManagers, trustManagers, new SecureRandom()); 1451 httpClient = httpClient.newBuilder() 1452 .sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustManagers[0]) 1453 .hostnameVerifier(hostnameVerifier) 1454 .build(); 1455 } catch (GeneralSecurityException e) { 1456 throw new RuntimeException(e); 1457 } 1458 } 1459 1460 private KeyStore newEmptyKeyStore(char[] password) throws GeneralSecurityException { 1461 try { 1462 KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); 1463 keyStore.load(null, password); 1464 return keyStore; 1465 } catch (IOException e) { 1466 throw new AssertionError(e); 1467 } 1468 } 1469 1470 private List<Pair> parsedMappedParams(String key, Object value, List<Pair> params){ 1471 if(value instanceof Map){ 1472 Map<String, Object> mappedValue = (Map<String, Object>) value; 1473 1474 for(Map.Entry<String, Object> entry : mappedValue.entrySet()){ 1475 String nestedKey = key + "[" + entry.getKey() + "]"; 1476 parsedMappedParams(nestedKey, entry.getValue(), params); 1477 } 1478 } 1479 else{ 1480 params.add(new Pair(key, parameterToString(value))); 1481 } 1482 1483 return params; 1484 } 1485 1486 }