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&lt;T&gt;
  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&lt;T&gt;
  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  }