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

box / box-java-sdk-gen / #30

25 Mar 2025 01:26PM UTC coverage: 49.836% (+0.2%) from 49.659%
#30

push

github

web-flow
chore: Add notify `changelog` workflow (box/box-codegen#685) (#254)

8375 of 16805 relevant lines covered (49.84%)

0.91 hits per line

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

81.6
/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.networking.fetchoptions.FetchOptions;
15
import com.box.sdkgen.networking.fetchoptions.MultipartItem;
16
import com.box.sdkgen.networking.fetchoptions.ResponseFormat;
17
import com.box.sdkgen.networking.fetchresponse.FetchResponse;
18
import com.box.sdkgen.networking.network.NetworkSession;
19
import com.box.sdkgen.networking.networkclient.NetworkClient;
20
import java.io.IOException;
21
import java.net.URI;
22
import java.util.Map;
23
import java.util.Objects;
24
import java.util.TreeMap;
25
import java.util.concurrent.TimeUnit;
26
import java.util.stream.Collectors;
27
import okhttp3.Call;
28
import okhttp3.Headers;
29
import okhttp3.HttpUrl;
30
import okhttp3.MediaType;
31
import okhttp3.MultipartBody;
32
import okhttp3.OkHttpClient;
33
import okhttp3.Request;
34
import okhttp3.RequestBody;
35
import okhttp3.Response;
36
import okio.BufferedSink;
37
import okio.Okio;
38
import okio.Source;
39

40
public class BoxNetworkClient implements NetworkClient {
41

42
  private static final int BASE_TIMEOUT = 1;
43
  private static final double RANDOM_FACTOR = 0.5;
44

45
  protected OkHttpClient httpClient;
46

47
  public BoxNetworkClient(OkHttpClient httpClient) {
×
48
    this.httpClient = httpClient;
×
49
  }
×
50

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

60
  public OkHttpClient getHttpClient() {
61
    return httpClient;
×
62
  }
63

64
  public FetchResponse fetch(FetchOptions options) {
65
    NetworkSession networkSession =
66
        options.getNetworkSession() == null ? new NetworkSession() : options.getNetworkSession();
1✔
67

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

75
    boolean authenticationNeeded = false;
1✔
76
    Request request;
77
    FetchResponse fetchResponse = null;
1✔
78
    Exception exceptionThrown = null;
1✔
79

80
    int attemptNumber = 0;
1✔
81

82
    while (true) {
83
      request = prepareRequest(fetchOptions, authenticationNeeded, networkSession);
1✔
84

85
      Response response = null;
1✔
86
      try {
87
        response = executeOnClient(request);
1✔
88

89
        Map<String, String> headersMap =
1✔
90
            response.headers().toMultimap().entrySet().stream()
1✔
91
                .collect(
1✔
92
                    Collectors.toMap(
1✔
93
                        Map.Entry::getKey,
94
                        e -> e.getValue().get(0),
1✔
95
                        (existing, replacement) -> existing,
×
96
                        () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)));
1✔
97

98
        String responseUrl =
99
            response.networkResponse() != null
1✔
100
                ? response.networkResponse().request().url().toString()
1✔
101
                : response.request().url().toString();
1✔
102
        fetchResponse =
103
            Objects.equals(fetchOptions.getResponseFormat().getEnumValue(), ResponseFormat.BINARY)
1✔
104
                ? new FetchResponse.FetchResponseBuilder(response.code(), headersMap)
1✔
105
                    .content(response.body().byteStream())
1✔
106
                    .url(responseUrl)
1✔
107
                    .build()
1✔
108
                : new FetchResponse.FetchResponseBuilder(response.code(), headersMap)
1✔
109
                    .data(
1✔
110
                        response.body() != null
1✔
111
                            ? jsonToSerializedData(response.body().string())
1✔
112
                            : null)
×
113
                    .url(responseUrl)
1✔
114
                    .build();
1✔
115

116
        fetchResponse =
1✔
117
            networkSession.getInterceptors().stream()
1✔
118
                .reduce(
1✔
119
                    fetchResponse,
120
                    (modifiedResponse, interceptor) -> interceptor.afterRequest(modifiedResponse),
1✔
121
                    (o1, o2) -> o2);
×
122

123
        boolean shouldRetry =
1✔
124
            networkSession
125
                .getRetryStrategy()
1✔
126
                .shouldRetry(fetchOptions, fetchResponse, attemptNumber);
1✔
127

128
        if (shouldRetry) {
1✔
129
          TimeUnit.SECONDS.sleep(
×
130
              (long)
131
                  networkSession
132
                      .getRetryStrategy()
×
133
                      .retryAfter(fetchOptions, fetchResponse, attemptNumber));
×
134
          attemptNumber++;
×
135
          continue;
×
136
        }
137

138
        if (fetchResponse.getStatus() >= 300
1✔
139
            && fetchResponse.getStatus() < 400
1✔
140
            && fetchOptions.followRedirects) {
1✔
141
          if (!fetchResponse.getHeaders().containsKey("Location")) {
1✔
142
            throw new BoxSDKError(
1✔
143
                "Redirect response missing Location header for " + fetchOptions.getUrl());
1✔
144
          }
145
          URI originalUri = URI.create(fetchOptions.getUrl());
1✔
146
          URI redirectUri = URI.create(fetchResponse.getHeaders().get("Location"));
1✔
147
          boolean sameOrigin =
1✔
148
              originalUri.getHost().equals(redirectUri.getHost())
1✔
149
                  && originalUri.getPort() == redirectUri.getPort()
×
150
                  && originalUri.getScheme().equals(redirectUri.getScheme());
1✔
151
          return fetch(
1✔
152
              new FetchOptions.FetchOptionsBuilder(
153
                      fetchResponse.getHeaders().get("Location"), "GET")
1✔
154
                  .responseFormat(fetchOptions.getResponseFormat())
1✔
155
                  .auth(sameOrigin ? fetchOptions.getAuth() : null)
1✔
156
                  .networkSession(networkSession)
1✔
157
                  .build());
1✔
158
        }
159

160
      } catch (Exception e) {
1✔
161
        exceptionThrown = e;
1✔
162
        // Retry network exception only once
163
        if (attemptNumber > 1) {
1✔
164
          if (response != null) {
×
165
            response.close();
×
166
          }
167
          throw new BoxSDKError(e.getMessage(), e);
×
168
        }
169
      }
1✔
170

171
      if (fetchResponse != null
1✔
172
          && fetchResponse.getStatus() >= 200
1✔
173
          && fetchResponse.getStatus() < 400) {
1✔
174
        return fetchResponse;
1✔
175
      }
176

177
      throwOnUnsuccessfulResponse(request, fetchResponse, exceptionThrown);
×
178
    }
×
179
  }
180

181
  private static Request prepareRequest(
182
      FetchOptions options, boolean reauthenticate, NetworkSession networkSession) {
183
    Request.Builder requestBuilder = new Request.Builder().url(options.getUrl());
1✔
184
    Headers headers = prepareHeaders(options, reauthenticate, networkSession);
1✔
185
    HttpUrl url = prepareUrl(options);
1✔
186
    RequestBody body = prepareRequestBody(options);
1✔
187

188
    requestBuilder.headers(headers);
1✔
189
    requestBuilder.url(url);
1✔
190
    requestBuilder.method(options.getMethod().toUpperCase(), body);
1✔
191
    return requestBuilder.build();
1✔
192
  }
193

194
  private static Headers prepareHeaders(
195
      FetchOptions options, boolean reauthenticate, NetworkSession networkSession) {
196
    Headers.Builder headersBuilder = new Headers.Builder();
1✔
197

198
    networkSession.getAdditionalHeaders().forEach(headersBuilder::add);
1✔
199

200
    if (options.getHeaders() != null) {
1✔
201
      options.getHeaders().forEach(headersBuilder::add);
1✔
202
    }
203
    if (options.getAuth() != null) {
1✔
204
      if (reauthenticate) {
1✔
205
        options.getAuth().refreshToken(networkSession);
×
206
      }
207
      headersBuilder.add(
1✔
208
          "Authorization", options.getAuth().retrieveAuthorizationHeader(networkSession));
1✔
209
    }
210
    headersBuilder.add("User-Agent", USER_AGENT_HEADER);
1✔
211
    headersBuilder.add("X-Box-UA", X_BOX_UA_HEADER);
1✔
212
    return headersBuilder.build();
1✔
213
  }
214

215
  private static HttpUrl prepareUrl(FetchOptions options) {
216

217
    HttpUrl baseUrl = HttpUrl.parse(options.getUrl());
1✔
218
    if (baseUrl == null) {
1✔
219
      throw new IllegalArgumentException("Invalid URL " + options.getUrl());
×
220
    }
221
    HttpUrl.Builder urlBuilder = baseUrl.newBuilder();
1✔
222
    if (options.getParams() != null) {
1✔
223
      options.getParams().forEach(urlBuilder::addQueryParameter);
1✔
224
    }
225
    return urlBuilder.build();
1✔
226
  }
227

228
  private static RequestBody prepareRequestBody(FetchOptions options) {
229
    if (options.getMethod().equalsIgnoreCase("GET")) {
1✔
230
      return null;
1✔
231
    }
232
    String contentType = options.getContentType();
1✔
233
    MediaType mediaType = MediaType.parse(contentType);
1✔
234
    switch (contentType) {
1✔
235
      case "application/json":
236
      case "application/json-patch+json":
237
        return options.getData() != null
1✔
238
            ? RequestBody.create(sdToJson(options.getData()), mediaType)
1✔
239
            : RequestBody.create("", mediaType);
1✔
240
      case "application/x-www-form-urlencoded":
241
        return options.getData() != null
1✔
242
            ? RequestBody.create(sdToUrlParams(options.getData()), mediaType)
1✔
243
            : RequestBody.create("", mediaType);
×
244
      case "multipart/form-data":
245
        MultipartBody.Builder bodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);
1✔
246
        for (MultipartItem part : options.multipartData) {
1✔
247
          if (part.getData() != null) {
1✔
248
            bodyBuilder.addFormDataPart(part.getPartName(), sdToJson(part.getData()));
1✔
249
          } else {
250
            bodyBuilder.addFormDataPart(
1✔
251
                part.getPartName(),
1✔
252
                part.getFileName() != null ? part.getFileName() : "file",
1✔
253
                createMultipartRequestBody(part));
1✔
254
          }
255
        }
1✔
256
        return bodyBuilder.build();
1✔
257
      case "application/octet-stream":
258
        return RequestBody.create(readByteStream(options.getFileStream()), mediaType);
1✔
259
      default:
260
        throw new IllegalArgumentException("Unsupported content type " + contentType);
×
261
    }
262
  }
263

264
  protected Call createNewCall(Request request) {
265
    return this.httpClient.newCall(request);
1✔
266
  }
267

268
  private Response executeOnClient(Request request) throws IOException {
269
    return createNewCall(request).execute();
1✔
270
  }
271

272
  private static void throwOnUnsuccessfulResponse(
273
      Request request, FetchResponse fetchResponse, Exception exceptionThrown) {
274
    if (fetchResponse == null) {
1✔
275
      throw new BoxSDKError(exceptionThrown.getMessage(), exceptionThrown);
1✔
276
    }
277
    try {
278
      throw BoxAPIError.fromAPICall(request, fetchResponse);
1✔
279
    } finally {
280
      try {
281
        if (fetchResponse.getContent() != null) {
1✔
282
          fetchResponse.getContent().close();
1✔
283
        }
284
      } catch (IOException ignored) {
×
285
      }
1✔
286
    }
287
  }
288

289
  private static int getRetryAfterTimeInSeconds(int attemptNumber, String retryAfterHeader) {
290

291
    if (retryAfterHeader != null) {
×
292
      return Integer.parseInt(retryAfterHeader);
×
293
    }
294

295
    double minWindow = 1 - RANDOM_FACTOR;
×
296
    double maxWindow = 1 + RANDOM_FACTOR;
×
297
    double jitter = (Math.random() * (maxWindow - minWindow)) + minWindow;
×
298
    return (int) (Math.pow(2, attemptNumber) * BASE_TIMEOUT * jitter);
×
299
  }
300

301
  public static RequestBody createMultipartRequestBody(MultipartItem part) {
302
    return new RequestBody() {
1✔
303
      @Override
304
      public MediaType contentType() {
305
        if (part.contentType != null) {
1✔
306
          return MediaType.parse(part.contentType);
1✔
307
        }
308
        return MediaType.parse("application/octet-stream");
1✔
309
      }
310

311
      @Override
312
      public void writeTo(BufferedSink sink) throws IOException {
313
        try (Source source = Okio.source(part.getFileStream())) {
1✔
314
          sink.writeAll(source);
1✔
315
        }
316
      }
1✔
317
    };
318
  }
319
}
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