• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

box / box-java-sdk / #7612

30 Sep 2026 09:27AM UTC coverage: 35.105% (+0.04%) from 35.062%
#7612

push

github

web-flow
chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci]

Co-authored-by: box-sdk-build <box-sdk-build@box.com>

19976 of 56903 relevant lines covered (35.11%)

0.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.46
/src/main/java/com/box/sdkgen/networking/boxnetworkclient/BoxNetworkClient.java
1
package com.box.sdkgen.networking.boxnetworkclient;
2

3
import static com.box.sdkgen.box.BoxConstants.USER_AGENT_HEADER;
4
import static com.box.sdkgen.box.BoxConstants.X_BOX_UA_HEADER;
5
import static com.box.sdkgen.internal.utils.UtilsManager.readByteStream;
6
import static com.box.sdkgen.serialization.json.JsonManager.jsonToSerializedData;
7
import static com.box.sdkgen.serialization.json.JsonManager.sdToJson;
8
import static com.box.sdkgen.serialization.json.JsonManager.sdToUrlParams;
9
import static java.util.Collections.singletonList;
10
import static okhttp3.ConnectionSpec.MODERN_TLS;
11

12
import com.box.sdkgen.box.errors.BoxAPIError;
13
import com.box.sdkgen.box.errors.BoxSDKError;
14
import com.box.sdkgen.internal.logging.DataSanitizer;
15
import com.box.sdkgen.networking.fetchoptions.FetchOptions;
16
import com.box.sdkgen.networking.fetchoptions.MultipartItem;
17
import com.box.sdkgen.networking.fetchoptions.ResponseFormat;
18
import com.box.sdkgen.networking.fetchresponse.FetchResponse;
19
import com.box.sdkgen.networking.network.NetworkSession;
20
import com.box.sdkgen.networking.networkclient.NetworkClient;
21
import com.box.sdkgen.networking.proxyconfig.ProxyConfig;
22
import com.box.sdkgen.networking.timeoutconfig.TimeoutConfig;
23
import com.fasterxml.jackson.databind.JsonNode;
24
import java.io.IOException;
25
import java.net.InetSocketAddress;
26
import java.net.Proxy;
27
import java.net.URI;
28
import java.nio.charset.StandardCharsets;
29
import java.util.Locale;
30
import java.util.Map;
31
import java.util.Objects;
32
import java.util.Optional;
33
import java.util.TreeMap;
34
import java.util.concurrent.TimeUnit;
35
import java.util.stream.Collectors;
36
import okhttp3.Call;
37
import okhttp3.Credentials;
38
import okhttp3.Headers;
39
import okhttp3.HttpUrl;
40
import okhttp3.MediaType;
41
import okhttp3.MultipartBody;
42
import okhttp3.OkHttpClient;
43
import okhttp3.Request;
44
import okhttp3.RequestBody;
45
import okhttp3.Response;
46
import okio.BufferedSink;
47
import okio.Okio;
48
import okio.Source;
49

50
public class BoxNetworkClient implements NetworkClient {
51

52
  private static final int BASE_TIMEOUT = 1;
53
  private static final double RANDOM_FACTOR = 0.5;
54
  private static final int DEFAULT_HTTP_PORT = 80;
55
  private static final int DEFAULT_HTTPS_PORT = 443;
56

57
  protected OkHttpClient httpClient;
58

59
  public BoxNetworkClient(OkHttpClient httpClient) {
1 ✔
60
    this.httpClient = httpClient;
1 ✔
61
  }
1 ✔
62

63
  public BoxNetworkClient() {
1 ✔
64
    httpClient = getDefaultOkHttpClientBuilder().build();
1 ✔
65
  }
1 ✔
66

67
  public OkHttpClient getHttpClient() {
68
    return httpClient;
×
69
  }
70

71
  public BoxNetworkClient withProxy(ProxyConfig config) {
72
    URI uri = URI.create(config.getUrl());
×
73
    String host = Objects.requireNonNull(uri.getHost(), "Invalid Proxy URL");
×
74

75
    String scheme =
×
76
        Optional.ofNullable(uri.getScheme())
×
77
            .filter(schema -> schema.startsWith("http"))
×
78
            .orElseThrow(() -> new IllegalArgumentException("Invalid Proxy URL: " + uri));
×
79

80
    int port =
×
81
        (uri.getPort() != -1)
×
82
            ? uri.getPort()
×
83
            : ("https".equalsIgnoreCase(scheme) ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT);
×
84

85
    OkHttpClient.Builder clientBuilder =
×
86
        httpClient
87
            .newBuilder()
×
88
            .proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port)));
×
89

90
    String username = config.getUsername();
×
91
    String password = config.getPassword();
×
92
    if (username != null && !username.trim().isEmpty() && password != null) {
×
93
      String basic = Credentials.basic(username, password, StandardCharsets.UTF_8);
×
94
      clientBuilder.proxyAuthenticator(
×
95
          (route, resp) ->
96
              resp.request().newBuilder().header("Proxy-Authorization", basic).build());
×
97
    }
98
    return new BoxNetworkClient(clientBuilder.build());
×
99
  }
100

101
  public BoxNetworkClient withTimeoutConfig(TimeoutConfig config) {
102
    if (config == null) {
1 ✔
103
      throw new IllegalArgumentException("TimeoutConfig cannot be null");
×
104
    }
105

106
    OkHttpClient.Builder clientBuilder = httpClient.newBuilder();
1 ✔
107

108
    Long connectionTimeoutMs = config.getConnectionTimeoutMs();
1 ✔
109
    if (connectionTimeoutMs != null) {
1 ✔
110
      if (connectionTimeoutMs < 0) {
1 ✔
111
        throw new IllegalArgumentException("connectionTimeoutMs cannot be negative");
×
112
      }
113
      clientBuilder.connectTimeout(connectionTimeoutMs.longValue(), TimeUnit.MILLISECONDS);
1 ✔
114
    }
115

116
    Long readTimeoutMs = config.getReadTimeoutMs();
1 ✔
117
    if (readTimeoutMs != null) {
1 ✔
118
      if (readTimeoutMs < 0) {
1 ✔
119
        throw new IllegalArgumentException("readTimeoutMs cannot be negative");
×
120
      }
121
      clientBuilder.readTimeout(readTimeoutMs.longValue(), TimeUnit.MILLISECONDS);
1 ✔
122
    }
123

124
    Long requestTimeoutMs = config.getRequestTimeoutMs();
1 ✔
125
    if (requestTimeoutMs != null) {
1 ✔
126
      if (requestTimeoutMs < 0) {
1 ✔
127
        throw new IllegalArgumentException("requestTimeoutMs cannot be negative");
×
128
      }
129
      clientBuilder.callTimeout(requestTimeoutMs.longValue(), TimeUnit.MILLISECONDS);
1 ✔
130
    }
131
    return new BoxNetworkClient(clientBuilder.build());
1 ✔
132
  }
133

134
  public FetchResponse fetch(FetchOptions options) {
135
    NetworkSession networkSession =
1 ✔
136
        options.getNetworkSession() == null ? new NetworkSession() : options.getNetworkSession();
1 ✔
137

138
    FetchOptions fetchOptions =
1 ✔
139
        networkSession.getInterceptors().stream()
1 ✔
140
            .reduce(
1 ✔
141
                options,
142
                (modifiedOptions, interceptor) -> interceptor.beforeRequest(modifiedOptions),
1 ✔
143
                (o1, o2) -> o2);
×
144

145
    boolean authenticationNeeded = false;
1 ✔
146
    Request request;
147
    FetchResponse fetchResponse = new FetchResponse.Builder(0, new TreeMap<>()).build();
1 ✔
148
    Exception exceptionThrown = null;
1 ✔
149

150
    int attemptNumber = 1;
1 ✔
151
    int numberOfRetriesOnException = 0;
1 ✔
152
    int attemptForRetry = 0;
1 ✔
153
    boolean shouldRetry = false;
1 ✔
154

155
    while (true) {
156
      request = prepareRequest(fetchOptions, authenticationNeeded, networkSession);
1 ✔
157

158
      Response response = null;
1 ✔
159
      String rawResponseBody = null;
1 ✔
160

161
      try {
162
        response = executeOnClient(request);
1 ✔
163

164
        Map<String, String> headersMap =
1 ✔
165
            response.headers().toMultimap().entrySet().stream()
1 ✔
166
                .collect(
1 ✔
167
                    Collectors.toMap(
1 ✔
168
                        Map.Entry::getKey,
169
                        e -> e.getValue().get(0),
1 ✔
170
                        (existing, replacement) -> existing,
×
171
                        () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)));
1 ✔
172

173
        String responseUrl =
1 ✔
174
            response.networkResponse() != null
1 ✔
175
                ? response.networkResponse().request().url().toString()
1 ✔
176
                : response.request().url().toString();
1 ✔
177

178
        attemptForRetry = attemptNumber;
1 ✔
179

180
        if (Objects.equals(
1 ✔
181
            fetchOptions.getResponseFormat().getEnumValue(), ResponseFormat.BINARY)) {
1 ✔
182
          fetchResponse =
1 ✔
183
              new FetchResponse.Builder(response.code(), headersMap)
1 ✔
184
                  .content(response.body().byteStream())
1 ✔
185
                  .url(responseUrl)
1 ✔
186
                  .build();
1 ✔
187
        } else {
188
          rawResponseBody = response.body() != null ? response.body().string() : null;
1 ✔
189
          fetchResponse =
1 ✔
190
              new FetchResponse.Builder(response.code(), headersMap)
1 ✔
191
                  .data(readJsonFromRawBody(rawResponseBody))
1 ✔
192
                  .url(responseUrl)
1 ✔
193
                  .build();
1 ✔
194
        }
195

196
        fetchResponse =
1 ✔
197
            networkSession.getInterceptors().stream()
1 ✔
198
                .reduce(
1 ✔
199
                    fetchResponse,
200
                    (modifiedResponse, interceptor) -> interceptor.afterRequest(modifiedResponse),
1 ✔
201
                    (o1, o2) -> o2);
×
202

203
      } catch (Exception e) {
1 ✔
204
        exceptionThrown = e;
1 ✔
205
        numberOfRetriesOnException++;
1 ✔
206
        attemptForRetry = numberOfRetriesOnException;
1 ✔
207
        fetchResponse = new FetchResponse.Builder(0, new TreeMap<>()).build();
1 ✔
208
        rawResponseBody = null;
1 ✔
209
        if (response != null) {
1 ✔
210
          response.close();
×
211
        }
212
      }
1 ✔
213

214
      shouldRetry =
1 ✔
215
          networkSession
216
              .getRetryStrategy()
1 ✔
217
              .shouldRetry(fetchOptions, fetchResponse, attemptForRetry);
1 ✔
218

219
      if (shouldRetry) {
1 ✔
220
        double retryDelay =
1 ✔
221
            networkSession
222
                .getRetryStrategy()
1 ✔
223
                .retryAfter(fetchOptions, fetchResponse, attemptForRetry);
1 ✔
224
        if (retryDelay > 0) {
1 ✔
225
          try {
226
            TimeUnit.SECONDS.sleep((long) retryDelay);
1 ✔
227
          } catch (InterruptedException ie) {
×
228
            Thread.currentThread().interrupt();
×
229
            throw new BoxSDKError("Retry interrupted", ie);
×
230
          }
1 ✔
231
        }
232
        attemptNumber++;
1 ✔
233
        continue;
1 ✔
234
      }
235

236
      if (fetchResponse.getStatus() >= 300
1 ✔
237
          && fetchResponse.getStatus() < 400
1 ✔
238
          && fetchOptions.followRedirects) {
1 ✔
239
        if (!fetchResponse.getHeaders().containsKey("Location")) {
1 ✔
240
          throw new BoxSDKError(
1 ✔
241
              "Redirect response missing Location header for " + fetchOptions.getUrl());
1 ✔
242
        }
243
        URI originalUri = URI.create(fetchOptions.getUrl());
1 ✔
244
        URI redirectUri = URI.create(fetchResponse.getHeaders().get("Location"));
1 ✔
245
        boolean sameOrigin =
1 ✔
246
            originalUri.getHost().equals(redirectUri.getHost())
1 ✔
247
                && originalUri.getPort() == redirectUri.getPort()
×
248
                && originalUri.getScheme().equals(redirectUri.getScheme());
1 ✔
249
        return fetch(
1 ✔
250
            new FetchOptions.Builder(fetchResponse.getHeaders().get("Location"), "GET")
1 ✔
251
                .responseFormat(fetchOptions.getResponseFormat())
1 ✔
252
                .auth(sameOrigin ? fetchOptions.getAuth() : null)
1 ✔
253
                .networkSession(networkSession)
1 ✔
254
                .build());
1 ✔
255
      }
256

257
      if (fetchResponse.getStatus() >= 200 && fetchResponse.getStatus() < 400) {
1 ✔
258
        return fetchResponse;
1 ✔
259
      }
260

261
      throwOnUnsuccessfulResponse(
1 ✔
262
          request,
263
          fetchResponse,
264
          rawResponseBody,
265
          exceptionThrown,
266
          fetchOptions.getContentType(),
1 ✔
267
          networkSession.getDataSanitizer());
1 ✔
268
    }
×
269
  }
270

271
  private static Request prepareRequest(
272
      FetchOptions options, boolean reauthenticate, NetworkSession networkSession) {
273
    Request.Builder requestBuilder = new Request.Builder().url(options.getUrl());
1 ✔
274
    Headers headers = prepareHeaders(options, reauthenticate, networkSession);
1 ✔
275
    HttpUrl url = prepareUrl(options);
1 ✔
276
    RequestBody body = prepareRequestBody(options);
1 ✔
277

278
    requestBuilder.headers(headers);
1 ✔
279
    requestBuilder.url(url);
1 ✔
280
    requestBuilder.method(options.getMethod().toUpperCase(Locale.ROOT), body);
1 ✔
281
    return requestBuilder.build();
1 ✔
282
  }
283

284
  private static Headers prepareHeaders(
285
      FetchOptions options, boolean reauthenticate, NetworkSession networkSession) {
286
    Headers.Builder headersBuilder = new Headers.Builder();
1 ✔
287

288
    networkSession.getAdditionalHeaders().forEach(headersBuilder::add);
1 ✔
289

290
    if (options.getHeaders() != null) {
1 ✔
291
      options.getHeaders().forEach(headersBuilder::add);
1 ✔
292
    }
293
    if (options.getAuth() != null) {
1 ✔
294
      if (reauthenticate) {
1 ✔
295
        options.getAuth().refreshToken(networkSession);
×
296
      }
297
      headersBuilder.add(
1 ✔
298
          "Authorization", options.getAuth().retrieveAuthorizationHeader(networkSession));
1 ✔
299
    }
300
    headersBuilder.add("User-Agent", USER_AGENT_HEADER);
1 ✔
301
    headersBuilder.add("X-Box-UA", X_BOX_UA_HEADER);
1 ✔
302
    return headersBuilder.build();
1 ✔
303
  }
304

305
  private static HttpUrl prepareUrl(FetchOptions options) {
306

307
    HttpUrl baseUrl = HttpUrl.parse(options.getUrl());
1 ✔
308
    if (baseUrl == null) {
1 ✔
309
      throw new IllegalArgumentException("Invalid URL " + options.getUrl());
×
310
    }
311
    HttpUrl.Builder urlBuilder = baseUrl.newBuilder();
1 ✔
312
    if (options.getParams() != null) {
1 ✔
313
      options.getParams().forEach(urlBuilder::addQueryParameter);
1 ✔
314
    }
315
    return urlBuilder.build();
1 ✔
316
  }
317

318
  private static RequestBody prepareRequestBody(FetchOptions options) {
319
    if (options.getMethod().equalsIgnoreCase("GET")) {
1 ✔
320
      return null;
1 ✔
321
    }
322
    String contentType = options.getContentType();
1 ✔
323
    MediaType mediaType = MediaType.parse(contentType);
1 ✔
324
    switch (contentType) {
1 ✔
325
      case "application/json":
326
      case "application/json-patch+json":
327
        return options.getData() != null
1 ✔
328
            ? RequestBody.create(sdToJson(options.getData()), mediaType)
1 ✔
329
            : RequestBody.create("", mediaType);
1 ✔
330
      case "application/x-www-form-urlencoded":
331
        return options.getData() != null
1 ✔
332
            ? RequestBody.create(sdToUrlParams(options.getData()), mediaType)
1 ✔
333
            : RequestBody.create("", mediaType);
×
334
      case "multipart/form-data":
335
        MultipartBody.Builder bodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);
1 ✔
336
        for (MultipartItem part : options.multipartData) {
1 ✔
337
          if (part.getData() != null) {
1 ✔
338
            bodyBuilder.addFormDataPart(part.getPartName(), sdToJson(part.getData()));
1 ✔
339
          } else {
340
            bodyBuilder.addFormDataPart(
1 ✔
341
                part.getPartName(),
1 ✔
342
                part.getFileName() != null ? part.getFileName() : "file",
1 ✔
343
                createMultipartRequestBody(part));
1 ✔
344
          }
345
        }
1 ✔
346
        return bodyBuilder.build();
1 ✔
347
      case "application/octet-stream":
348
        return RequestBody.create(readByteStream(options.getFileStream()), mediaType);
1 ✔
349
      default:
350
        throw new IllegalArgumentException("Unsupported content type " + contentType);
×
351
    }
352
  }
353

354
  protected Call createNewCall(Request request) {
355
    return this.httpClient.newCall(request);
1 ✔
356
  }
357

358
  private Response executeOnClient(Request request) throws IOException {
359
    return createNewCall(request).execute();
1 ✔
360
  }
361

362
  private static JsonNode readJsonFromRawBody(String rawResponseBody) {
363
    if (rawResponseBody == null) {
1 ✔
364
      return null;
×
365
    }
366

367
    try {
368
      return jsonToSerializedData(rawResponseBody);
1 ✔
369
    } catch (Exception e) {
1 ✔
370
      return null;
1 ✔
371
    }
372
  }
373

374
  private static void throwOnUnsuccessfulResponse(
375
      Request request,
376
      FetchResponse fetchResponse,
377
      String rawResponseBody,
378
      Exception exceptionThrown,
379
      String contentType,
380
      DataSanitizer dataSanitizer) {
381
    if (fetchResponse.getStatus() == 0 && exceptionThrown != null) {
1 ✔
382
      throw new BoxSDKError(exceptionThrown.getMessage(), exceptionThrown);
1 ✔
383
    }
384
    try {
385
      throw BoxAPIError.fromAPICall(
1 ✔
386
          request, fetchResponse, rawResponseBody, dataSanitizer, contentType);
387
    } finally {
388
      try {
389
        if (fetchResponse.getContent() != null) {
1 ✔
390
          fetchResponse.getContent().close();
1 ✔
391
        }
392
      } catch (IOException ignored) {
×
393
      }
1 ✔
394
    }
395
  }
396

397
  private static int getRetryAfterTimeInSeconds(int attemptNumber, String retryAfterHeader) {
398

399
    if (retryAfterHeader != null) {
×
400
      return Integer.parseInt(retryAfterHeader);
×
401
    }
402

403
    double minWindow = 1 - RANDOM_FACTOR;
×
404
    double maxWindow = 1 + RANDOM_FACTOR;
×
405
    double jitter = (Math.random() * (maxWindow - minWindow)) + minWindow;
×
406
    return (int) (Math.pow(2, attemptNumber) * BASE_TIMEOUT * jitter);
×
407
  }
408

409
  public static RequestBody createMultipartRequestBody(MultipartItem part) {
410
    return new RequestBody() {
1 ✔
411
      @Override
412
      public MediaType contentType() {
413
        if (part.contentType != null) {
1 ✔
414
          return MediaType.parse(part.contentType);
1 ✔
415
        }
416
        return MediaType.parse("application/octet-stream");
1 ✔
417
      }
418

419
      @Override
420
      public void writeTo(BufferedSink sink) throws IOException {
421
        try (Source source = Okio.source(part.getFileStream())) {
1 ✔
422
          sink.writeAll(source);
1 ✔
423
        }
424
      }
1 ✔
425
    };
426
  }
427

428
  public static OkHttpClient.Builder getDefaultOkHttpClientBuilder() {
429
    return new OkHttpClient.Builder()
1 ✔
430
        .followSslRedirects(true)
1 ✔
431
        .followRedirects(false)
1 ✔
432
        .connectionSpecs(singletonList(MODERN_TLS))
1 ✔
433
        .retryOnConnectionFailure(false);
1 ✔
434
  }
435
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc