TestandtargetHttpClientImpl.java 19.3 KB
/*
 * Decompiled with CFR 0_118.
 * 
 * Could not load the following classes:
 *  com.google.gson.JsonElement
 *  com.google.gson.JsonObject
 *  com.google.gson.JsonParser
 *  javax.jcr.RepositoryException
 *  org.apache.commons.httpclient.Header
 *  org.apache.commons.httpclient.HttpClient
 *  org.apache.commons.httpclient.HttpException
 *  org.apache.commons.httpclient.HttpMethod
 *  org.apache.commons.httpclient.HttpStatus
 *  org.apache.commons.httpclient.HttpURL
 *  org.apache.commons.httpclient.HttpsURL
 *  org.apache.commons.httpclient.URIException
 *  org.apache.commons.httpclient.methods.ByteArrayRequestEntity
 *  org.apache.commons.httpclient.methods.DeleteMethod
 *  org.apache.commons.httpclient.methods.EntityEnclosingMethod
 *  org.apache.commons.httpclient.methods.GetMethod
 *  org.apache.commons.httpclient.methods.PostMethod
 *  org.apache.commons.httpclient.methods.PutMethod
 *  org.apache.commons.httpclient.methods.RequestEntity
 *  org.apache.commons.httpclient.params.HttpClientParams
 *  org.apache.commons.lang.StringUtils
 *  org.apache.felix.scr.annotations.Activate
 *  org.apache.felix.scr.annotations.Component
 *  org.apache.felix.scr.annotations.Deactivate
 *  org.apache.felix.scr.annotations.Properties
 *  org.apache.felix.scr.annotations.Property
 *  org.apache.felix.scr.annotations.Service
 *  org.apache.sling.commons.json.JSONException
 *  org.apache.sling.commons.json.JSONObject
 *  org.apache.sling.commons.osgi.PropertiesUtil
 *  org.osgi.service.component.ComponentContext
 *  org.slf4j.Logger
 *  org.slf4j.LoggerFactory
 */
package com.day.cq.analytics.testandtarget.impl;

import com.day.cq.analytics.testandtarget.TestandtargetCallOptions;
import com.day.cq.analytics.testandtarget.TestandtargetException;
import com.day.cq.analytics.testandtarget.TestandtargetHttpClient;
import com.day.cq.analytics.testandtarget.TestandtargetHttpParameters;
import com.day.cq.analytics.testandtarget.impl.TestandtargetHttpClientResponseException;
import com.day.cq.analytics.testandtarget.impl.TestandtargetHttpParametersImpl;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.jcr.RepositoryException;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.HttpURL;
import org.apache.commons.httpclient.HttpsURL;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.commons.lang.StringUtils;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.commons.json.JSONException;
import org.apache.sling.commons.json.JSONObject;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(metatype=1, label="Adobe AEM Target HTTP Client", description="HTTP Client for Adobe Target")
@Service
@Properties(value={@Property(name="service.description", value={"Adobe Target HTTP Client"})})
public class TestandtargetHttpClientImpl
implements TestandtargetHttpClient {
    private static final Logger log = LoggerFactory.getLogger(TestandtargetHttpClientImpl.class);
    private static final int DEFAULT_TIMEOUT = 60000;
    private static final String NO_ERROR_MESSAGE_SET = "No error message set";
    private static final String DEFAULT_RECOMMENDATIONS_ENDPOINT_REPLACE = "/rest";
    private static final String DEFAULT_RECOMMENDATIONS_ENDPOINT_REPLACEWITH = "/rest/recs";
    @Property(label="Adobe Target API URL", description="URL of the Adobe Target API", name="cq.analytics.testandtarget.api.url", value={"https://admin.testandtarget.omniture.com/api"})
    private static final String API_URL = "cq.analytics.testandtarget.api.url";
    @Property(label="Global timeout", description="Timeout value for all operations involving the Adobe Target API. Defaults to 1 minute if not set. A value of 0 is interpreted as 'no timeout'.", intValue={60000})
    private static final String TIMEOUT = "cq.analytics.testandtarget.timeout";
    @Property(name="cq.analytics.testandtarget.recommendations.url.replace", label="Adobe Target Recommendations URL replace regex token", description="Controls the token in Adobe Target endpoint URL that needs to be replaced to point to the Target Recommendations API URL", value={"/rest"})
    private static final String RECOMMENDATIONS_ENDPOINT_REPLACE = "cq.analytics.testandtarget.recommendations.url.replace";
    @Property(name="cq.analytics.testandtarget.recommendations.url.replacewith", label="Adobe Target Recommendations URL replace with token", description="Replacement for the regex above so that the resulting URL will point to Target Recommendations API", value={"/rest/recs"})
    private static final String RECOMMENDATIONS_ENDPOINT_REPLACEWITH = "cq.analytics.testandtarget.recommendations.url.replacewith";
    private static String DEFAULT_API_URL = "https://admin.testandtarget.omniture.com/api";
    private static String DEFAULT_REST_API_HOST = "https://admin.testandtarget.omniture.com/";
    private String apiUrl;
    private int timeout;
    private String restApiHost;
    private int restApiPort = -1;
    private String restApiScheme = "";
    private String recsEndpointReplace;
    private String recsEndpointReplacewith;
    private final Map<String, String> clientCodeToEndpointLocation = new HashMap<String, String>();

    @Override
    public String execute(String[] queryKeys, String[] queryValues) throws TestandtargetException {
        HashMap<String, String> queryParams = new HashMap<String, String>();
        for (int queryParamIdx = 0; queryParamIdx < queryKeys.length; ++queryParamIdx) {
            queryParams.put(queryKeys[queryParamIdx], queryValues[queryParamIdx]);
        }
        TestandtargetHttpParametersImpl testandtargetParameters = new TestandtargetHttpParametersImpl(queryParams);
        return this.executeMethod(TestandtargetHttpClient.TestandtargetMethodType.GET, this.processUrlLocation(this.apiUrl, TestandtargetHttpClient.TestandtargetSolution.TARGET), testandtargetParameters);
    }

    @Override
    public String execute(TestandtargetHttpClient.TestandtargetMethodType callMethod, TestandtargetHttpClient.TestandtargetSolution solution, TestandtargetHttpParameters testandtargetParameters) throws TestandtargetException {
        return this.executeMethod(callMethod, this.processUrlLocation(this.apiUrl, solution), testandtargetParameters);
    }

    private String executeMethod(TestandtargetHttpClient.TestandtargetMethodType method, String location, TestandtargetHttpParameters testandtargetParameters) throws TestandtargetException {
        return this.executeMethod(method, location, testandtargetParameters, null);
    }

    private String executeMethod(TestandtargetHttpClient.TestandtargetMethodType method, String location, TestandtargetHttpParameters testandtargetParameters, String apiVersion) throws TestandtargetException {
        HttpMethod requestMethod = null;
        HttpClient http = new HttpClient();
        HttpClientParams params = http.getParams();
        params.setSoTimeout(this.timeout);
        try {
            requestMethod = this.buildHttpMethod(method, location, testandtargetParameters, apiVersion);
            int statusCode = http.executeMethod(requestMethod);
            String response = this.consumeResponse(requestMethod);
            if (statusCode > 399 && statusCode < 600) {
                String errMsg;
                String responseType = this.extractResponseType(requestMethod);
                log.warn("Adobe Target HTTP call returned status code " + statusCode);
                if ("application/json".equalsIgnoreCase(responseType)) {
                    errMsg = this.parseJsonError(response);
                } else {
                    errMsg = "" + statusCode + " " + HttpStatus.getStatusText((int)statusCode);
                    log.warn("Adobe Target HTTP call returned error response: " + response);
                }
                throw new TestandtargetHttpClientResponseException(statusCode, errMsg);
            }
            String responseType = response;
            return responseType;
        }
        catch (URIException e) {
            throw new TestandtargetException(e.getMessage(), (Throwable)e);
        }
        catch (HttpException e) {
            throw new TestandtargetException(e.getMessage(), (Throwable)e);
        }
        catch (UnsupportedEncodingException e) {
            throw new TestandtargetException(e.getMessage(), e);
        }
        catch (IOException e) {
            throw new TestandtargetException(e.getMessage(), e);
        }
        finally {
            if (requestMethod != null) {
                requestMethod.releaseConnection();
            }
        }
    }

    private HttpMethod buildHttpMethod(TestandtargetHttpClient.TestandtargetMethodType method, String location, TestandtargetHttpParameters testandtargetParameters, String apiVersion) throws URIException, NullPointerException, UnsupportedEncodingException {
        HttpsURL url;
        Object requestMethod = null;
        if (StringUtils.isNotEmpty((String)apiVersion)) {
            if (StringUtils.isNumeric((String)apiVersion)) {
                apiVersion = "v" + apiVersion;
            }
            location = StringUtils.replace((String)location, (String)"v1", (String)apiVersion);
        }
        HttpsURL httpsURL = url = location.startsWith("https") ? new HttpsURL(location) : new HttpURL(location);
        if (testandtargetParameters != null && testandtargetParameters.getQueryParameters() != null) {
            String[] queryKeys = new String[testandtargetParameters.getQueryParameters().size()];
            String[] queryValues = new String[testandtargetParameters.getQueryParameters().size()];
            int i = 0;
            log.debug("Calling URL {}", (Object)url.getEscapedURI());
            for (Map.Entry<String, String> paramEntry : testandtargetParameters.getQueryParameters().entrySet()) {
                queryKeys[i] = paramEntry.getKey();
                queryValues[i] = paramEntry.getValue();
                if (!"password".equals(queryKeys[i])) {
                    log.debug("Adding parameter {}={}", (Object)queryKeys[i], (Object)queryValues[i]);
                } else {
                    log.debug("Adding parameter {}=*****", (Object)queryKeys[i]);
                }
                ++i;
            }
            url.setQuery(queryKeys, queryValues);
        }
        switch (method) {
            case GET: {
                requestMethod = new GetMethod(url.toString());
                break;
            }
            case POST: {
                requestMethod = new PostMethod(url.toString());
                break;
            }
            case PUT: {
                requestMethod = new PutMethod(url.toString());
                break;
            }
            case DELETE: {
                requestMethod = new DeleteMethod(url.toString());
                break;
            }
            case PATCH: {
                requestMethod = new PostMethod(url.toString()){

                    public String getName() {
                        return "PATCH";
                    }
                };
                break;
            }
            default: {
                requestMethod = new GetMethod(url.toString());
            }
        }
        if (testandtargetParameters != null && requestMethod instanceof EntityEnclosingMethod && testandtargetParameters.getEntityContent() != null) {
            log.debug("Setting entity {}", (Object)new String(testandtargetParameters.getEntityContent()));
            ((EntityEnclosingMethod)requestMethod).setRequestEntity((RequestEntity)new ByteArrayRequestEntity(testandtargetParameters.getEntityContent(), testandtargetParameters.getEntityContentType()));
        }
        return requestMethod;
    }

    private String processUrlLocation(String location, TestandtargetHttpClient.TestandtargetSolution solution) {
        if (solution == TestandtargetHttpClient.TestandtargetSolution.RECOMMENDATIONS) {
            location = location.replaceAll(this.recsEndpointReplace, this.recsEndpointReplacewith);
        }
        return location;
    }

    private String extractResponseType(HttpMethod response) {
        Header responseTypeHeader = response.getResponseHeader("Content-type");
        if (responseTypeHeader == null) {
            log.warn("No 'Content-type' reponse header found.");
            log.debug("Status is {}", (Object)response.getStatusText());
            return "text/plain";
        }
        String responseType = responseTypeHeader.getValue();
        return StringUtils.contains((String)responseType, (char)';') ? responseType.split(";")[0] : responseType;
    }

    private String parseJsonError(String response) {
        JsonParser parser = new JsonParser();
        JsonObject object = parser.parse(response).getAsJsonObject();
        return object.has("message") ? object.get("message").getAsString() : "No error message set";
    }

    private String consumeResponse(HttpMethod method) throws IOException {
        String line;
        StringBuffer sb = new StringBuffer();
        BufferedReader responseReader = new BufferedReader(new InputStreamReader(method.getResponseBodyAsStream(), StandardCharsets.UTF_8));
        while ((line = responseReader.readLine()) != null) {
            sb.append(line);
        }
        return sb.toString();
    }

    @Override
    public String executeRestGetCall(String clientCode, String location, Map<String, String> parameters) throws TestandtargetException {
        TestandtargetHttpParametersImpl testandtargetParameters = new TestandtargetHttpParametersImpl(parameters);
        TestandtargetCallOptions callOptions = new TestandtargetCallOptions().withClientCode(clientCode).withLocation(location).withParameters(testandtargetParameters);
        return this.executeRestGetCall(callOptions);
    }

    @Override
    public String executeRestGetCall(TestandtargetCallOptions options) throws TestandtargetException {
        return this.executeRestCall(options);
    }

    @Override
    public String executeRestCall(TestandtargetHttpClient.TestandtargetMethodType callMethod, String clientCode, String location, TestandtargetHttpClient.TestandtargetSolution solution, TestandtargetHttpParameters testandtargetParameters) throws TestandtargetException {
        String endpoint = this.getOrDiscoverEndpoint(clientCode);
        return this.executeMethod(callMethod, this.processUrlLocation(endpoint + location, solution), testandtargetParameters);
    }

    @Override
    public String executeRestCall(TestandtargetCallOptions options) throws TestandtargetException {
        TestandtargetHttpClient.TestandtargetMethodType callMethod = options.getCallMethod();
        String location = options.getLocation();
        TestandtargetHttpParameters httpParams = options.getHttpParameters();
        TestandtargetHttpClient.TestandtargetSolution solution = options.getSolution();
        String apiVersion = options.getApiVersion();
        String clientCode = options.getClientCode() == null ? httpParams.getQueryParameters().get("client") : options.getClientCode();
        String endpoint = this.getOrDiscoverEndpoint(clientCode);
        return this.executeMethod(callMethod, this.processUrlLocation(endpoint + location, solution), httpParams, apiVersion);
    }

    /*
     * WARNING - Removed try catching itself - possible behaviour change.
     */
    private String getOrDiscoverEndpoint(String clientCode) throws TestandtargetException {
        String endpoint;
        Map<String, String> map = this.clientCodeToEndpointLocation;
        synchronized (map) {
            endpoint = this.clientCodeToEndpointLocation.get(clientCode);
            if (endpoint == null) {
                String serverLocation = (this.restApiScheme.length() == 0 ? "https" : this.restApiScheme) + "://" + this.restApiHost + (this.restApiPort > 0 ? new StringBuilder().append(":").append(this.restApiPort).toString() : "");
                String endpointJson = this.executeMethod(TestandtargetHttpClient.TestandtargetMethodType.GET, serverLocation + "/rest/v1/endpoint/" + clientCode + ".json", null);
                log.debug("Retrieving endpoint for clientCode '{}'", (Object)clientCode);
                try {
                    endpoint = new JSONObject(endpointJson).getString("api");
                }
                catch (JSONException e) {
                    throw new TestandtargetException("Failed retrieving endpoint for clientCode '" + clientCode + "' : " + e.getMessage(), (Throwable)e);
                }
                this.clientCodeToEndpointLocation.put(clientCode, endpoint);
                log.debug("Cached endpoint '{}' for clientCode '{}'", (Object)endpoint, (Object)clientCode);
            }
        }
        return endpoint;
    }

    @Activate
    protected void activate(ComponentContext componentContext) throws RepositoryException, TestandtargetException {
        Dictionary properties = componentContext.getProperties();
        this.apiUrl = PropertiesUtil.toString(properties.get("cq.analytics.testandtarget.api.url"), (String)DEFAULT_API_URL);
        this.timeout = PropertiesUtil.toInteger(properties.get("cq.analytics.testandtarget.timeout"), (int)60000);
        this.recsEndpointReplace = PropertiesUtil.toString(properties.get("cq.analytics.testandtarget.recommendations.url.replace"), (String)"/rest");
        this.recsEndpointReplacewith = PropertiesUtil.toString(properties.get("cq.analytics.testandtarget.recommendations.url.replacewith"), (String)"/rest/recs");
        try {
            URI apiURI = URI.create(this.apiUrl);
            this.restApiHost = apiURI.getHost();
            this.restApiPort = apiURI.getPort();
            this.restApiScheme = apiURI.getScheme();
        }
        catch (IllegalArgumentException e) {
            log.warn("Unable to extract restApiHost from apiUrl {}, using default value", (Object)this.apiUrl);
            this.restApiHost = DEFAULT_REST_API_HOST;
        }
        log.debug("Started with apiUrl {}, restApiHost {} and timeout {} ms", new Object[]{this.apiUrl, this.restApiHost, this.timeout});
    }

    @Deactivate
    protected void deactivate(ComponentContext componentContext) {
        log.debug("Stopped", (Object)TestandtargetHttpClientImpl.class.getSimpleName());
    }

}