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

com.adobe.pdfservices.operation.internal.http.HttpClientWrapper Maven / Gradle / Ivy

Go to download

Adobe PDF Services SDK allows you to access RESTful APIs to create, convert, and manipulate PDFs within your applications. Older versions can be found under groupId: com.adobe.documentservices, artifactId: pdftools-sdk

There is a newer version: 4.2.0
Show newest version
/*
 * Copyright 2019 Adobe
 * All Rights Reserved.
 *
 * NOTICE: Adobe permits you to use, modify, and distribute this file in
 * accordance with the terms of the Adobe license agreement accompanying
 * it. If you have received this file from a source other than Adobe,
 * then your use, modification, or distribution of it requires the prior
 * written permission of Adobe.
 */

package com.adobe.pdfservices.operation.internal.http;

import com.adobe.pdfservices.operation.config.proxy.ProxyServerConfig;
import com.adobe.pdfservices.operation.exception.SDKException;
import com.adobe.pdfservices.operation.exception.ServiceApiException;
import com.adobe.pdfservices.operation.exception.ServiceUsageException;
import com.adobe.pdfservices.operation.internal.GlobalConfig;
import com.adobe.pdfservices.operation.internal.auth.Authenticator;
import com.adobe.pdfservices.operation.internal.constants.CustomErrorMessages;
import com.adobe.pdfservices.operation.internal.constants.RequestKey;
import com.adobe.pdfservices.operation.internal.dto.response.ErrorResponse;
import com.adobe.pdfservices.operation.internal.dto.response.ImsErrorResponse;
import com.adobe.pdfservices.operation.internal.dto.response.error.PlatformAPISubmitError;
import com.adobe.pdfservices.operation.internal.exception.OperationException;
import com.adobe.pdfservices.operation.internal.exception.UnauthorizedClientException;
import com.adobe.pdfservices.operation.internal.http.config.HttpClientConfig;
import com.adobe.pdfservices.operation.internal.util.JsonUtil;
import net.jodah.failsafe.Failsafe;
import net.jodah.failsafe.RetryPolicy;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.Objects;
import java.util.Set;

/**
 * A wrapper class for the underlying http client.
 */
public class HttpClientWrapper implements HttpClient {

    private static final Logger LOGGER = LoggerFactory.getLogger(HttpClient.class);
    private static final int CREATED_SUCCESS_STATUS_CODE = 201;
    private static final int NO_CONTENT_SUCCESS_STATUS_CODE = 204;
    private static final int STATUS_OK = 200;
    private static final int SERVICE_USAGE_EXCEPTION_STATUS_CODE = 429;
    private static final int PROXY_AUTHENTICATION_EXCEPTION_STATUS_CODE = 407;
    private static final String UNAUTHORIZED_ERROR_CODE = "401013";

    // IMS error handling specific constants
    private static final String IMS_INVALID_TOKEN_ERROR_STRING = "invalid_token";
    private static final String IMS_CERTIFICATE_EXPIRY_ERROR_DESCRIPTION_STRING = "Could not match JWT signature to " +
            "any of the bindings";

    //Service usage and quota exhaustion specific error code constants
    private static final String SERVICE_USAGE_EXCEPTION_STATUS_CODE_429001_STRING = "429001";
    private static final String SERVICE_USAGE_EXCEPTION_STATUS_CODE_429002_STRING = "429002";

    private int connectTimeout;
    private int socketTimeout;
    private org.apache.http.client.HttpClient httpClient;
    private int maxRetries;

    private Set retriableErrorCodes;
    private Set successResponseCodes;


    public HttpClientWrapper(HttpClientConfig httpClientConfig) {
        this.connectTimeout = httpClientConfig.getConnectTimeout();
        this.socketTimeout = httpClientConfig.getSocketTimeout();
        this.maxRetries = httpClientConfig.getMaxRetries();


        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        cm.setMaxTotal(GlobalConfig.getMaxApacheConnections());
        cm.setDefaultMaxPerRoute(GlobalConfig.getMaxApacheConnectionsPerRoute());
        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(connectTimeout)
                .setSocketTimeout(socketTimeout).build();
        this.httpClient = HttpClients.custom().setConnectionManager(cm).setDefaultRequestConfig(requestConfig)
                .disableAutomaticRetries().build();
        retriableErrorCodes = GlobalConfig.getRetriableResponseCodes();
        successResponseCodes = GlobalConfig.getSuccessResponseCodes();

    }


    @Override
    public  HttpResponse send(HttpRequest httpRequest, Class responseType) {

        RetryPolicy> retryPolicy = getAuthenticationRetryPolicy(httpRequest);

        return Failsafe.with(retryPolicy).get(() -> executeRequest(httpRequest, responseType));

    }

    /**
     * This method can be moved to the appropriate authenticator to define custom policies
     *
     * @param 
     * @param httpRequest
     * @return
     */
    private  RetryPolicy> getAuthenticationRetryPolicy(HttpRequest httpRequest) {

        //Max retries 'x' means total attempts will be 'x+1', 1 for the original execution and x in case of failures
        //We don't need to refresh session token once we've reached the retry limit is reached, hence the if condition
        //Retry parameters can be added to the http request config, needn't be binded to the http client
        return new RetryPolicy>().withMaxRetries(this.maxRetries)
                .handle(UnauthorizedClientException.class).onFailedAttempt(listener -> {
                    LOGGER.debug(" Retry attempt count {} ", listener.getAttemptCount());
                    if (listener.getAttemptCount() <= this.maxRetries) {
                        httpRequest.forceAuthenticate();
                    }
                });
    }

    private  HttpResponse executeRequest(HttpRequest httpRequest, Class responseType) throws IOException,
            ServiceApiException {
        httpRequest.authenticate();
        HttpUriRequest apacheHttpRequest = ApacheHttpUtil.getApacheRequest(httpRequest);
        org.apache.http.HttpResponse httpResponse = executeApacheHttpRequest(httpRequest, apacheHttpRequest);
        return handleResponse(httpResponse, httpRequest, responseType);

    }

    private org.apache.http.HttpResponse executeApacheHttpRequest(HttpRequest httpRequest,
                                                                  HttpUriRequest apacheHttpRequest) {
        org.apache.http.HttpResponse httpResponse;
        try {
            HttpClientContext context = getHttpClientContextForProxy(httpRequest);
            httpResponse = this.httpClient.execute(apacheHttpRequest, context);
        } catch (ClientProtocolException e) {
            LOGGER.error("Error in http protocol, request could not be completed ", e);
            throw new SDKException("Http protocol exception encountered while executing request ", e);
        } catch (IOException e) {
            LOGGER.warn("Connection exception encountered while executing request {} ", httpRequest.getRequestKey()
                    .getValue());
            throw new SDKException("Request could not be completed. Possible cause attached!", e);
        }
        return httpResponse;
    }

    private static HttpClientContext getHttpClientContextForProxy(HttpRequest httpRequest) {
        HttpClientContext context = null;
        ProxyServerConfig proxyServerConfig = httpRequest.getConfig().getProxyServerConfig();
        if (proxyServerConfig != null && proxyServerConfig.getCredentials() != null) {
            String scheme = proxyServerConfig.getProxyScheme() == null ? null :
                            String.valueOf(proxyServerConfig.getProxyScheme());
            HttpHost proxy = new HttpHost(proxyServerConfig.getHost(), proxyServerConfig.getPort(), scheme);
            com.adobe.pdfservices.operation.config.proxy.UsernamePasswordCredentials credentials =
                    (com.adobe.pdfservices.operation.config.proxy.UsernamePasswordCredentials) proxyServerConfig.getCredentials();

            CredentialsProvider credsProvider = new BasicCredentialsProvider();
            credsProvider.setCredentials(new AuthScope(proxy),
                                         new UsernamePasswordCredentials(credentials.getUsername(),
                                                                         credentials.getPassword()));
            context = HttpClientContext.create();
            context.setCredentialsProvider(credsProvider);
        }
        return context;
    }

    private  HttpResponse handleResponse(org.apache.http.HttpResponse httpResponse, HttpRequest httpRequest,
                                               Class responseType) throws IOException, ServiceApiException {

        if (httpResponse == null) {
            return null;
        }
        int statusCode = httpResponse.getStatusLine().getStatusCode();
        LOGGER.debug("Response received {} ", httpResponse);
        RequestKey requestKey = httpRequest.getRequestKey();
        if (successResponseCodes.contains(statusCode)) {
            LOGGER.debug("Success response code {} received for request {}", statusCode, requestKey.getValue());
            if (statusCode == NO_CONTENT_SUCCESS_STATUS_CODE) {
                return new BaseHttpResponse<>(statusCode, ApacheHttpUtil.getHeaders(httpResponse.getAllHeaders()));
            }

            InputStream responseContent = httpResponse.getEntity().getContent();
            if (statusCode == CREATED_SUCCESS_STATUS_CODE) {
                responseContent.close();
                return new BaseHttpResponse<>(statusCode, ApacheHttpUtil.getHeaders(httpResponse.getAllHeaders()));
            } else if (statusCode == STATUS_OK) {
                if (httpRequest.getRequestKey().equals(RequestKey.DOWNLOAD)) {
                    return new BaseHttpResponse<>(statusCode, ApacheHttpUtil.getHeaders(httpResponse.getAllHeaders())
                            , responseContent);
                }
                if (httpRequest.getRequestKey().equals(RequestKey.UPLOAD)) {
                    return new BaseHttpResponse<>(statusCode, ApacheHttpUtil.getHeaders(httpResponse.getAllHeaders()));
                }
                T dcBaseResponseDto = JsonUtil.deserializeJsonStream(responseContent, responseType);
                return new BaseHttpResponse<>(statusCode, ApacheHttpUtil.getHeaders(httpResponse.getAllHeaders()),
                                              dcBaseResponseDto);
            } else {
                T dcBaseResponseDto = JsonUtil.deserializeJsonStream(responseContent, responseType);
                return new BaseHttpResponse<>(statusCode, ApacheHttpUtil.getHeaders(httpResponse.getAllHeaders()),
                                              dcBaseResponseDto);
            }
        } else {
            String errorResponseBody = EntityUtils.toString(httpResponse.getEntity());
            LOGGER.error("Failure response code {} encountered from backend", statusCode);
            // Check if we need a custom error message for this status code
            if (GlobalConfig.isCustomErrorMessageRequired((statusCode))) {
                throw new OperationException("Error response received for request", statusCode,
                                             getRequestTrackingIdFromResponse(httpResponse, requestKey.getValue()),
                                             GlobalConfig.getErrorCodeForHttpStatusCode(statusCode),
                                             GlobalConfig.getErrorMessageForHttpStatusCode(statusCode),
                                             errorResponseBody);
            }

            // special handling for proxy authentication failure
            if (statusCode == PROXY_AUTHENTICATION_EXCEPTION_STATUS_CODE) {
                throw new UnauthorizedClientException("Proxy server authentication failed for the request",
                                                      statusCode, httpRequest.getHeaders()
                        .get("x-request-id"), null, "Proxy server authentication failed for the request", null);
            }

            handleIMSCallFailure(httpResponse, statusCode, requestKey, errorResponseBody);

            handleUploadAssetFailure(httpResponse, statusCode, requestKey, errorResponseBody);
            // Special handling for service usage exception cases
            if (statusCode == SERVICE_USAGE_EXCEPTION_STATUS_CODE) {
                handleServiceUsageFailure(errorResponseBody, httpResponse, requestKey, statusCode);
            }
            ErrorResponse errorResponse = getErrorResponseFromBody(errorResponseBody, statusCode);
            if (retriableErrorCodes.contains(statusCode)) {
                // we don't want to retry when authentication calls fail with 401, that needs to be dealt with
                if (statusCode == 401 && errorResponse.getCode().equalsIgnoreCase(UNAUTHORIZED_ERROR_CODE)) {
                    LOGGER.debug("Request was not authenticated. Will retry with refreshed session token");
                    throw new UnauthorizedClientException(String.format("Error response received for request %s",
                                                                        requestKey.getValue()), statusCode,
                                                          getRequestTrackingIdFromResponse(httpResponse,
                                                                                           requestKey.getValue()),
                                                          errorResponse.getCode(), errorResponse.getMessage(),
                                                          errorResponseBody);
                }
            }
            throw new OperationException("Error response received for request", statusCode,
                                         getRequestTrackingIdFromResponse(httpResponse, requestKey.getValue()),
                                         errorResponse.getCode(), errorResponse.getMessage(), errorResponseBody,
                                         errorResponse.getReportErrorCode());

        }
    }

    private ErrorResponse getErrorResponseFromBody(String errorResponseBody, int statusCode) {
        PlatformAPISubmitError errorResponse = JsonUtil.deserializeJsonString(errorResponseBody,
                                                                              PlatformAPISubmitError.class);
        String errorCode = String.valueOf(statusCode);
        String errorMessage = errorResponse.getError().getMessage();
        String reportErrorCode = errorResponse.getError().getCode();

        return new ErrorResponse(errorCode, errorMessage, reportErrorCode);
    }

    private void handleIMSCallFailure(org.apache.http.HttpResponse httpResponse, int statusCode,
                                      RequestKey requestKey, String errorResponseBody) {
        // Any error from IMS is not to be retried currently, and should throw an exception
        if (!isNotAuthenticationRequest(requestKey)) {
            LOGGER.error("IMS call failed with status code {}", statusCode);
            ImsErrorResponse imsErrorResponse = JsonUtil.deserializeJsonString(errorResponseBody,
                                                                               ImsErrorResponse.class);

            // When error is returned with no error description
            if (imsErrorResponse.getErrorDescription() == null) {
                imsErrorResponse.setErrorDescription(imsErrorResponse.getError());
            }

            // Special handling for invalid token and certificate expiry cases
            if (imsErrorResponse.getError().equals(IMS_INVALID_TOKEN_ERROR_STRING)) {
                if (imsErrorResponse.getErrorDescription().equals(IMS_CERTIFICATE_EXPIRY_ERROR_DESCRIPTION_STRING)) {
                    imsErrorResponse.setErrorDescription(CustomErrorMessages.IMS_CERTIFICATE_EXPIRED_ERROR_MESSAGE);
                } else {
                    imsErrorResponse.setErrorDescription(CustomErrorMessages.IMS_INVALID_TOKEN_GENERIC_ERROR_MESSAGE);
                }
            }
            throw new OperationException(String.format("Error response received for request %s", requestKey),
                                         statusCode, getRequestTrackingIdFromResponse(httpResponse,
                                                                                      requestKey.getValue()),
                                         imsErrorResponse.getError(), imsErrorResponse.getErrorDescription(),
                                         errorResponseBody);
        }
    }

    private void handleUploadAssetFailure(org.apache.http.HttpResponse httpResponse, int statusCode,
                                          RequestKey requestKey, String errorResponseBody) throws IOException,
            ServiceApiException {

        if (requestKey == RequestKey.UPLOAD) {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder;
            try {
                builder = factory.newDocumentBuilder();
            } catch (ParserConfigurationException e) {
                throw new ServiceApiException("Error in uploading file");
            }

            // removing xml tag ""
            errorResponseBody = errorResponseBody.replaceAll("\\<\\?xml(.+?)\\?\\>", "").trim();

            InputSource is = new InputSource(new StringReader(errorResponseBody));
            Document doc;
            try {
                doc = builder.parse(is);
            } catch (SAXException e) {
                throw new ServiceApiException("Error in uploading file");
            }

            String errorCode = doc.getDocumentElement().getElementsByTagName("Code").item(0).getTextContent();
            String message = doc.getDocumentElement().getElementsByTagName("Message").item(0).getTextContent();
            String requestId = doc.getDocumentElement().getElementsByTagName("RequestId").item(0).getTextContent();

            throw new OperationException("Error response received for request", statusCode, requestId, null, message,
                                         errorResponseBody, errorCode);
        }
    }

    private void handleServiceUsageFailure(String errorResponseBody, org.apache.http.HttpResponse httpResponse,
                                           RequestKey requestKey, int statusCode) {

        PlatformAPISubmitError errorResponse = JsonUtil.deserializeJsonString(errorResponseBody,
                                                                              PlatformAPISubmitError.class);
        String errorCode = errorResponse.getError().getCode();
        String errorMessageResponse = null;

        if (!Objects.nonNull(errorResponse.getError().getDetails())) {
            throw new ServiceUsageException(errorResponse.getError()
                                                    .getMessage(), getRequestTrackingIdFromResponse(httpResponse,
                                                                                                    requestKey.getValue()), statusCode, errorCode);
        } else {
            if (errorResponse.getError().getDetails().getErrorCode()
                    .equals(SERVICE_USAGE_EXCEPTION_STATUS_CODE_429001_STRING)) {
                errorMessageResponse = CustomErrorMessages.SERVICE_USAGE_LIMIT_REACHED_ERROR_MESSAGE;
            } else if (errorResponse.getError().getDetails().getErrorCode()
                    .equals(SERVICE_USAGE_EXCEPTION_STATUS_CODE_429002_STRING)) {
                errorMessageResponse = CustomErrorMessages.INTEGRATION_SERVICE_USAGE_LIMIT_REACHED_ERROR_MESSAGE;
            }
        }
        throw new ServiceUsageException(errorMessageResponse, getRequestTrackingIdFromResponse(httpResponse,
                                                                                               requestKey.getValue())
                , statusCode, errorCode);
    }

    private boolean isNotAuthenticationRequest(RequestKey requestKey) {
        return !(requestKey == RequestKey.AUTHN);
    }

    private String getRequestTrackingIdFromResponse(org.apache.http.HttpResponse httpResponse, String requestKey) {

        if (Authenticator.SESSION_TOKEN_REQUEST_GROUP_KEY.equalsIgnoreCase(requestKey)) {
            return httpResponse.getFirstHeader(DefaultRequestHeaders.SESSION_TOKEN_REQUEST_ID_HEADER_KEY).getValue();
        } else {
            return httpResponse.getFirstHeader(DefaultRequestHeaders.DC_REQUEST_ID_HEADER_KEY).getValue();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy