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

box / box-java-sdk / #4766

04 Sep 2025 02:54PM UTC coverage: 37.239% (-1.6%) from 38.858%
#4766

push

github

web-flow
feat: Support event with long polling (box/box-codegen#807) (#1409)

32 of 420 new or added lines in 7 files covered. (7.62%)

800 existing lines in 66 files now uncovered.

18480 of 49626 relevant lines covered (37.24%)

0.37 hits per line

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

86.74
/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.fasterxml.jackson.databind.JsonNode;
22
import java.io.IOException;
23
import java.net.URI;
24
import java.util.Locale;
25
import java.util.Map;
26
import java.util.Objects;
27
import java.util.TreeMap;
28
import java.util.concurrent.TimeUnit;
29
import java.util.stream.Collectors;
30
import okhttp3.Call;
31
import okhttp3.Headers;
32
import okhttp3.HttpUrl;
33
import okhttp3.MediaType;
34
import okhttp3.MultipartBody;
35
import okhttp3.OkHttpClient;
36
import okhttp3.Request;
37
import okhttp3.RequestBody;
38
import okhttp3.Response;
39
import okio.BufferedSink;
40
import okio.Okio;
41
import okio.Source;
42

43
public class BoxNetworkClient implements NetworkClient {
44

45
  private static final int BASE_TIMEOUT = 1;
46
  private static final double RANDOM_FACTOR = 0.5;
47

48
  protected OkHttpClient httpClient;
49

50
  public BoxNetworkClient(OkHttpClient httpClient) {
×
51
    this.httpClient = httpClient;
×
52
  }
×
53

54
  public BoxNetworkClient() {
1✔
55
    OkHttpClient.Builder builder =
1✔
56
        new OkHttpClient.Builder()
57
            .followSslRedirects(true)
1✔
58
            .followRedirects(false)
1✔
59
            .connectionSpecs(singletonList(MODERN_TLS));
1✔
60
    httpClient = builder.build();
1✔
61
  }
1✔
62

63
  public OkHttpClient getHttpClient() {
64
    return httpClient;
×
65
  }
66

67
  public FetchResponse fetch(FetchOptions options) {
68
    NetworkSession networkSession =
1✔
69
        options.getNetworkSession() == null ? new NetworkSession() : options.getNetworkSession();
1✔
70

71
    FetchOptions fetchOptions =
1✔
72
        networkSession.getInterceptors().stream()
1✔
73
            .reduce(
1✔
74
                options,
75
                (modifiedOptions, interceptor) -> interceptor.beforeRequest(modifiedOptions),
1✔
76
                (o1, o2) -> o2);
×
77

78
    boolean authenticationNeeded = false;
1✔
79
    Request request;
80
    FetchResponse fetchResponse = new FetchResponse.Builder(0, new TreeMap<>()).build();
1✔
81
    Exception exceptionThrown = null;
1✔
82

83
    int attemptNumber = 1;
1✔
84
    int numberOfRetriesOnException = 0;
1✔
85
    int attemptForRetry = 0;
1✔
86
    boolean shouldRetry = false;
1✔
87

88
    while (true) {
89
      request = prepareRequest(fetchOptions, authenticationNeeded, networkSession);
1✔
90

91
      Response response = null;
1✔
92
      String rawResponseBody = null;
1✔
93

94
      try {
95
        response = executeOnClient(request);
1✔
96

97
        Map<String, String> headersMap =
1✔
98
            response.headers().toMultimap().entrySet().stream()
1✔
99
                .collect(
1✔
100
                    Collectors.toMap(
1✔
101
                        Map.Entry::getKey,
102
                        e -> e.getValue().get(0),
1✔
103
                        (existing, replacement) -> existing,
×
104
                        () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)));
1✔
105

106
        String responseUrl =
1✔
107
            response.networkResponse() != null
1✔
108
                ? response.networkResponse().request().url().toString()
1✔
109
                : response.request().url().toString();
1✔
110

111
        attemptForRetry = attemptNumber;
1✔
112

113
        if (Objects.equals(
1✔
114
            fetchOptions.getResponseFormat().getEnumValue(), ResponseFormat.BINARY)) {
1✔
115
          fetchResponse =
1✔
116
              new FetchResponse.Builder(response.code(), headersMap)
1✔
117
                  .content(response.body().byteStream())
1✔
118
                  .url(responseUrl)
1✔
119
                  .build();
1✔
120
        } else {
121
          rawResponseBody = response.body() != null ? response.body().string() : null;
1✔
122
          fetchResponse =
1✔
123
              new FetchResponse.Builder(response.code(), headersMap)
1✔
124
                  .data(readJsonFromRawBody(rawResponseBody))
1✔
125
                  .url(responseUrl)
1✔
126
                  .build();
1✔
127
        }
128

129
        fetchResponse =
1✔
130
            networkSession.getInterceptors().stream()
1✔
131
                .reduce(
1✔
132
                    fetchResponse,
133
                    (modifiedResponse, interceptor) -> interceptor.afterRequest(modifiedResponse),
1✔
134
                    (o1, o2) -> o2);
×
135

136
      } catch (Exception e) {
1✔
137
        exceptionThrown = e;
1✔
138
        numberOfRetriesOnException++;
1✔
139
        attemptForRetry = numberOfRetriesOnException;
1✔
140
        if (response != null) {
1✔
141
          response.close();
×
142
        }
143
      }
1✔
144

145
      shouldRetry =
1✔
146
          networkSession
147
              .getRetryStrategy()
1✔
148
              .shouldRetry(fetchOptions, fetchResponse, attemptForRetry);
1✔
149

150
      if (shouldRetry) {
1✔
151
        double retryDelay =
1✔
152
            networkSession
153
                .getRetryStrategy()
1✔
154
                .retryAfter(fetchOptions, fetchResponse, attemptForRetry);
1✔
155
        if (retryDelay > 0) {
1✔
156
          try {
157
            TimeUnit.SECONDS.sleep((long) retryDelay);
1✔
UNCOV
158
          } catch (InterruptedException ie) {
×
UNCOV
159
            Thread.currentThread().interrupt();
×
UNCOV
160
            throw new BoxSDKError("Retry interrupted", ie);
×
161
          }
1✔
162
        }
163
        attemptNumber++;
1✔
164
        continue;
1✔
165
      }
166

167
      if (fetchResponse.getStatus() >= 300
1✔
168
          && fetchResponse.getStatus() < 400
1✔
169
          && fetchOptions.followRedirects) {
1✔
170
        if (!fetchResponse.getHeaders().containsKey("Location")) {
1✔
171
          throw new BoxSDKError(
1✔
172
              "Redirect response missing Location header for " + fetchOptions.getUrl());
1✔
173
        }
174
        URI originalUri = URI.create(fetchOptions.getUrl());
1✔
175
        URI redirectUri = URI.create(fetchResponse.getHeaders().get("Location"));
1✔
176
        boolean sameOrigin =
1✔
177
            originalUri.getHost().equals(redirectUri.getHost())
1✔
178
                && originalUri.getPort() == redirectUri.getPort()
1✔
179
                && originalUri.getScheme().equals(redirectUri.getScheme());
1✔
180
        return fetch(
1✔
181
            new FetchOptions.Builder(fetchResponse.getHeaders().get("Location"), "GET")
1✔
182
                .responseFormat(fetchOptions.getResponseFormat())
1✔
183
                .auth(sameOrigin ? fetchOptions.getAuth() : null)
1✔
184
                .networkSession(networkSession)
1✔
185
                .build());
1✔
186
      }
187

188
      if (fetchResponse.getStatus() >= 200 && fetchResponse.getStatus() < 400) {
1✔
189
        return fetchResponse;
1✔
190
      }
191

192
      throwOnUnsuccessfulResponse(
1✔
193
          request,
194
          fetchResponse,
195
          rawResponseBody,
196
          exceptionThrown,
197
          networkSession.getDataSanitizer());
1✔
UNCOV
198
    }
×
199
  }
200

201
  private static Request prepareRequest(
202
      FetchOptions options, boolean reauthenticate, NetworkSession networkSession) {
203
    Request.Builder requestBuilder = new Request.Builder().url(options.getUrl());
1✔
204
    Headers headers = prepareHeaders(options, reauthenticate, networkSession);
1✔
205
    HttpUrl url = prepareUrl(options);
1✔
206
    RequestBody body = prepareRequestBody(options);
1✔
207

208
    requestBuilder.headers(headers);
1✔
209
    requestBuilder.url(url);
1✔
210
    requestBuilder.method(options.getMethod().toUpperCase(Locale.ROOT), body);
1✔
211
    return requestBuilder.build();
1✔
212
  }
213

214
  private static Headers prepareHeaders(
215
      FetchOptions options, boolean reauthenticate, NetworkSession networkSession) {
216
    Headers.Builder headersBuilder = new Headers.Builder();
1✔
217

218
    networkSession.getAdditionalHeaders().forEach(headersBuilder::add);
1✔
219

220
    if (options.getHeaders() != null) {
1✔
221
      options.getHeaders().forEach(headersBuilder::add);
1✔
222
    }
223
    if (options.getAuth() != null) {
1✔
224
      if (reauthenticate) {
1✔
UNCOV
225
        options.getAuth().refreshToken(networkSession);
×
226
      }
227
      headersBuilder.add(
1✔
228
          "Authorization", options.getAuth().retrieveAuthorizationHeader(networkSession));
1✔
229
    }
230
    headersBuilder.add("User-Agent", USER_AGENT_HEADER);
1✔
231
    headersBuilder.add("X-Box-UA", X_BOX_UA_HEADER);
1✔
232
    return headersBuilder.build();
1✔
233
  }
234

235
  private static HttpUrl prepareUrl(FetchOptions options) {
236

237
    HttpUrl baseUrl = HttpUrl.parse(options.getUrl());
1✔
238
    if (baseUrl == null) {
1✔
UNCOV
239
      throw new IllegalArgumentException("Invalid URL " + options.getUrl());
×
240
    }
241
    HttpUrl.Builder urlBuilder = baseUrl.newBuilder();
1✔
242
    if (options.getParams() != null) {
1✔
243
      options.getParams().forEach(urlBuilder::addQueryParameter);
1✔
244
    }
245
    return urlBuilder.build();
1✔
246
  }
247

248
  private static RequestBody prepareRequestBody(FetchOptions options) {
249
    if (options.getMethod().equalsIgnoreCase("GET")) {
1✔
250
      return null;
1✔
251
    }
252
    String contentType = options.getContentType();
1✔
253
    MediaType mediaType = MediaType.parse(contentType);
1✔
254
    switch (contentType) {
1✔
255
      case "application/json":
256
      case "application/json-patch+json":
257
        return options.getData() != null
1✔
258
            ? RequestBody.create(sdToJson(options.getData()), mediaType)
1✔
259
            : RequestBody.create("", mediaType);
1✔
260
      case "application/x-www-form-urlencoded":
261
        return options.getData() != null
1✔
262
            ? RequestBody.create(sdToUrlParams(options.getData()), mediaType)
1✔
UNCOV
263
            : RequestBody.create("", mediaType);
×
264
      case "multipart/form-data":
265
        MultipartBody.Builder bodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);
1✔
266
        for (MultipartItem part : options.multipartData) {
1✔
267
          if (part.getData() != null) {
1✔
268
            bodyBuilder.addFormDataPart(part.getPartName(), sdToJson(part.getData()));
1✔
269
          } else {
270
            bodyBuilder.addFormDataPart(
1✔
271
                part.getPartName(),
1✔
272
                part.getFileName() != null ? part.getFileName() : "file",
1✔
273
                createMultipartRequestBody(part));
1✔
274
          }
275
        }
1✔
276
        return bodyBuilder.build();
1✔
277
      case "application/octet-stream":
278
        return RequestBody.create(readByteStream(options.getFileStream()), mediaType);
1✔
279
      default:
UNCOV
280
        throw new IllegalArgumentException("Unsupported content type " + contentType);
×
281
    }
282
  }
283

284
  protected Call createNewCall(Request request) {
285
    return this.httpClient.newCall(request);
1✔
286
  }
287

288
  private Response executeOnClient(Request request) throws IOException {
289
    return createNewCall(request).execute();
1✔
290
  }
291

292
  private static JsonNode readJsonFromRawBody(String rawResponseBody) {
293
    if (rawResponseBody == null) {
1✔
UNCOV
294
      return null;
×
295
    }
296

297
    try {
298
      return jsonToSerializedData(rawResponseBody);
1✔
299
    } catch (Exception e) {
1✔
300
      return null;
1✔
301
    }
302
  }
303

304
  private static void throwOnUnsuccessfulResponse(
305
      Request request,
306
      FetchResponse fetchResponse,
307
      String rawResponseBody,
308
      Exception exceptionThrown,
309
      DataSanitizer dataSanitizer) {
310
    if (fetchResponse.getStatus() == 0 && exceptionThrown != null) {
1✔
311
      throw new BoxSDKError(exceptionThrown.getMessage(), exceptionThrown);
1✔
312
    }
313
    try {
314
      throw BoxAPIError.fromAPICall(request, fetchResponse, rawResponseBody, dataSanitizer);
1✔
315
    } finally {
316
      try {
317
        if (fetchResponse.getContent() != null) {
1✔
318
          fetchResponse.getContent().close();
1✔
319
        }
UNCOV
320
      } catch (IOException ignored) {
×
321
      }
1✔
322
    }
323
  }
324

325
  private static int getRetryAfterTimeInSeconds(int attemptNumber, String retryAfterHeader) {
326

UNCOV
327
    if (retryAfterHeader != null) {
×
UNCOV
328
      return Integer.parseInt(retryAfterHeader);
×
329
    }
330

UNCOV
331
    double minWindow = 1 - RANDOM_FACTOR;
×
UNCOV
332
    double maxWindow = 1 + RANDOM_FACTOR;
×
333
    double jitter = (Math.random() * (maxWindow - minWindow)) + minWindow;
×
334
    return (int) (Math.pow(2, attemptNumber) * BASE_TIMEOUT * jitter);
×
335
  }
336

337
  public static RequestBody createMultipartRequestBody(MultipartItem part) {
338
    return new RequestBody() {
1✔
339
      @Override
340
      public MediaType contentType() {
341
        if (part.contentType != null) {
1✔
342
          return MediaType.parse(part.contentType);
1✔
343
        }
344
        return MediaType.parse("application/octet-stream");
1✔
345
      }
346

347
      @Override
348
      public void writeTo(BufferedSink sink) throws IOException {
349
        try (Source source = Okio.source(part.getFileStream())) {
1✔
350
          sink.writeAll(source);
1✔
351
        }
352
      }
1✔
353
    };
354
  }
355
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc