Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
// Generated by delombok at Thu Oct 10 18:56:32 UTC 2024
/* SPDX-License-Identifier: Apache-2.0
Copyright 2022 Atlan Pte. Ltd. */
package com.atlan.net;
/* Based on original code from https://github.com/stripe/stripe-java (under MIT license) */
import com.atlan.Atlan;
import com.atlan.AtlanClient;
import com.atlan.exception.ApiConnectionException;
import com.atlan.exception.ApiException;
import com.atlan.exception.AtlanException;
import com.atlan.serde.Serde;
import com.atlan.util.Stopwatch;
import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
/**
* Base abstract class for HTTP clients used to send requests to Atlan's API.
*/
public abstract class HttpClient {
@java.lang.SuppressWarnings("all")
@lombok.Generated
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(HttpClient.class);
/**
* Maximum sleep time between tries to send HTTP requests after network failure.
*/
public static final Duration maxNetworkRetriesDelay = Duration.ofSeconds(5);
/**
* Minimum sleep time between tries to send HTTP requests after network failure.
*/
public static final Duration minNetworkRetriesDelay = Duration.ofMillis(500);
/**
* A value indicating whether the client should sleep between automatic request retries.
*/
boolean networkRetriesSleep = true;
/**
* Initializes a new instance of the {@link HttpClient} class.
*/
protected HttpClient() {
}
/**
* Sends the given request to Atlan's API, buffering the response body into memory.
*
* @param request the request
* @return the response
* @throws AtlanException If the request fails for any reason
*/
public abstract AtlanResponse request(AtlanRequest request) throws AtlanException;
/**
* Sends the given request to Atlan's API, and returns a buffered response.
*
* @param request the request
* @return the response
* @throws ApiConnectionException if an error occurs when sending or receiving
*/
public abstract AtlanEventStreamResponse requestES(AtlanRequest request) throws AtlanException;
/**
* Sends the given request to Atlan's API, streaming the response body.
*
* @param request the request
* @return the response
* @throws AtlanException If the request fails for any reason
*/
public AtlanResponseStream requestStream(AtlanRequest request) throws AtlanException {
throw new UnsupportedOperationException("requestStream is unimplemented for this HttpClient");
}
@FunctionalInterface
private interface RequestSendFunction {
R apply(AtlanRequest request) throws AtlanException;
}
private > T sendWithTelemetry(AtlanRequest request, RequestSendFunction send) throws AtlanException {
if (!Atlan.enableTelemetry) {
// If telemetry is disabled, just make the request and respond
return send.apply(request);
} else {
// Otherwise, time the request / response and embed the metrics back into the response itself
Stopwatch stopwatch = Stopwatch.startNew();
T response = send.apply(request);
stopwatch.stop();
RequestMetrics.embed(response, stopwatch.getElapsed());
return response;
}
}
/**
* Sends the given request to Atlan's API, handling telemetry if not disabled.
*
* @param request the request
* @return the response
* @throws AtlanException If the request fails for any reason
*/
public AtlanResponse requestWithTelemetry(AtlanRequest request) throws AtlanException {
return sendWithTelemetry(request, this::request);
}
/**
* Sends the given request to Atlan's API, retrying if it encounters certain problems.
*
* @param request the request
* @param send the function to use for sending the request (e.g. with or without telemetry)
* @return the response
* @param the type of the response
* @throws AtlanException if the request fails for any reason, even after retries
*/
public > T sendWithRetries(AtlanRequest request, RequestSendFunction send) throws AtlanException {
AtlanException requestException = null;
T response = null;
int retry = 0;
while (true) {
requestException = null;
try {
response = send.apply(request);
} catch (ApiConnectionException e) {
requestException = e;
}
if (!this.shouldRetry(retry, requestException, request, response)) {
break;
}
retry += 1;
try {
Thread.sleep(this.sleepTime(retry).toMillis());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
if (requestException != null) {
throw requestException;
}
response.numRetries(retry);
return response;
}
/**
* Sends the given request to Atlan's API, retrying the request in cases of intermittent problems.
*
* @param request the request
* @return the response
* @throws AtlanException If the request fails for any reason
*/
public AtlanResponse requestWithRetries(AtlanRequest request) throws AtlanException {
return sendWithRetries(request, r -> this.requestWithTelemetry(r));
}
/**
* Sends the given request to Atlan's API, retrying the request in cases of intermittent problems.
*
* @param request the request
* @return the response
* @throws AtlanException If the request fails for any reason
*/
public AtlanEventStreamResponse requestEventStream(AtlanRequest request) throws AtlanException {
AtlanException requestException = null;
AtlanEventStreamResponse response = null;
try {
response = requestES(request);
} catch (ApiException e) {
requestException = e;
}
if (requestException != null) {
throw requestException;
}
return response;
}
/**
* Builds the value of the {@code User-Agent} header.
*
* @param client through which to connect to Atlan
* @return a string containing the value of the {@code User-Agent} header
*/
protected static String buildUserAgentString(AtlanClient client) {
String userAgent = String.format("Atlan-JavaSDK/%s", Atlan.VERSION);
if (client.getAppInfo() != null) {
userAgent += " " + formatAppInfo(client.getAppInfo());
}
return userAgent;
}
/**
* Builds the value of the {@code X-Atlan-Client-User-Agent} header.
*
* @param client through which to connect to Atlan
* @return a string containing the value of the {@code X-Atlan-Client-User-Agent} header
*/
protected static String buildXAtlanClientUserAgentString(AtlanClient client) {
String[] propertyNames = {"os.name", "os.version", "os.arch", "java.version", "java.vendor", "java.vm.version", "java.vm.vendor"};
Map propertyMap = new HashMap<>();
for (String propertyName : propertyNames) {
propertyMap.put(propertyName, System.getProperty(propertyName));
}
propertyMap.put("bindings.version", Atlan.VERSION);
propertyMap.put("lang", "Java");
propertyMap.put("publisher", "Atlan");
try {
if (client.getAppInfo() != null) {
propertyMap.put("application", Serde.allInclusiveMapper.writeValueAsString(client.getAppInfo()));
}
return Serde.allInclusiveMapper.writeValueAsString(propertyMap);
} catch (IOException e) {
throw new RuntimeException("Unable to build client user agent string.", e);
}
}
private static String formatAppInfo(Map info) {
String str = info.get("name");
if (info.get("version") != null) {
str += String.format("/%s", info.get("version"));
}
if (info.get("url") != null) {
str += String.format(" (%s)", info.get("url"));
}
return str;
}
private > boolean shouldRetry(int numRetries, AtlanException exception, AtlanRequest request, T response) {
// Do not retry if we are out of retries.
if (numRetries >= request.options().getMaxNetworkRetries()) {
if (exception != null) {
log.error(" ... beyond max retries ({}), failing! If this is unexpected, you can try increasing the maximum retries through Atlan.setMaxNetworkRetries()", request.options().getMaxNetworkRetries(), exception);
} else {
log.error(" ... beyond max retries ({}), failing! If this is unexpected, you can try increasing the maximum retries through Atlan.setMaxNetworkRetries()", request.options().getMaxNetworkRetries());
}
return false;
}
// Retry on connection error.
if ((exception != null) && (exception.getCause() != null) && (exception.getCause() instanceof ConnectException || exception.getCause() instanceof SocketTimeoutException)) {
log.debug(" ... network issue, will retry.", exception);
return true;
}
if (response != null) {
if (response.code() == 401) {
// Retry authentication on an authentication failure (token could have expired)
String userId = request.client().getUserId();
if (userId != null) {
try {
log.info(" ... authentication failed, attempting to exchange new token for user: {}", userId);
String token = request.client().impersonate.user(userId);
request.client().setApiToken(token);
return true;
} catch (AtlanException e) {
log.warn(" ... attempt to impersonate user {} failed, not retrying.", userId, exception);
}
}
// If there is no user to impersonate, no need to retry, just short-circuit to failure
return false;
} else if (response.code() == 403) {
// Retry on permission failure (since these are granted asynchronously)
if (exception != null) {
log.debug(" ... no permission for the operation (yet), will retry: {}", response.body(), exception);
} else {
log.debug(" ... no permission for the operation (yet), will retry: {}", response.body());
}
} else if (response.code() >= 500) {
// Retry on 500, 503, and other internal errors.
if (exception != null) {
log.debug(" ... internal server error, will retry: {}", response.body(), exception);
} else {
log.debug(" ... internal server error, will retry: {}", response.body());
}
}
return (response.code() == 403 || response.code() >= 500);
}
return false;
}
private Duration sleepTime(int numRetries) {
// We disable sleeping in some cases for tests.
if (!this.networkRetriesSleep) {
return Duration.ZERO;
}
return waitTime(numRetries);
}
/**
* Calculate an exponential-backoff time to wait, with a jitter.
*
* @param attempt the retry attempt (count)
* @return a duration giving the time to wait (sleep)
*/
public static Duration waitTime(int attempt) {
// Apply exponential backoff with MinNetworkRetriesDelay on the number of numRetries
// so far as inputs.
Duration delay = Duration.ofNanos((long) (minNetworkRetriesDelay.toNanos() * Math.pow(2, attempt - 1)));
// Do not allow the number to exceed MaxNetworkRetriesDelay
if (delay.compareTo(maxNetworkRetriesDelay) > 0) {
delay = maxNetworkRetriesDelay;
}
// Apply some jitter by randomizing the value in the range of 75%-100%.
double jitter = ThreadLocalRandom.current().nextDouble(0.75, 1.0);
delay = Duration.ofNanos((long) (delay.toNanos() * jitter));
// But never sleep less than the base sleep seconds.
if (delay.compareTo(minNetworkRetriesDelay) < 0) {
delay = minNetworkRetriesDelay;
}
return delay;
}
}