All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.trimble.id.HttpClient Maven / Gradle / Ivy

Go to download

Trimble Identity OAuth Client library holds the client classes that are used for communicating with Trimble Identity Service

The newest version!
package com.trimble.id;

import static com.trimble.id.AuthenticationConstants.FILE;
import static com.trimble.id.AuthenticationConstants.TEXT;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;

/**
 * HttpClient class to make HTTP requests.
 */
public class HttpClient {
    private URI baseUri;
    private String accessToken;
    private OkHttpClient client;
    private HttpClientConfiguration configuration;
    private final Lock lock = new ReentrantLock();
    private static final ObjectMapper objectMapper = new ObjectMapper();

    /**
     * Default constructor
     */
    public HttpClient() {
    }

    /**
     * Constructor with configuration
     * 
     * @param configuration - HttpClientConfiguration
     */
    public HttpClient(HttpClientConfiguration configuration) {
        this.configuration = configuration;
    }

    /**
     * Constructor with baseUri
     * 
     * @param baseUri - baseUri
     */
    public HttpClient(URI baseUri) {
        this.baseUri = baseUri;
    }

    /**
     * Constructor with baseUri and configuration
     * 
     * @param baseUri       - baseUri
     * @param configuration - HttpClientConfiguration
     */
    public HttpClient(URI baseUri, HttpClientConfiguration configuration) {
        this.baseUri = baseUri;
        this.configuration = configuration;
    }

    /**
     * Get method
     * 
     * @param           - responseType
     * @param url          - url
     * @param headers      - headers
     * @param responseType - responseType
     * @return - Future that resolves to Response
     * @throws TCPServiceException - If an error occurs due to API or
     *                             network-related
     *                             issues.
     * @throws SDKClientException  - If a client-side error occurs within the SDK.
     */

    public  CompletableFuture> get(String url, List headers, Class responseType)
            throws TCPServiceException, SDKClientException {
        try {
            String finalUrl = buildUrl(url);
            Request.Builder builder = new Request.Builder().url(finalUrl).get();
            addHeaders(headers, builder);
            Request request = builder.build();
            return executeRequest(request, responseType);
        } catch (Exception e) {
            return handleException(e);
        }
    }

    /**
     * Post method
     * 
     * @param           - responseType
     * @param url          - url
     * @param payload      - It has the payload data and content type
     * @param headers      - headers
     * @param responseType - responseType
     * @return - Future that resolves to Response
     * @throws TCPServiceException - If an error occurs due to API or
     *                             network-related
     *                             issues.
     * @throws SDKClientException  - If a client-side error occurs within the SDK.
     */
    public  CompletableFuture> post(String url, RequestPayload payload, List headers,
            Class responseType) throws TCPServiceException, SDKClientException {
        try {
            String finalUrl = buildUrl(url);
            RequestBody requestBody = createRequestBody(payload);
            Request.Builder builder = new Request.Builder().url(finalUrl).post(requestBody);
            addHeaders(headers, builder);
            Request request = builder.build();
            return executeRequest(request, responseType);
        } catch (Exception e) {
            return handleException(e);
        }
    }

    /**
     * Patch method
     * 
     * @param           - responseType
     * @param url          - url
     * @param payload      - It has the payload data and content type
     * @param headers      - headers
     * @param responseType - responseType
     * @return
     * @throws TCPServiceException - If an error occurs due to API or
     *                             network-related
     *                             issues.
     * @throws SDKClientException  - If a client-side error occurs within the SDK.
     */
    public  CompletableFuture> patch(String url, RequestPayload payload, List headers,
            Class responseType) throws TCPServiceException, SDKClientException {
        try {
            String finalUrl = buildUrl(url);
            RequestBody requestBody = createRequestBody(payload);
            Request.Builder builder = new Request.Builder().url(finalUrl).patch(requestBody);
            addHeaders(headers, builder);
            Request request = builder.build();
            return executeRequest(request, responseType);
        } catch (Exception e) {
            return handleException(e);
        }
    }

    /**
     * Put method
     * 
     * @param           - responseType
     * @param url          - url
     * @param payload      - It has the payload data and content type
     * @param headers      - headers
     * @param responseType - responseType
     * @return - Future that resolves to Response
     * @throws TCPServiceException - If an error occurs due to API or
     *                             network-related
     *                             issues.
     * @throws SDKClientException  - If a client-side error occurs within the SDK.
     */
    public  CompletableFuture> put(String url, RequestPayload payload, List headers,
            Class responseType)
            throws TCPServiceException, SDKClientException {
        try {
            String finalUrl = buildUrl(url);
            RequestBody requestBody = createRequestBody(payload);
            Request.Builder builder = new Request.Builder();
            addHeaders(headers, builder);
            Request request = builder.url(finalUrl).put(requestBody).build();
            return executeRequest(request, responseType);
        } catch (Exception e) {
            return handleException(e);
        }
    }

    /**
     * Delete method
     * 
     * @param           - responseType
     * @param url          - url
     * @param headers      - headers
     * @param responseType - responseType
     * @return - Future that resolves to Response
     * @throws TCPServiceException - If an error occurs due to API or
     *                             network-related
     *                             issues.
     * @throws SDKClientException  - If a client-side error occurs within the SDK.
     */
    public  CompletableFuture> delete(String url, List headers, Class responseType)
            throws TCPServiceException, SDKClientException {
        try {
            String finalUrl = this.buildUrl(url);
            Request.Builder builder = new Request.Builder();
            addHeaders(headers, builder);
            Request request = builder.url(finalUrl).delete().build();
            return executeRequest(request, responseType);
        } catch (Exception e) {
            return handleException(e);
        }
    }

    /**
     * Shutdown the HttpClient
     */
    public void dispose() {
        if (client != null) {
            client.dispatcher().executorService().shutdown();
            client.connectionPool().evictAll();
        }
    }

    private  CompletableFuture> handleException(Exception e) {
        CompletableFuture> future = new CompletableFuture<>();
        future.completeExceptionally(e);
        return future;
    }

     CompletableFuture> executeRequest(Request request, Class responseType)
            throws TCPServiceException, SDKClientException {
        CompletableFuture> future = new CompletableFuture<>();

        if (this.configuration == null) {
            this.configuration = new HttpClientConfiguration();
        }
        this.client = new OkHttpClient.Builder()
                .connectTimeout(Duration.ofMinutes(5))
                .callTimeout(configuration.getHttpTimeout())
                .addInterceptor(new RetryInterceptor(configuration))
                .build();

        long startTime = System.currentTimeMillis();
        
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                future.completeExceptionally(e);
            }

            @Override
            public void onResponse(Call call, okhttp3.Response response) throws IOException {
                try {
                    int statusCode = response.code();
                    Map> headers = response.headers().toMultimap();
                    String requestId = getRequestId(headers);
                    String apiEndpoint = request.url().toString();
                    if (!response.isSuccessful()) {
                        String responseBody = response.body() != null ? response.body().string() : null;
                        String errorMessage = responseBody != null ? responseBody
                                : response.message();
                        future.completeExceptionally(new TCPServiceException(errorMessage, requestId, statusCode));
                    } else {
                        Response result = handleSuccessfulResponse(response, responseType, statusCode, headers,
                                requestId);
                        long endTime = System.currentTimeMillis();
                        long responseTime = endTime - startTime;    
                        result.setApiEndpoint(apiEndpoint);
                        result.setResponseTime(responseTime);
                        future.complete(result);
                    }
                } catch (Exception e) {
                    future.completeExceptionally(new SDKClientException(e));
                } finally {
                    if (response != null) {
                        response.close();
                    }
                }
            }
        });
        return future;
    }

    private String getRequestId(Map> headers) {
        return headers.get("tcp-request-id") != null ? headers.get("tcp-request-id").get(0) : null;
    }

    private  Response handleSuccessfulResponse(okhttp3.Response response, Class responseType, int statusCode,
            Map> headers, String requestId) throws IOException {

        if (responseType == byte[].class) {
            byte[] responseBodyBytes;
            responseBodyBytes = response.body() != null ? response.body().bytes() : null;

            return new Response<>(statusCode, headers, responseType.cast(responseBodyBytes), requestId, null);
        } else {
            String responseBody = response.body() != null ? response.body().string() : null;
            return handleResponse(responseBody, statusCode, headers, responseType, requestId);
        }
    }

    private  Response handleResponse(String responseBody, int statusCode, Map> headers,
            Class responseType, String requestId) {
        if (responseBody == null || responseBody.isEmpty()) {
            return new Response<>(statusCode, headers, null, requestId, null);
        }
        try {
            objectMapper.readTree(responseBody);
            T responseObject = objectMapper.readValue(responseBody, responseType);
            return new Response<>(statusCode, headers, responseObject, requestId, null);
        } catch (JsonProcessingException e) {
            // Handle invalid JSON, treat responseBody as plain string
            T castedResponseBody = null;
            castedResponseBody = responseType.cast(responseBody);
            return new Response<>(statusCode, headers, castedResponseBody, requestId, null);
        }
    }

    private void addHeaders(List headers, Request.Builder builder) {
        if (headers != null && !headers.isEmpty()) {
            for (NameValuePair header : headers) {
                builder.addHeader(header.getName(), header.getValue());
            }
        }
        if (accessToken != null) {
            builder.header("Authorization", accessToken);
        }
    }

    private RequestBody createRequestBody(RequestPayload payload) {
        if (payload == null) {
            return RequestBody.create(new byte[0], MediaType.parse(AuthenticationConstants.APPLICATION_OCTET_STREAM));
        }
        String contentType = payload.getContentType();

        if (AuthenticationConstants.APPLICATION_JSON.equals(contentType)) {
            String jsonPayload;
            try {
                jsonPayload = objectMapper.writeValueAsString(payload.getPayload());
                return RequestBody.create(jsonPayload, MediaType.parse(AuthenticationConstants.APPLICATION_JSON));

            } catch (JsonProcessingException e) {
                throw new SDKClientException(e);
            }
        } else if (AuthenticationConstants.APPLICATION_OCTET_STREAM.equals(contentType)) {
            return createOctetStreamRequestBody(payload);
        } else if (AuthenticationConstants.MULTIPART_FORM_DATA.equals(contentType)) {
            return createMultipartRequestBody(payload);
        }

        // Default case: convert to string and use application/json
        return RequestBody.create(payload.toString(), MediaType.parse(AuthenticationConstants.APPLICATION_JSON));
    }

    private RequestBody createOctetStreamRequestBody(RequestPayload payload) {
        Object payloadData = payload.getPayload();
        MediaType mediaType = MediaType.parse(AuthenticationConstants.APPLICATION_OCTET_STREAM);

        if (payloadData instanceof byte[]) {
            return RequestBody.create((byte[]) payloadData, mediaType);
        }
        if (payloadData instanceof File) {
            return RequestBody.create((File) payloadData, mediaType);
        } else {
            throw new SDKClientException("Pass byte[] or File as payload for application/octet-stream");
        }
    }

    private RequestBody createMultipartRequestBody(RequestPayload payload) {
        MultipartBody.Builder multipartBodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);

        Set> formEntries = payload.getFormPayload().entrySet();

        for (Entry formEntry : formEntries) {

            FormData form = formEntry.getValue();

            if (FILE.equals(formEntry.getKey())) {

                multipartBodyBuilder.addFormDataPart(form.getFieldName(), form.getFileName(),
                        RequestBody.create(form.getFile(), MediaType.parse(form.getMimeType())));

            } else if (TEXT.equals(formEntry.getKey())) {
                multipartBodyBuilder.addFormDataPart(form.getFieldName(), form.getFieldValue());
            }
        }
        return multipartBodyBuilder.build();
    }

    private static class RetryInterceptor implements Interceptor {
        private final HttpClientConfiguration configuration;

        public RetryInterceptor(HttpClientConfiguration configuration) {
            this.configuration = configuration;
        }

        @Override
        public okhttp3.Response intercept(Chain chain) throws SDKClientException, TCPServiceException {
            Request request = chain.request();
            IOException exception = null;
            int tryCount = 0;
            int code = 0;
            okhttp3.Response response = null;
            // Retry for server error and network error
            while (tryCount < configuration.getRetries()) {

                try {
                    response = chain.proceed(request);
                    code = response.code();
                    if (!isServerError(code)) {
                        return response;
                    } else {
                        tryCount++;
                        handleRetryDelay();
                    }
                } catch (IOException e) {
                    exception = e;
                    tryCount++;
                    handleRetryDelay();
                } finally {
                    if (response != null && isServerError(code)) {
                        response.close();
                    }
                }
            }
            if (isServerError(code)) {
                throw new TCPServiceException("Internal Server error", null, code);
            } else {
                throw new TCPServiceException(exception, null, code);
            }
        }

        private boolean isServerError(int code) {
            return code >= 500 && code < 600;
        }

        private void handleRetryDelay() throws SDKClientException {
            try {
                Thread.sleep(configuration.getRetryInterval().toMillis());
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
                throw new SDKClientException(ie);
            }
        }
    }

    void setAuthorizationToken(String accessToken) {
        lock.lock();
        try {
            this.accessToken = "Bearer ".concat(accessToken);
        } finally {
            lock.unlock();
        }
    }

    private String buildUrl(String url) {
        if (baseUri != null) {
            return baseUri.resolve(url).toString();
        }
        return url;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy