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

smartsheet / smartsheet-java-sdk / #50

02 Oct 2023 04:35PM UTC coverage: 56.752% (+0.03%) from 56.722%
#50

push

github-actions

web-flow
Checkstyle fixes and refactor DefaultHttpClient#request (#72)

* Correct additional checkstyle issues and refactor DefaultHttpClient#request

* Reduce checkstyle errors to 5

* Update to add supressions instead of removing rules

45 of 45 new or added lines in 5 files covered. (100.0%)

3875 of 6828 relevant lines covered (56.75%)

0.57 hits per line

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

68.53
/src/main/java/com/smartsheet/api/internal/http/DefaultHttpClient.java
1
/*
2
 * Copyright (C) 2023 Smartsheet
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package com.smartsheet.api.internal.http;
18

19
import com.smartsheet.api.Trace;
20
import com.smartsheet.api.internal.json.JacksonJsonSerializer;
21
import com.smartsheet.api.internal.json.JsonSerializer;
22
import com.smartsheet.api.internal.util.StreamUtil;
23
import com.smartsheet.api.internal.util.Util;
24
import com.smartsheet.api.models.Error;
25
import org.apache.http.Header;
26
import org.apache.http.NoHttpResponseException;
27
import org.apache.http.client.ClientProtocolException;
28
import org.apache.http.client.NonRepeatableRequestException;
29
import org.apache.http.client.config.RequestConfig;
30
import org.apache.http.client.methods.CloseableHttpResponse;
31
import org.apache.http.client.methods.HttpDelete;
32
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
33
import org.apache.http.client.methods.HttpGet;
34
import org.apache.http.client.methods.HttpPost;
35
import org.apache.http.client.methods.HttpPut;
36
import org.apache.http.client.methods.HttpRequestBase;
37
import org.apache.http.client.methods.HttpRequestWrapper;
38
import org.apache.http.entity.ContentType;
39
import org.apache.http.entity.InputStreamEntity;
40
import org.apache.http.impl.client.CloseableHttpClient;
41
import org.apache.http.impl.client.HttpClients;
42
import org.apache.http.protocol.BasicHttpContext;
43
import org.apache.http.protocol.HttpContext;
44
import org.jetbrains.annotations.Nullable;
45
import org.slf4j.Logger;
46
import org.slf4j.LoggerFactory;
47

48
import java.io.ByteArrayInputStream;
49
import java.io.IOException;
50
import java.io.InputStream;
51
import java.io.OutputStream;
52
import java.io.PrintWriter;
53
import java.util.Arrays;
54
import java.util.Collections;
55
import java.util.HashMap;
56
import java.util.HashSet;
57
import java.util.Map;
58
import java.util.Random;
59
import java.util.Set;
60

61
/**
62
 * This is the Apache HttpClient based HttpClient implementation.
63
 * <p>
64
 * Thread Safety: This class is thread safe because it is immutable and the underlying Apache CloseableHttpClient is
65
 * thread safe.
66
 * @see <a href="http://hc.apache.org/httpcomponents-client-ga/index.html">Apache HttpClient</a>
67
 */
68
public class DefaultHttpClient implements HttpClient {
69

70
    /** logger for general errors, warnings, etc */
71
    protected static final Logger logger = LoggerFactory.getLogger(DefaultHttpClient.class);
1✔
72

73
    /** used by default retry/timeout logic and available for overriders */
74
    protected static final String JSON_MIME_TYPE = ContentType.APPLICATION_JSON.getMimeType();
1✔
75

76
    protected JsonSerializer jsonSerializer;
77

78
    protected long maxRetryTimeMillis = 15000;
1✔
79

80
    /**
81
     * Represents the underlying Apache CloseableHttpClient.
82
     * <p>
83
     * It will be initialized in constructor and will not change afterwards.
84
     */
85
    private final CloseableHttpClient httpClient;
86

87
    /** The apache http response. */
88
    private CloseableHttpResponse apacheHttpResponse;
89

90
    /** to avoid creating new sets for each call (we use Sets for practical and perf reasons) */
91
    private static final Set<Trace> REQUEST_RESPONSE_SUMMARY = Collections.unmodifiableSet(new HashSet<>(
1✔
92
            Arrays.asList(Trace.RequestHeaders, Trace.RequestBodySummary, Trace.ResponseHeaders, Trace.ResponseBodySummary)));
1✔
93

94
    private static final Set<Trace> REQUEST_RESPONSE = Collections.unmodifiableSet(new HashSet<>(
1✔
95
            Arrays.asList(Trace.RequestHeaders, Trace.RequestBody, Trace.ResponseHeaders, Trace.ResponseBody)));
1✔
96

97
    /** default values for trace-logging extracted from system-properties (can still be overwritten at the instance level) */
98
    private static final boolean TRACE_PRETTY_PRINT_DEFAULT = Boolean.parseBoolean(System.getProperty("Smartsheet.trace.pretty", "true"));
1✔
99

100
    // empty by default
101
    private static final Set<Trace> TRACE_DEFAULT_TRACE_SET = Trace.parse(System.getProperty("Smartsheet.trace.parts"));
1✔
102

103
    /** where to send trace logs */
104
    private static PrintWriter TRACE_WRITER;
105

106
    static {
107
        // default trace stream
108
        setTraceStream(System.out);
1✔
109
        if (TRACE_DEFAULT_TRACE_SET.size() > 0) {
1✔
110
            TRACE_WRITER.println("default trace logging - pretty:" + TRACE_PRETTY_PRINT_DEFAULT + " parts:" + TRACE_DEFAULT_TRACE_SET);
×
111
        }
112
    }
1✔
113

114
    /** the set of Trace levels to use in trace-logging */
115
    private final Set<Trace> traces = new HashSet<>(TRACE_DEFAULT_TRACE_SET);
1✔
116

117
    /** whether to log pretty or compact */
118
    private boolean tracePrettyPrint = TRACE_PRETTY_PRINT_DEFAULT;
1✔
119

120
    private static final String LOG_ARG = "{}";
121
    private static final String ERROR_OCCURRED = "Error occurred.";
122

123
    /**
124
     * Constructor.
125
     */
126
    public DefaultHttpClient() {
127
        this(HttpClients.createDefault(), new JacksonJsonSerializer());
1✔
128
    }
1✔
129

130
    /**
131
     * Constructor.
132
     * <p>
133
     * Parameters: - httpClient : the Apache CloseableHttpClient to use
134
     * <p>
135
     * Exceptions: - IllegalArgumentException : if any argument is null
136
     *
137
     * @param httpClient the http client
138
     */
139
    public DefaultHttpClient(CloseableHttpClient httpClient, JsonSerializer jsonSerializer) {
1✔
140
        this.httpClient = Util.throwIfNull(httpClient);
1✔
141
        this.jsonSerializer = jsonSerializer;
1✔
142
    }
1✔
143

144
    /**
145
     * Log to the SLF4J logger (level based upon response status code). Override this function to add logging
146
     * or capture performance metrics.
147
     *
148
     * @param request request
149
     * @param requestEntity request body
150
     * @param response response
151
     * @param responseEntity response body
152
     * @param durationMillis response time in ms
153
     */
154
    public void logRequest(HttpRequestBase request, HttpEntitySnapshot requestEntity,
155
                           HttpResponse response, HttpEntitySnapshot responseEntity, long durationMillis) throws IOException {
156

157
        logger.info("{} {}, Response Code:{}, Request completed in {} ms", request.getMethod(), request.getURI(),
1✔
158
                response.getStatusCode(), durationMillis);
1✔
159
        if (response.getStatusCode() != 200) {
1✔
160
            // log the request and response on error
161
            logger.warn(LOG_ARG, RequestAndResponseData.of(request, requestEntity, response, responseEntity, REQUEST_RESPONSE));
1✔
162
        } else {
163
            // log the summary request and response on success
164
            logger.debug(LOG_ARG, RequestAndResponseData.of(request, requestEntity, response, responseEntity, REQUEST_RESPONSE_SUMMARY));
1✔
165
        }
166
    }
1✔
167

168
    /**
169
     * Make an HTTP request and return the response.
170
     *
171
     * @param smartsheetRequest the smartsheet request
172
     * @return the HTTP response
173
     * @throws HttpClientException the HTTP client exception
174
     */
175
    public HttpResponse request(HttpRequest smartsheetRequest) throws HttpClientException {
176
        Util.throwIfNull(smartsheetRequest);
1✔
177
        if (smartsheetRequest.getUri() == null) {
1✔
178
            throw new IllegalArgumentException("A Request URI is required.");
×
179
        }
180

181
        long start = System.currentTimeMillis();
1✔
182

183
        HttpRequestBase apacheHttpRequest;
184
        HttpResponse smartsheetResponse;
185

186
        InputStream bodyStream = null;
1✔
187
        if (smartsheetRequest.getEntity() != null && smartsheetRequest.getEntity().getContent() != null) {
1✔
188
            bodyStream = smartsheetRequest.getEntity().getContent();
1✔
189
        }
190
        // the retry logic will consume the body stream so we make sure it supports mark/reset and mark it
191
        boolean canRetryRequest = bodyStream == null || bodyStream.markSupported();
1✔
192
        if (!canRetryRequest) {
1✔
193
            try {
194
                // attempt to wrap the body stream in a input-stream that does support mark/reset
195
                bodyStream = new ByteArrayInputStream(StreamUtil.readBytesFromStream(bodyStream));
1✔
196
                // close the old stream (just to be tidy) and then replace it with a reset-able stream
197
                smartsheetRequest.getEntity().getContent().close();
1✔
198
                smartsheetRequest.getEntity().setContent(bodyStream);
1✔
199
                canRetryRequest = true;
1✔
200
            } catch (IOException ignore) {
1✔
201
            }
1✔
202
        }
203

204
        // the retry loop
205
        while (true) {
206
            int attempt = 0;
1✔
207
            apacheHttpRequest = createApacheRequest(smartsheetRequest);
1✔
208

209
            // Set HTTP headers
210
            if (smartsheetRequest.getHeaders() != null) {
1✔
211
                for (Map.Entry<String, String> header : smartsheetRequest.getHeaders().entrySet()) {
1✔
212
                    apacheHttpRequest.addHeader(header.getKey(), header.getValue());
1✔
213
                }
1✔
214
            }
215

216
            HttpEntitySnapshot responseEntityCopy = null;
1✔
217
            // Set HTTP entity
218
            HttpEntitySnapshot requestEntityCopy = copyRequestEntity(smartsheetRequest, apacheHttpRequest);
1✔
219

220
            // mark the body so we can reset on retry
221
            if (canRetryRequest && bodyStream != null) {
1✔
222
                bodyStream.mark((int) smartsheetRequest.getEntity().getContentLength());
1✔
223
            }
224

225
            // Make the HTTP request
226
            smartsheetResponse = new HttpResponse();
1✔
227
            HttpContext context = new BasicHttpContext();
1✔
228
            try {
229
                long startTime = System.currentTimeMillis();
1✔
230
                apacheHttpResponse = this.httpClient.execute(apacheHttpRequest, context);
1✔
231
                long endTime = System.currentTimeMillis();
1✔
232

233
                HttpEntitySnapshot newResponseCopy = updateWithResponse(apacheHttpRequest, context, smartsheetResponse);
1✔
234
                if (newResponseCopy != null) {
1✔
235
                    responseEntityCopy = newResponseCopy;
1✔
236
                }
237

238
                long responseTime = endTime - startTime;
1✔
239
                logRequest(apacheHttpRequest, requestEntityCopy, smartsheetResponse, responseEntityCopy, responseTime);
1✔
240

241
                // trace-logging of request and response (if so configured)
242
                if (traces.size() > 0) {
1✔
243
                    RequestAndResponseData requestAndResponseData = RequestAndResponseData.of(apacheHttpRequest,
×
244
                            requestEntityCopy, smartsheetResponse, responseEntityCopy, traces);
245
                    TRACE_WRITER.println(requestAndResponseData.toString(tracePrettyPrint));
×
246
                }
247

248
                if (smartsheetResponse.getStatusCode() == 200) {
1✔
249
                    // call successful, exit the retry loop
250
                    break;
1✔
251
                }
252

253
                // the retry logic might consume the content stream so we make sure it supports mark/reset and mark it
254
                InputStream contentStream = smartsheetResponse.getEntity().getContent();
1✔
255
                if (!contentStream.markSupported()) {
1✔
256
                    // wrap the response stream in a input-stream that does support mark/reset
257
                    contentStream = new ByteArrayInputStream(StreamUtil.readBytesFromStream(contentStream));
×
258
                    // close the old stream (just to be tidy) and then replace it with a reset-able stream
259
                    smartsheetResponse.getEntity().getContent().close();
×
260
                    smartsheetResponse.getEntity().setContent(contentStream);
×
261
                }
262
                try {
263
                    contentStream.mark((int) smartsheetResponse.getEntity().getContentLength());
1✔
264
                    long timeSpent = System.currentTimeMillis() - start;
1✔
265
                    if (!shouldRetry(++attempt, timeSpent, smartsheetResponse)) {
1✔
266
                        // should not retry, or retry time exceeded, exit the retry loop
267
                        break;
268
                    }
269
                } finally {
270
                    if (bodyStream != null) {
1✔
271
                        bodyStream.reset();
1✔
272
                    }
273
                    contentStream.reset();
1✔
274
                }
275
                // moving this to finally causes issues because socket is closed (which means response stream is closed)
276
                this.releaseConnection();
×
277

278
            } catch (ClientProtocolException e) {
1✔
279
                logger.warn("ClientProtocolException " + e.getMessage());
1✔
280
                logger.warn(LOG_ARG, RequestAndResponseData.of(apacheHttpRequest, requestEntityCopy, smartsheetResponse,
1✔
281
                        responseEntityCopy, REQUEST_RESPONSE_SUMMARY));
282
                try {
283
                    // if this is a PUT and was retried by the http client, the body content stream is at the
284
                    // end and is a NonRepeatableRequest. If we marked the body content stream prior to execute,
285
                    // reset and retry
286
                    if (canRetryRequest && e.getCause() instanceof NonRepeatableRequestException) {
1✔
287
                        if (smartsheetRequest.getEntity() != null) {
×
288
                            smartsheetRequest.getEntity().getContent().reset();
×
289
                        }
290
                        continue;
×
291
                    }
292
                } catch (IOException ignore) {
×
293
                }
1✔
294
                throw new HttpClientException(ERROR_OCCURRED, e);
1✔
295
            } catch (NoHttpResponseException e) {
×
296
                logger.warn("NoHttpResponseException " + e.getMessage());
×
297
                logger.warn(LOG_ARG, RequestAndResponseData.of(apacheHttpRequest, requestEntityCopy, smartsheetResponse,
×
298
                        responseEntityCopy, REQUEST_RESPONSE_SUMMARY));
299
                try {
300
                    // check to see if the response was empty and this was a POST. All other HTTP methods
301
                    // will be automatically retried by the http client.
302
                    // (POST is non-idempotent and is not retried automatically, but is safe for us to retry)
303
                    if (canRetryRequest && smartsheetRequest.getMethod() == HttpMethod.POST) {
×
304
                        if (smartsheetRequest.getEntity() != null) {
×
305
                            smartsheetRequest.getEntity().getContent().reset();
×
306
                        }
307
                        continue;
×
308
                    }
309
                } catch (IOException ignore) {
×
310
                }
×
311
                throw new HttpClientException(ERROR_OCCURRED, e);
×
312
            } catch (IOException e) {
×
313
                logger.warn(LOG_ARG, RequestAndResponseData.of(apacheHttpRequest, requestEntityCopy, smartsheetResponse,
×
314
                        responseEntityCopy, REQUEST_RESPONSE_SUMMARY));
315
                throw new HttpClientException(ERROR_OCCURRED, e);
×
316
            }
×
317
        }
×
318
        return smartsheetResponse;
1✔
319
    }
320

321
    private HttpEntitySnapshot copyRequestEntity(HttpRequest smartsheetRequest, HttpRequestBase apacheHttpRequest) {
322
        final HttpEntity entity = smartsheetRequest.getEntity();
1✔
323
        HttpEntitySnapshot requestEntityCopy = null;
1✔
324
        if (apacheHttpRequest instanceof HttpEntityEnclosingRequestBase && entity != null && entity.getContent() != null) {
1✔
325
            try {
326
                // we need access to the original request stream so we can log it (in the event of errors and/or tracing)
327
                requestEntityCopy = new HttpEntitySnapshot(entity);
1✔
328
            } catch (IOException iox) {
×
329
                logger.error("failed to make copy of original request entity", iox);
×
330
            }
1✔
331

332
            InputStreamEntity streamEntity = new InputStreamEntity(entity.getContent(), entity.getContentLength());
1✔
333
            // why?  not supported by library?
334
            streamEntity.setChunked(false);
1✔
335
            ((HttpEntityEnclosingRequestBase) apacheHttpRequest).setEntity(streamEntity);
1✔
336
        }
337
        return requestEntityCopy;
1✔
338
    }
339

340
    @Nullable
341
    private HttpEntitySnapshot updateWithResponse(HttpRequestBase apacheHttpRequest, HttpContext context,
342
                                                  HttpResponse smartsheetResponse) throws IOException {
343
        // Set request headers to values ACTUALLY SENT (not just created by us), this would include:
344
        // 'Connection', 'Accept-Encoding', etc. However, if a proxy is used, this may be the proxy's CONNECT
345
        // request, hence the test for HTTP method first
346
        Object httpRequest = context.getAttribute("http.request");
1✔
347
        if (httpRequest != null && HttpRequestWrapper.class.isAssignableFrom(httpRequest.getClass())) {
1✔
348
            HttpRequestWrapper actualRequest = (HttpRequestWrapper) httpRequest;
1✔
349
            switch (HttpMethod.valueOf(actualRequest.getMethod())) {
1✔
350
                case GET:
351
                case POST:
352
                case PUT:
353
                case DELETE:
354
                    apacheHttpRequest.setHeaders(actualRequest.getAllHeaders());
1✔
355
                    break;
1✔
356
                default:
357
                    break;
358
            }
359
        }
360

361
        // Set returned headers
362
        smartsheetResponse.setHeaders(new HashMap<>());
1✔
363
        for (Header header : apacheHttpResponse.getAllHeaders()) {
1✔
364
            smartsheetResponse.getHeaders().put(header.getName(), header.getValue());
1✔
365
        }
366
        smartsheetResponse.setStatus(apacheHttpResponse.getStatusLine().getStatusCode(),
1✔
367
                apacheHttpResponse.getStatusLine().toString());
1✔
368

369
        // Set returned entities
370
        if (apacheHttpResponse.getEntity() != null) {
1✔
371
            HttpEntity httpEntity = new HttpEntity();
1✔
372
            httpEntity.setContentType(apacheHttpResponse.getEntity().getContentType().getValue());
1✔
373
            httpEntity.setContentLength(apacheHttpResponse.getEntity().getContentLength());
1✔
374
            httpEntity.setContent(apacheHttpResponse.getEntity().getContent());
1✔
375
            smartsheetResponse.setEntity(httpEntity);
1✔
376
            return new HttpEntitySnapshot(httpEntity);
1✔
377
        }
378

379
        return null;
×
380
    }
381

382
    /**
383
     * Create the Apache HTTP request. Override this function to inject additional
384
     * haaders in the request or use a proxy.
385
     *
386
     * @param smartsheetRequest (request method and base URI come from here)
387
     * @return the Apache HTTP request
388
     */
389
    public HttpRequestBase createApacheRequest(HttpRequest smartsheetRequest) {
390
        HttpRequestBase apacheHttpRequest;
391

392
        // Create Apache HTTP request based on the smartsheetRequest request type
393
        switch (smartsheetRequest.getMethod()) {
1✔
394
            case GET:
395
                apacheHttpRequest = new HttpGet(smartsheetRequest.getUri());
1✔
396
                break;
1✔
397
            case POST:
398
                apacheHttpRequest = new HttpPost(smartsheetRequest.getUri());
1✔
399
                break;
1✔
400
            case PUT:
401
                apacheHttpRequest = new HttpPut(smartsheetRequest.getUri());
1✔
402
                break;
1✔
403
            case DELETE:
404
                apacheHttpRequest = new HttpDelete(smartsheetRequest.getUri());
1✔
405
                break;
1✔
406
            default:
407
                throw new UnsupportedOperationException("Request method " + smartsheetRequest.getMethod() + " is not supported!");
×
408
        }
409

410
        RequestConfig.Builder builder = RequestConfig.custom();
1✔
411
        if (apacheHttpRequest.getConfig() != null) {
1✔
412
            builder = RequestConfig.copy(apacheHttpRequest.getConfig());
×
413
        }
414
        builder.setRedirectsEnabled(true);
1✔
415
        RequestConfig config = builder.build();
1✔
416
        apacheHttpRequest.setConfig(config);
1✔
417
        return apacheHttpRequest;
1✔
418
    }
419

420
    /**
421
     * Set the max retry time for API calls which fail and are retry-able.
422
     */
423
    public void setMaxRetryTimeMillis(long maxRetryTimeMillis) {
424
        this.maxRetryTimeMillis = maxRetryTimeMillis;
×
425
    }
×
426

427
    /**
428
     * The backoff calculation routine. Uses exponential backoff. If the maximum elapsed time
429
     * has expired, this calculation returns -1 causing the caller to fall out of the retry loop.
430
     * @return -1 to fall out of retry loop, positive number indicates backoff time
431
     */
432
    public long calcBackoff(int previousAttempts, long totalElapsedTimeMillis, Error error) {
433

434
        long backoffMillis = (long) (Math.pow(2, previousAttempts) * 1000) + new Random().nextInt(1000);
×
435

436
        if (totalElapsedTimeMillis + backoffMillis > maxRetryTimeMillis) {
×
437
            logger.info("Elapsed time " + totalElapsedTimeMillis + " + backoff time " + backoffMillis +
×
438
                    " exceeds max retry time " + maxRetryTimeMillis + ", exiting retry loop");
439
            return -1;
×
440
        }
441
        return backoffMillis;
×
442
    }
443

444
    /**
445
     * Called when an API request fails to determine if it can retry the request.
446
     * Calls calcBackoff to determine the time to wait in between retries.
447
     *
448
     * @param previousAttempts number of attempts (including this one) to execute request
449
     * @param totalElapsedTimeMillis total time spent in millis for all previous (and this) attempt
450
     * @param response the failed HttpResponse
451
     * @return true if this request can be retried
452
     */
453
    public boolean shouldRetry(int previousAttempts, long totalElapsedTimeMillis, HttpResponse response) {
454
        String contentType = response.getEntity().getContentType();
1✔
455
        if (contentType != null && !contentType.startsWith(JSON_MIME_TYPE)) {
1✔
456
            // it's not JSON; don't even try to parse it
457
            return false;
×
458
        }
459
        Error error;
460
        try {
461
            error = jsonSerializer.deserialize(Error.class, response.getEntity().getContent());
1✔
462
        } catch (IOException e) {
×
463
            return false;
×
464
        }
1✔
465
        switch (error.getErrorCode()) {
1✔
466
            case 4001:
467
                // Smartsheet.com is currently offline for system maintenance. Please check back again shortly.
468
            case 4002:
469
                // Server timeout exceeded. Request has failed
470
            case 4003:
471
                // Rate limit exceeded.
472
            case 4004:
473
                // An unexpected error has occurred. Please retry your request
474
                // If you encounter this error repeatedly, please contact api@smartsheet.com for assistance
475
                break;
×
476
            default:
477
                return false;
1✔
478
        }
479

480
        long backoffMillis = calcBackoff(previousAttempts, totalElapsedTimeMillis, error);
×
481
        if (backoffMillis < 0) {
×
482
            return false;
×
483
        }
484

485
        logger.info("HttpError StatusCode=" + response.getStatusCode() + ": Retrying in " + backoffMillis + " milliseconds");
×
486
        try {
487
            Thread.sleep(backoffMillis);
×
488
        } catch (InterruptedException e) {
×
489
            logger.warn("sleep interrupted", e);
×
490
            return false;
×
491
        }
×
492
        return true;
×
493
    }
494

495
    /**
496
     * Close the HttpClient.
497
     *
498
     * @throws IOException Signals that an I/O exception has occurred.
499
     */
500
    @Override
501
    public void close() throws IOException {
502
        this.httpClient.close();
1✔
503
    }
1✔
504

505
    /* (non-Javadoc)
506
     * @see com.smartsheet.api.internal.http.HttpClient#releaseConnection()
507
     */
508
    @Override
509
    public void releaseConnection() {
510
        if (apacheHttpResponse != null) {
1✔
511
            try {
512
                apacheHttpResponse.close();
1✔
513
                apacheHttpResponse = null;
1✔
514
            } catch (IOException e) {
×
515
                logger.error("error closing Apache HttpResponse", e);
×
516
            }
1✔
517
        }
518
    }
1✔
519

520
    /**
521
     * set the traces for this client
522
     * @param traces the fields to include in trace-logging
523
     */
524
    public void setTraces(Trace... traces) {
525
        this.traces.clear();
×
526
        for (Trace trace : traces) {
×
527
            if (!trace.addReplacements(this.traces)) {
×
528
                this.traces.add(trace);
×
529
            }
530
        }
531
    }
×
532

533
    /**
534
     * set whether to use nicely-formatted JSON or more compact format JSON in trace logging
535
     * @param pretty whether to print JSON in a "pretty" format or compact
536
     */
537
    public void setTracePrettyPrint(boolean pretty) {
538
        tracePrettyPrint = pretty;
×
539
    }
×
540

541
    /** only included for testing purposes */
542
    public static void setTraceStream(OutputStream traceStream) {
543
        TRACE_WRITER = new PrintWriter(traceStream, true);
1✔
544
    }
1✔
545
}
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