com.adobe.pdfservices.operation.internal.http.HttpClientWrapper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pdfservices-sdk Show documentation
Show all versions of pdfservices-sdk Show documentation
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
/*
* 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