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

smartsheet / smartsheet-java-sdk / #44

25 Aug 2023 05:39PM UTC coverage: 50.55% (+0.1%) from 50.427%
#44

push

github-actions

web-flow
Fix remaining Checkstyle violations and Enable Checkstyle (#58)

* Fix remaining Checkstyle violations and Enable Checkstyle

We are now down to `20` checkstyle violations in main and `0` violations in test.

The remaining 20 violations are not trivial to fix, so I've set checkstyle to allow those 20 violations to exist, but to fail the build if we ever exceed 20 violations. This should make the build fail if any new violations are added.

For tests, we do not allow _any_ violations. This means adding a single violation will fail the build. Once the 20 violations in main are cleaned up, we can make main and test have the same config.

Note: This MR also changes our PR pipeline to run `./gradlew clean build` instead of `./gradlew clean test`. The reason for this is that build runs all the tests and performs all the other checks (such as checkstyle), whereas `test` didn't run checkstyle and we wouldn't have noticed violations until we tried to deploy.

148 of 148 new or added lines in 24 files covered. (100.0%)

3448 of 6821 relevant lines covered (50.55%)

0.51 hits per line

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

67.19
/src/main/java/com/smartsheet/api/internal/http/DefaultHttpClient.java
1
package com.smartsheet.api.internal.http;
2

3
/*
4
 * #[license]
5
 * Smartsheet SDK for Java
6
 * %%
7
 * Copyright (C) 2023 Smartsheet
8
 * %%
9
 * Licensed under the Apache License, Version 2.0 (the "License");
10
 * you may not use this file except in compliance with the License.
11
 * You may obtain a copy of the License at
12
 *
13
 *      http://www.apache.org/licenses/LICENSE-2.0
14
 *
15
 * Unless required by applicable law or agreed to in writing, software
16
 * distributed under the License is distributed on an "AS IS" BASIS,
17
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
 * See the License for the specific language governing permissions and
19
 * limitations under the License.
20
 * %[license]
21
 */
22

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

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

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

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

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

79
    protected JsonSerializer jsonSerializer;
80

81
    protected long maxRetryTimeMillis = 15000;
1✔
82

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

90
    /** The apache http response. */
91
    private CloseableHttpResponse apacheHttpResponse;
92

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

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

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

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

106
    /** where to send trace logs */
107
    private static PrintWriter TRACE_WRITER;
108

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

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

120
    /** whether to log pretty or compact */
121
    private boolean tracePrettyPrint = TRACE_PRETTY_PRINT_DEFAULT;
1✔
122

123
    private static final String LOG_ARG = "{}";
124
    private static final String ERROR_OCCURRED = "Error occurred.";
125

126
    /**
127
     * Constructor.
128
     */
129
    public DefaultHttpClient() {
130
        this(HttpClients.createDefault(), new JacksonJsonSerializer());
1✔
131
    }
1✔
132

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

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

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

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

184
        int attempt = 0;
1✔
185
        long start = System.currentTimeMillis();
1✔
186

187
        HttpRequestBase apacheHttpRequest;
188
        HttpResponse smartsheetResponse;
189

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

208
        // the retry loop
209
        while (true) {
210

211
            apacheHttpRequest = createApacheRequest(smartsheetRequest);
1✔
212

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

220
            HttpEntitySnapshot requestEntityCopy = null;
1✔
221
            HttpEntitySnapshot responseEntityCopy = null;
1✔
222
            // Set HTTP entity
223
            final HttpEntity entity = smartsheetRequest.getEntity();
1✔
224
            if (apacheHttpRequest instanceof HttpEntityEnclosingRequestBase && entity != null && entity.getContent() != null) {
1✔
225
                try {
226
                    // we need access to the original request stream so we can log it (in the event of errors and/or tracing)
227
                    requestEntityCopy = new HttpEntitySnapshot(entity);
1✔
228
                } catch (IOException iox) {
×
229
                    logger.error("failed to make copy of original request entity", iox);
×
230
                }
1✔
231

232
                InputStreamEntity streamEntity = new InputStreamEntity(entity.getContent(), entity.getContentLength());
1✔
233
                // why?  not supported by library?
234
                streamEntity.setChunked(false);
1✔
235
                ((HttpEntityEnclosingRequestBase) apacheHttpRequest).setEntity(streamEntity);
1✔
236
            }
237

238
            // mark the body so we can reset on retry
239
            if (canRetryRequest && bodyStream != null) {
1✔
240
                bodyStream.mark((int) smartsheetRequest.getEntity().getContentLength());
1✔
241
            }
242

243
            // Make the HTTP request
244
            smartsheetResponse = new HttpResponse();
1✔
245
            HttpContext context = new BasicHttpContext();
1✔
246
            try {
247
                long startTime = System.currentTimeMillis();
1✔
248
                apacheHttpResponse = this.httpClient.execute(apacheHttpRequest, context);
1✔
249
                long endTime = System.currentTimeMillis();
1✔
250

251
                // Set request headers to values ACTUALLY SENT (not just created by us), this would include:
252
                // 'Connection', 'Accept-Encoding', etc. However, if a proxy is used, this may be the proxy's CONNECT
253
                // request, hence the test for HTTP method first
254
                Object httpRequest = context.getAttribute("http.request");
1✔
255
                if (httpRequest != null && HttpRequestWrapper.class.isAssignableFrom(httpRequest.getClass())) {
1✔
256
                    HttpRequestWrapper actualRequest = (HttpRequestWrapper) httpRequest;
1✔
257
                    switch (HttpMethod.valueOf(actualRequest.getMethod())) {
1✔
258
                        case GET:
259
                        case POST:
260
                        case PUT:
261
                        case DELETE:
262
                            apacheHttpRequest.setHeaders(((HttpRequestWrapper) httpRequest).getAllHeaders());
1✔
263
                            break;
264
                    }
265
                }
266

267
                // Set returned headers
268
                smartsheetResponse.setHeaders(new HashMap<>());
1✔
269
                for (Header header : apacheHttpResponse.getAllHeaders()) {
1✔
270
                    smartsheetResponse.getHeaders().put(header.getName(), header.getValue());
1✔
271
                }
272
                smartsheetResponse.setStatus(apacheHttpResponse.getStatusLine().getStatusCode(),
1✔
273
                        apacheHttpResponse.getStatusLine().toString());
1✔
274

275
                // Set returned entities
276
                if (apacheHttpResponse.getEntity() != null) {
1✔
277
                    HttpEntity httpEntity = new HttpEntity();
1✔
278
                    httpEntity.setContentType(apacheHttpResponse.getEntity().getContentType().getValue());
1✔
279
                    httpEntity.setContentLength(apacheHttpResponse.getEntity().getContentLength());
1✔
280
                    httpEntity.setContent(apacheHttpResponse.getEntity().getContent());
1✔
281
                    smartsheetResponse.setEntity(httpEntity);
1✔
282
                    responseEntityCopy = new HttpEntitySnapshot(httpEntity);
1✔
283
                }
284

285
                long responseTime = endTime - startTime;
1✔
286
                logRequest(apacheHttpRequest, requestEntityCopy, smartsheetResponse, responseEntityCopy, responseTime);
1✔
287

288
                // trace-logging of request and response (if so configured)
289
                if (traces.size() > 0) {
1✔
290
                    RequestAndResponseData requestAndResponseData = RequestAndResponseData.of(apacheHttpRequest,
×
291
                            requestEntityCopy, smartsheetResponse, responseEntityCopy, traces);
292
                    TRACE_WRITER.println(requestAndResponseData.toString(tracePrettyPrint));
×
293
                }
294

295
                if (smartsheetResponse.getStatusCode() == 200) {
1✔
296
                    // call successful, exit the retry loop
297
                    break;
1✔
298
                }
299

300
                // the retry logic might consume the content stream so we make sure it supports mark/reset and mark it
301
                InputStream contentStream = smartsheetResponse.getEntity().getContent();
1✔
302
                if (!contentStream.markSupported()) {
1✔
303
                    // wrap the response stream in a input-stream that does support mark/reset
304
                    contentStream = new ByteArrayInputStream(StreamUtil.readBytesFromStream(contentStream));
×
305
                    // close the old stream (just to be tidy) and then replace it with a reset-able stream
306
                    smartsheetResponse.getEntity().getContent().close();
×
307
                    smartsheetResponse.getEntity().setContent(contentStream);
×
308
                }
309
                try {
310
                    contentStream.mark((int) smartsheetResponse.getEntity().getContentLength());
1✔
311
                    long timeSpent = System.currentTimeMillis() - start;
1✔
312
                    if (!shouldRetry(++attempt, timeSpent, smartsheetResponse)) {
1✔
313
                        // should not retry, or retry time exceeded, exit the retry loop
314
                        break;
315
                    }
316
                } finally {
317
                    if (bodyStream != null) {
1✔
318
                        bodyStream.reset();
1✔
319
                    }
320
                    contentStream.reset();
1✔
321
                }
322
                // moving this to finally causes issues because socket is closed (which means response stream is closed)
323
                this.releaseConnection();
×
324

325
            } catch (ClientProtocolException e) {
1✔
326
                try {
327
                    logger.warn("ClientProtocolException " + e.getMessage());
1✔
328
                    logger.warn(LOG_ARG, RequestAndResponseData.of(apacheHttpRequest, requestEntityCopy, smartsheetResponse,
1✔
329
                            responseEntityCopy, REQUEST_RESPONSE_SUMMARY));
330
                    // if this is a PUT and was retried by the http client, the body content stream is at the
331
                    // end and is a NonRepeatableRequest. If we marked the body content stream prior to execute,
332
                    // reset and retry
333
                    if (canRetryRequest && e.getCause() instanceof NonRepeatableRequestException) {
1✔
334
                        if (smartsheetRequest.getEntity() != null) {
×
335
                            smartsheetRequest.getEntity().getContent().reset();
×
336
                        }
337
                        continue;
×
338
                    }
339
                } catch (IOException ignore) {
×
340
                }
1✔
341
                throw new HttpClientException(ERROR_OCCURRED, e);
1✔
342
            } catch (NoHttpResponseException e) {
×
343
                try {
344
                    logger.warn("NoHttpResponseException " + e.getMessage());
×
345
                    logger.warn(LOG_ARG, RequestAndResponseData.of(apacheHttpRequest, requestEntityCopy, smartsheetResponse,
×
346
                            responseEntityCopy, REQUEST_RESPONSE_SUMMARY));
347
                    // check to see if the response was empty and this was a POST. All other HTTP methods
348
                    // will be automatically retried by the http client.
349
                    // (POST is non-idempotent and is not retried automatically, but is safe for us to retry)
350
                    if (canRetryRequest && smartsheetRequest.getMethod() == HttpMethod.POST) {
×
351
                        if (smartsheetRequest.getEntity() != null) {
×
352
                            smartsheetRequest.getEntity().getContent().reset();
×
353
                        }
354
                        continue;
×
355
                    }
356
                } catch (IOException ignore) {
×
357
                }
×
358
                throw new HttpClientException(ERROR_OCCURRED, e);
×
359
            } catch (IOException e) {
×
360
                try {
361
                    logger.warn(LOG_ARG, RequestAndResponseData.of(apacheHttpRequest, requestEntityCopy, smartsheetResponse,
×
362
                            responseEntityCopy, REQUEST_RESPONSE_SUMMARY));
363
                } catch (IOException ignore) {
×
364
                }
×
365
                throw new HttpClientException(ERROR_OCCURRED, e);
×
366
            }
×
367
        }
×
368
        return smartsheetResponse;
1✔
369
    }
370

371
    /**
372
     * Create the Apache HTTP request. Override this function to inject additional
373
     * haaders in the request or use a proxy.
374
     *
375
     * @param smartsheetRequest (request method and base URI come from here)
376
     * @return the Apache HTTP request
377
     */
378
    public HttpRequestBase createApacheRequest(HttpRequest smartsheetRequest) {
379
        HttpRequestBase apacheHttpRequest;
380

381
        // Create Apache HTTP request based on the smartsheetRequest request type
382
        switch (smartsheetRequest.getMethod()) {
1✔
383
            case GET:
384
                apacheHttpRequest = new HttpGet(smartsheetRequest.getUri());
1✔
385
                break;
1✔
386
            case POST:
387
                apacheHttpRequest = new HttpPost(smartsheetRequest.getUri());
1✔
388
                break;
1✔
389
            case PUT:
390
                apacheHttpRequest = new HttpPut(smartsheetRequest.getUri());
1✔
391
                break;
1✔
392
            case DELETE:
393
                apacheHttpRequest = new HttpDelete(smartsheetRequest.getUri());
1✔
394
                break;
1✔
395
            default:
396
                throw new UnsupportedOperationException("Request method " + smartsheetRequest.getMethod() + " is not supported!");
×
397
        }
398

399
        RequestConfig.Builder builder = RequestConfig.custom();
1✔
400
        if (apacheHttpRequest.getConfig() != null) {
1✔
401
            builder = RequestConfig.copy(apacheHttpRequest.getConfig());
×
402
        }
403
        builder.setRedirectsEnabled(true);
1✔
404
        RequestConfig config = builder.build();
1✔
405
        apacheHttpRequest.setConfig(config);
1✔
406
        return apacheHttpRequest;
1✔
407
    }
408

409
    /**
410
     * Set the max retry time for API calls which fail and are retry-able.
411
     */
412
    public void setMaxRetryTimeMillis(long maxRetryTimeMillis) {
413
        this.maxRetryTimeMillis = maxRetryTimeMillis;
×
414
    }
×
415

416
    /**
417
     * The backoff calculation routine. Uses exponential backoff. If the maximum elapsed time
418
     * has expired, this calculation returns -1 causing the caller to fall out of the retry loop.
419
     * @return -1 to fall out of retry loop, positive number indicates backoff time
420
     */
421
    public long calcBackoff(int previousAttempts, long totalElapsedTimeMillis, Error error) {
422

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

425
        if (totalElapsedTimeMillis + backoffMillis > maxRetryTimeMillis) {
×
426
            logger.info("Elapsed time " + totalElapsedTimeMillis + " + backoff time " + backoffMillis +
×
427
                    " exceeds max retry time " + maxRetryTimeMillis + ", exiting retry loop");
428
            return -1;
×
429
        }
430
        return backoffMillis;
×
431
    }
432

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

469
        long backoffMillis = calcBackoff(previousAttempts, totalElapsedTimeMillis, error);
×
470
        if (backoffMillis < 0) {
×
471
            return false;
×
472
        }
473

474
        logger.info("HttpError StatusCode=" + response.getStatusCode() + ": Retrying in " + backoffMillis + " milliseconds");
×
475
        try {
476
            Thread.sleep(backoffMillis);
×
477
        } catch (InterruptedException e) {
×
478
            logger.warn("sleep interrupted", e);
×
479
            return false;
×
480
        }
×
481
        return true;
×
482
    }
483

484
    /**
485
     * Close the HttpClient.
486
     *
487
     * @throws IOException Signals that an I/O exception has occurred.
488
     */
489
    @Override
490
    public void close() throws IOException {
491
        this.httpClient.close();
1✔
492
    }
1✔
493

494
    /* (non-Javadoc)
495
     * @see com.smartsheet.api.internal.http.HttpClient#releaseConnection()
496
     */
497
    @Override
498
    public void releaseConnection() {
499
        if (apacheHttpResponse != null) {
1✔
500
            try {
501
                apacheHttpResponse.close();
1✔
502
                apacheHttpResponse = null;
1✔
503
            } catch (IOException e) {
×
504
                logger.error("error closing Apache HttpResponse", e);
×
505
            }
1✔
506
        }
507
    }
1✔
508

509
    /**
510
     * set the traces for this client
511
     * @param traces the fields to include in trace-logging
512
     */
513
    public void setTraces(Trace... traces) {
514
        this.traces.clear();
×
515
        for (Trace trace : traces) {
×
516
            if (!trace.addReplacements(this.traces)) {
×
517
                this.traces.add(trace);
×
518
            }
519
        }
520
    }
×
521

522
    /**
523
     * set whether to use nicely-formatted JSON or more compact format JSON in trace logging
524
     * @param pretty whether to print JSON in a "pretty" format or compact
525
     */
526
    public void setTracePrettyPrint(boolean pretty) {
527
        tracePrettyPrint = pretty;
×
528
    }
×
529

530
    /** only included for testing purposes */
531
    public static void setTraceStream(OutputStream traceStream) {
532
        TRACE_WRITER = new PrintWriter(traceStream, true);
1✔
533
    }
1✔
534
}
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