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

box / box-java-sdk / #4744

06 Aug 2025 12:44PM UTC coverage: 38.166% (+0.001%) from 38.165%
#4744

push

github

web-flow
chore: Adjust `github` workflows for legacy repos (box/box-codegen#772) (#1331)

18978 of 49725 relevant lines covered (38.17%)

0.38 hits per line

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

0.0
/src/main/java/com/box/sdkgen/networking/defaultnetworkclient/DefaultNetworkClient.java
1
package com.box.sdkgen.networking.defaultnetworkclient;
2

3
import static com.box.sdkgen.serialization.json.JsonManager.jsonToSerializedData;
4
import static com.box.sdkgen.serialization.json.JsonManager.sdToJson;
5
import static com.box.sdkgen.serialization.json.JsonManager.sdToUrlParams;
6
import static java.util.Collections.singletonList;
7
import static okhttp3.ConnectionSpec.MODERN_TLS;
8

9
import com.box.sdkgen.box.errors.BoxSDKError;
10
import com.box.sdkgen.networking.fetchoptions.FetchOptions;
11
import com.box.sdkgen.networking.fetchoptions.MultipartItem;
12
import com.box.sdkgen.networking.fetchoptions.ResponseFormat;
13
import com.box.sdkgen.networking.fetchresponse.FetchResponse;
14
import com.box.sdkgen.networking.network.NetworkSession;
15
import com.box.sdkgen.networking.networkclient.NetworkClient;
16
import java.io.IOException;
17
import java.util.Locale;
18
import java.util.Map;
19
import java.util.Objects;
20
import java.util.TreeMap;
21
import java.util.concurrent.TimeUnit;
22
import java.util.stream.Collectors;
23
import okhttp3.Call;
24
import okhttp3.Headers;
25
import okhttp3.HttpUrl;
26
import okhttp3.MediaType;
27
import okhttp3.MultipartBody;
28
import okhttp3.OkHttpClient;
29
import okhttp3.Request;
30
import okhttp3.RequestBody;
31
import okhttp3.Response;
32
import okio.BufferedSink;
33
import okio.Okio;
34
import okio.Source;
35

36
public class DefaultNetworkClient implements NetworkClient {
37

38
  protected OkHttpClient httpClient;
39

40
  public DefaultNetworkClient(OkHttpClient httpClient) {
×
41
    this.httpClient = httpClient;
×
42
  }
×
43

44
  public DefaultNetworkClient() {
×
45
    OkHttpClient.Builder builder =
×
46
        new OkHttpClient.Builder()
47
            .followSslRedirects(true)
×
48
            .followRedirects(false)
×
49
            .connectionSpecs(singletonList(MODERN_TLS));
×
50
    httpClient = builder.build();
×
51
  }
×
52

53
  public FetchResponse fetch(FetchOptions options) {
54
    NetworkSession networkSession =
55
        options.getNetworkSession() == null ? new NetworkSession() : options.getNetworkSession();
×
56

57
    FetchOptions fetchOptions =
×
58
        networkSession.getInterceptors().stream()
×
59
            .reduce(
×
60
                options,
61
                (modifiedOptions, interceptor) -> interceptor.beforeRequest(modifiedOptions),
×
62
                (o1, o2) -> o2);
×
63

64
    boolean authenticationNeeded = false;
×
65
    Request request;
66
    FetchResponse fetchResponse = null;
×
67
    Exception exceptionThrown = null;
×
68

69
    int attemptNumber = 1;
×
70
    int numberOfRetriesOnException = 0;
×
71
    int attemptForRetry = 0;
×
72
    boolean shouldRetry = false;
×
73

74
    while (true) {
75
      request = prepareRequest(fetchOptions, authenticationNeeded, networkSession);
×
76

77
      Response response = null;
×
78
      try {
79
        response = executeOnClient(request);
×
80

81
        Map<String, String> headersMap =
×
82
            response.headers().toMultimap().entrySet().stream()
×
83
                .collect(
×
84
                    Collectors.toMap(
×
85
                        Map.Entry::getKey,
86
                        e -> e.getValue().get(0),
×
87
                        (existing, replacement) -> existing,
×
88
                        () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)));
×
89

90
        String responseUrl =
91
            response.networkResponse() != null
×
92
                ? response.networkResponse().request().url().toString()
×
93
                : response.request().url().toString();
×
94

95
        attemptForRetry = attemptNumber;
×
96
        fetchResponse =
97
            Objects.equals(fetchOptions.getResponseFormat().getEnumValue(), ResponseFormat.BINARY)
×
98
                ? new FetchResponse.Builder(response.code(), headersMap)
×
99
                    .content(response.body().byteStream())
×
100
                    .url(responseUrl)
×
101
                    .build()
×
102
                : new FetchResponse.Builder(response.code(), headersMap)
×
103
                    .data(
×
104
                        response.body() != null
×
105
                            ? jsonToSerializedData(response.body().string())
×
106
                            : null)
×
107
                    .url(responseUrl)
×
108
                    .build();
×
109

110
        fetchResponse =
×
111
            networkSession.getInterceptors().stream()
×
112
                .reduce(
×
113
                    fetchResponse,
114
                    (modifiedResponse, interceptor) -> interceptor.afterRequest(modifiedResponse),
×
115
                    (o1, o2) -> o2);
×
116

117
      } catch (Exception e) {
×
118
        exceptionThrown = e;
×
119
        numberOfRetriesOnException++;
×
120
        attemptForRetry = numberOfRetriesOnException;
×
121
        if (response != null) {
×
122
          response.close();
×
123
        }
124

125
        fetchResponse =
×
126
            new FetchResponse.Builder(0, new TreeMap<>(String.CASE_INSENSITIVE_ORDER)).build();
×
127
      }
×
128

129
      shouldRetry =
×
130
          networkSession
131
              .getRetryStrategy()
×
132
              .shouldRetry(fetchOptions, fetchResponse, attemptForRetry);
×
133

134
      if (shouldRetry) {
×
135
        double retryDelay =
×
136
            networkSession
137
                .getRetryStrategy()
×
138
                .retryAfter(fetchOptions, fetchResponse, attemptForRetry);
×
139
        if (retryDelay > 0) {
×
140
          try {
141
            TimeUnit.SECONDS.sleep((long) retryDelay);
×
142
          } catch (InterruptedException ie) {
×
143
            Thread.currentThread().interrupt();
×
144
            throw new BoxSDKError("Retry interrupted", ie);
×
145
          }
×
146
        }
147
        attemptNumber++;
×
148
        continue;
×
149
      }
150

151
      if (fetchResponse.getStatus() >= 300
×
152
          && fetchResponse.getStatus() < 400
×
153
          && fetchOptions.followRedirects) {
×
154
        if (!fetchResponse.getHeaders().containsKey("Location")) {
×
155
          throw new BoxSDKError(
×
156
              "Redirect response missing Location header for " + fetchOptions.getUrl());
×
157
        }
158
        return fetch(
×
159
            new FetchOptions.Builder(fetchResponse.getHeaders().get("Location"), "GET")
×
160
                .responseFormat(fetchOptions.getResponseFormat())
×
161
                .auth(fetchOptions.getAuth())
×
162
                .networkSession(networkSession)
×
163
                .build());
×
164
      }
165

166
      if (fetchResponse != null
×
167
          && fetchResponse.getStatus() >= 200
×
168
          && fetchResponse.getStatus() < 400) {
×
169
        return fetchResponse;
×
170
      }
171

172
      throwOnUnsuccessfulResponse(request, fetchResponse, exceptionThrown);
×
173
    }
×
174
  }
175

176
  private static Request prepareRequest(
177
      FetchOptions options, boolean reauthenticate, NetworkSession networkSession) {
178
    Request.Builder requestBuilder = new Request.Builder().url(options.getUrl());
×
179
    Headers headers = prepareHeaders(options, reauthenticate, networkSession);
×
180
    HttpUrl url = prepareUrl(options);
×
181
    RequestBody body = prepareRequestBody(options);
×
182

183
    requestBuilder.headers(headers);
×
184
    requestBuilder.url(url);
×
185
    requestBuilder.method(options.getMethod().toUpperCase(Locale.ROOT), body);
×
186
    return requestBuilder.build();
×
187
  }
188

189
  private static Headers prepareHeaders(
190
      FetchOptions options, boolean reauthenticate, NetworkSession networkSession) {
191
    Headers.Builder headersBuilder = new Headers.Builder();
×
192

193
    networkSession.getAdditionalHeaders().forEach(headersBuilder::add);
×
194

195
    if (options.getHeaders() != null) {
×
196
      options.getHeaders().forEach(headersBuilder::add);
×
197
    }
198
    if (options.getAuth() != null) {
×
199
      if (reauthenticate) {
×
200
        options.getAuth().refreshToken(networkSession);
×
201
      }
202
      headersBuilder.add(
×
203
          "Authorization", options.getAuth().retrieveAuthorizationHeader(networkSession));
×
204
    }
205
    return headersBuilder.build();
×
206
  }
207

208
  private static HttpUrl prepareUrl(FetchOptions options) {
209

210
    HttpUrl baseUrl = HttpUrl.parse(options.getUrl());
×
211
    if (baseUrl == null) {
×
212
      throw new IllegalArgumentException("Invalid URL " + options.getUrl());
×
213
    }
214
    HttpUrl.Builder urlBuilder = baseUrl.newBuilder();
×
215
    if (options.getParams() != null) {
×
216
      options.getParams().forEach(urlBuilder::addQueryParameter);
×
217
    }
218
    return urlBuilder.build();
×
219
  }
220

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

255
  protected Call createNewCall(Request request) {
256
    return this.httpClient.newCall(request);
×
257
  }
258

259
  private Response executeOnClient(Request request) {
260
    try {
261
      return createNewCall(request).execute();
×
262
    } catch (IOException e) {
×
263
      throw new RuntimeException(e);
×
264
    }
265
  }
266

267
  public static RequestBody createMultipartRequestBody(MultipartItem part) {
268
    return new RequestBody() {
×
269
      @Override
270
      public MediaType contentType() {
271
        if (part.contentType != null) {
×
272
          return MediaType.parse(part.contentType);
×
273
        }
274
        return MediaType.parse("application/octet-stream");
×
275
      }
276

277
      @Override
278
      public void writeTo(BufferedSink sink) throws IOException {
279
        try (Source source = Okio.source(part.getFileStream())) {
×
280
          sink.writeAll(source);
×
281
        }
282
      }
×
283
    };
284
  }
285

286
  private static void throwOnUnsuccessfulResponse(
287
      Request request, FetchResponse fetchResponse, Exception exceptionThrown) {
288
    if (fetchResponse == null) {
×
289
      throw new RuntimeException(exceptionThrown.getMessage(), exceptionThrown);
×
290
    }
291
    throw new RuntimeException(fetchResponse.getData().toString());
×
292
  }
293
}
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

© 2025 Coveralls, Inc