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

com.adobe.cq.mcm.salesforce.SalesforceClient Maven / Gradle / Ivy

/*************************************************************************
 *
 * ADOBE CONFIDENTIAL
 * ___________________
 *
 *  Copyright 2012 Adobe Systems Incorporated
 *  All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Adobe Systems Incorporated and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to Adobe Systems Incorporated and its
 * suppliers and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Adobe Systems Incorporated.
 **************************************************************************/

package com.adobe.cq.mcm.salesforce;

import org.apache.commons.lang3.CharEncoding;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.HttpClientUtils;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.osgi.services.HttpClientBuilderFactory;
import org.apache.http.util.EntityUtils;
import org.apache.sling.commons.json.JSONArray;
import org.apache.sling.commons.json.JSONException;
import org.apache.sling.commons.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * The SalesforceClient uses the Salesforce REST API to authenticate and query Salesforce.
 * */
public class SalesforceClient {

    public static final String INVALID_CLIENT_ID = "invalid_client_id";
    public static final String INVALID_CLIENT = "invalid_client";
    public static final String CLIENT_ID = "client_id";
    public static final String CLIENT_SECRET = "client_secret";
    public static final String REFRESH_ACCESS_TOKEN_ENDPOINT = "/services/oauth2/token";
    public static final String APPLICATION_FORMURL_ENCODED = "application/x-www-form-urlencoded";
    public static final String GRANT_TYPE = "grant_type";
    public static final String REFRESH_TOKEN = "refresh_token";
    final protected int HTTP_CODE_REFRESH_TOKEN_SUCCESS = 200;

    public enum AvailableMethods {GET, POST, PATCH};

    protected String clientId = "";
    protected String clientSecret = "";
    protected String instanceURL = "";
    protected String path = "";
    protected String accessToken = "";
    protected String refreshToken = "";
    protected String contentType = "";
    protected String data = "";
    protected HttpClientBuilderFactory clientBuilderFactory = null;

    protected AvailableMethods method;
    protected HashMap parameters = new HashMap();

    private final Logger logger = LoggerFactory.getLogger(SalesforceClient.class);

    /**
     * Returns the Client Identifier.
     * */
    public String getClientId() {
        return clientId;
    }

    /**
     * Sets the Client Identifier. Also add into the data if not already present.
     * */
    public void setClientId(String clientId) {
        this.clientId = clientId;
        if(!data.contains(CLIENT_ID)){
            addData(CLIENT_ID, clientId);
        }
    }

    /**
     * Returns the Client Secret.
     * */
    public String getClientSecret() {
        return clientSecret;
    }

    /**
     * Sets the Client Secret. Also add into the data if not already present.
     * */
    public void setClientSecret(String clientSecret) {
        this.clientSecret = clientSecret;
        if(!data.contains(CLIENT_SECRET)){
            addData(CLIENT_SECRET, clientSecret);
        }
    }

    /**
     * Returns the URL encoded parameters used for REST Api calls as a String.
     * */
    public String getData() {
        return data;
    }

    /**
     * Adds a key-value pair to parameters list
     * */
    public void addData(String key, String value) {
        addData(key, value, true);
    }

    /**
     *  Adds a key-value pair to parameters list with an option not to encode the parameters
     * */
    public void addData(String key, String value, boolean encode) {
        if (!this.data.equals("")) {
            this.data += "&";
        }
        try {
            this.data += key + "=" + (encode ? URLEncoder.encode(value, CharEncoding.UTF_8) : value);
        } catch (UnsupportedEncodingException e) {
            logger.error("Exception in adding data to Salesforce Client: "+e.getMessage());
        }

    }

    /**
     * Set the URL encoded parameters used for REST Api calls as a Map of key-value pairs.
     * */
    public void setData(Map data) {
        this.data = "";

        Iterator it = data.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry pairs = (Map.Entry) it.next();
            try {
                this.data += pairs.getKey() + "=" + URLEncoder.encode(((String) pairs.getValue()), CharEncoding.UTF_8);
            } catch (UnsupportedEncodingException e) {
                logger.error("Exception in adding data to Salesforce Client: " + e.getMessage());
            }
            if (it.hasNext()) {
                this.data += "&";
            }
        }
    }

    /**
     * Set the URL encoded parameters used for REST Api calls as a String.
     * */
    public void setData(String data) {
        this.data = data;
    }

    /**
     * Returns the Instance URL for the Salesforce configuration
     * */
    public String getInstanceURL() {
        return instanceURL;
    }

    /**
     * Sets the Instance URL for the Salesforce configuration
     * */
    public void setInstanceURL(String instanceURL) {
        this.instanceURL = instanceURL;
    }

    /**
     *  Returns the current REST API endpoint
     * */
    public String getPath() {
        return path;
    }

    /**
     *  Sets the current REST API endpoint
     * */
    public void setPath(String path) {
        this.path = path;
    }

    /**
     *  Returns the Access Token
     * */
    public String getAccessToken() {
        return accessToken;
    }

    /**
     * Sets the Access Token
     * */
    public void setAccessToken(String accessToken) {
        this.accessToken = accessToken;
    }

    /**
     *  Returns the Refresh Token
     * */
    public String getRefreshToken() {
        return refreshToken;
    }

    /**
     *  Sets the Refresh Token
     * */
    public void setRefreshToken(String refreshToken) {
        this.refreshToken = refreshToken;
    }

    /**
     * Returns the Content-Type of the Request
     * */
    public String getContentType() {
        return contentType;
    }

    /**
     * Sets the Content-Type of the Request
     * */
    public void setContentType(String contentType) {
        this.contentType = contentType;
    }

    /**
     * Returns the HTTP Method used in the Current Request. Will be one of the {@link AvailableMethods}
     * */
    public AvailableMethods getMethod() {
        return method;
    }

    /**
     *  Sets the HTTP Method used in the Current Request. Will be one of the {@link AvailableMethods}
     * */
    public void setMethod(AvailableMethods method) {
        this.method = method;
    }

    /**
     * Sets the HTTP Method as a String to be used in Current Request
     * */
    public void setStringMethod(String method) {
        if (method.equals("GET")) {
            this.setMethod(AvailableMethods.GET);
        } else if (method.equals("POST")) {
            this.setMethod(AvailableMethods.POST);
        } else if (method.equals("PATCH")) {
            this.setMethod(AvailableMethods.PATCH);
        }
    }

    /**
     * Adds a parameter to the param list
     * */
    public void addParameter(String key, String value) {
        parameters.put(key, value);
    }

    /**
     * Returns the list of parameters as a map
     * */
    public HashMap getParameters() {
        return parameters;
    }

    /**
     * Sets the list of parameters.
     * */
    public void setParameters(HashMap parameters) {
        this.parameters = parameters;
    }

    /**
     * Sets the HTTP Client Builder factory
     * @param clientBuilderFactory The HTTP Client Builder factory
     */
    public void setClientBuilderFactory(HttpClientBuilderFactory clientBuilderFactory) {
        this.clientBuilderFactory = clientBuilderFactory;
    }

    /**
     *  Executes the current request. Delegates to appropriate method based on the HTTP method used
     * */
    public SalesforceResponse executeRequest() throws SalesforceException {
        if (method == AvailableMethods.GET) {
            return executeGetRequest();
        } else if (method == AvailableMethods.POST || method == AvailableMethods.PATCH) {
            return executeDataRequest();
        }
        throw new SalesforceException("Unavailable HTTP Method");
    }

    /**
     * Executes the Current GET Request and refreshes the access token if expired
     * */
    public SalesforceResponse executeGetRequest() throws SalesforceException {
        SalesforceResponse response = new SalesforceResponse();

        try {
            response = doExecuteGetRequest();
            if (isAccessTokenExpired(response)) {
                SalesforceResponse refreshResponse = refreshAccessToken();
                response = doExecuteGetRequest();
                response.setAccessTokenUpdated(refreshResponse.getAccessTokenUpdated());
            }
        } catch (IOException e) {
            logger.error("Transport Error while executing a GET Request ", e);
            throw new SalesforceException("Transport Error while executing a GET Request ", e);
        }
        return response;
    }

    /**
     * Executes the Current GET Request
     * */
    public SalesforceResponse doExecuteGetRequest() throws IOException, SalesforceException {
        HttpClient client = getHttpClient();
        SalesforceResponse response = new SalesforceResponse();

        try {
            String uri = instanceURL + path + "?";
            Iterator it = parameters.entrySet().iterator();
            while (it.hasNext()) {
                Map.Entry pairs = (Map.Entry) it.next();
                uri += (String) pairs.getKey() + "=" + URLEncoder.encode((String) pairs.getValue(),  CharEncoding.UTF_8);
            }
            HttpGet method = new HttpGet(uri);
            if (!contentType.isEmpty()) {
                method.setHeader("Content-Type", contentType);
            }
            if (!accessToken.isEmpty()) {
                method.removeHeaders("Authorization");
                method.setHeader("Authorization", "OAuth " + accessToken);
            }

            // Execute the method.
            internalExecuteRequest(client, method, response);
        }
        catch (Exception e){
            logger.error("Error while Executing GET Request to Salesforce.com: ", e);
            throw new SalesforceException("Error while Executing GET Request to Salesforce.com: ", e);
        } finally {
            HttpClientUtils.closeQuietly(client);
        }
        return response;
    }

    /**
     *  Executes the Current POST/PATCH Request and refreshes the access token if expired
     * */
    public SalesforceResponse executeDataRequest() throws SalesforceException {
        SalesforceResponse response = new SalesforceResponse();

        try {
            response = doExecuteDataRequest();
            if (isAccessTokenExpired(response)) {
                SalesforceResponse refreshResponse = refreshAccessToken();
                response = doExecuteDataRequest();
                response.setAccessTokenUpdated(refreshResponse.getAccessTokenUpdated());
            }
        } catch (IOException e) {
            logger.error("Transport Error while executing a GET Request ", e);
            throw new SalesforceException("Transport Error while executing a GET Request ", e);
        }
        return response;
    }

    /**
     * Executes the Current POST/PATCH Request
     * */
    public SalesforceResponse doExecuteDataRequest() throws IOException, SalesforceException {
        HttpEntityEnclosingRequestBase method = null;
        if (this.method == AvailableMethods.POST) {
            method = new HttpPost(instanceURL + path);
        } else if (this.method == AvailableMethods.PATCH) {
            method = new HttpPatch(instanceURL + path);
        }

        SalesforceResponse response = new SalesforceResponse();
        HttpClient client = getHttpClient();
        try {
            if (!contentType.isEmpty()) {
                method.setHeader("Content-Type", contentType);
            }

            if (!accessToken.isEmpty()) {
                method.removeHeaders("Authorization");
                method.setHeader("Authorization", "OAuth " + accessToken);
            }
            method.setEntity(new StringEntity(data, ContentType.create(contentType,  CharEncoding.UTF_8)));

            // Read the response body.
            // Execute the method.
            internalExecuteRequest(client, method, response);
        }
        catch (Exception e){
            logger.error("Error while Executing POST/PUT Request to Salesforce.com: ", e);
            throw new SalesforceException("Error while Executing POST/PUT Request to Salesforce.com: ", e);
        }
        finally {
            // Release the connection.
            method.releaseConnection();
        }
        return response;
    }

    private void internalExecuteRequest(HttpClient client, HttpUriRequest method, SalesforceResponse response) throws IOException {
        HttpResponse httpResponse = client.execute(method);
        try {
            response.setCode(httpResponse.getStatusLine().getStatusCode());
            HttpEntity httpEntity = httpResponse.getEntity();
            if (httpEntity != null) {
                String body = EntityUtils.toString(httpResponse.getEntity());
                response.setBody(body);
            }
        } finally {
            HttpClientUtils.closeQuietly(httpResponse);
        }
    }

    /**
     * Refreshes the Access Token using the refresh_token
     * Assumes that the client_id and client_secret are set before invoking this function
     * */
    public SalesforceResponse refreshAccessToken() throws SalesforceException {

        SalesforceClient salesforceRefreshClient = new SalesforceClient();
        SalesforceResponse response;

        salesforceRefreshClient.setClientBuilderFactory(clientBuilderFactory);

        salesforceRefreshClient.setRefreshToken(refreshToken);
        salesforceRefreshClient.setPath(REFRESH_ACCESS_TOKEN_ENDPOINT);
        salesforceRefreshClient.setInstanceURL(instanceURL);
        salesforceRefreshClient.setContentType(APPLICATION_FORMURL_ENCODED);

        salesforceRefreshClient.addData(GRANT_TYPE, REFRESH_TOKEN);
        salesforceRefreshClient.addData(CLIENT_ID, clientId);
        salesforceRefreshClient.addData(CLIENT_SECRET, clientSecret);
        salesforceRefreshClient.addData(REFRESH_TOKEN, refreshToken);

        salesforceRefreshClient.setMethod(SalesforceClient.AvailableMethods.POST);

        response = salesforceRefreshClient.executeRequest();
        try {

            if (response.getCode() != HTTP_CODE_REFRESH_TOKEN_SUCCESS) {

                String errorMessage = "";
                JSONObject errorJSON = response.getBodyAsJSON();
                if(errorJSON.get("error").equals(INVALID_CLIENT_ID)){
                    // Invalid Client/Customer Key
                   errorMessage = "Can't refresh access token due to Invalid Customer Key ";
                }
                else if(errorJSON.get("error").equals(INVALID_CLIENT)){
                    // Invalid Client/Customer Secret
                    errorMessage = "Can't refresh access token due to Invalid Customer Secret ";
                }
                else{
                    // Generic Handling
                    errorMessage = "Can't refresh access token due to some unknown error. " +
                            "Please contact Administrator ";
                }

                logger.error("Can't refresh access token.");
                throw new SalesforceException(errorMessage);
            }
            else{

                JSONObject responseJson = new JSONObject(response.getBody());
                this.setInstanceURL(responseJson.getString("instance_url"));
                this.setAccessToken(responseJson.getString("access_token"));
                response.setAccessTokenUpdated(true);
            }

        } catch (JSONException e) {
            logger.error("JSON Exception while refreshing the Access Token.");
            throw new SalesforceException("JSON Exception while refreshing the Access Token.");
        }
        return response;
    }

    /**
     * Checks if the currently set access token has been expired or not
     * */
    public boolean isAccessTokenExpired(SalesforceResponse salesforceResponse) throws SalesforceException {
        JSONArray salesforceResponseJson;

       if (salesforceResponse.getBody()!=null && !"".equals(salesforceResponse.getBody())) {
            try {
                // [{"message":"Session expired or invalid","errorCode":"INVALID_SESSION_ID"}]

                String responseBody = salesforceResponse.getBody();
                if(responseBody.charAt(0)!='[' && responseBody.charAt(responseBody.length()-1)!=']')
                    responseBody = '['+responseBody+']';

                salesforceResponseJson = new JSONArray(responseBody);
                return salesforceResponseJson.getJSONObject(0).getString("errorCode").equals("INVALID_SESSION_ID");

            } catch (JSONException e) {
                // Ignore the parsing error as errorCode is not found in success
                logger.info("ErrorCode not found meaning success hence ignoring exception ");
            }
        }
        return false;
    }

    /**
     * Returns a HttpClient object
     * @return HttpClient object
     */
    private HttpClient getHttpClient() {
        HttpClient client = null;

        if (clientBuilderFactory != null) {
            // Get HttpClient from the library to respect proxy settings
            HttpClientBuilder builder = clientBuilderFactory.newBuilder();
            if (builder != null) {
                client = builder.build();
            }
        }

        if (client == null) {
            // If not possible, get default client
            client = HttpClients.createDefault();
        }

        return client;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy