Salesforce.com interface to directly access Salesforce.com

Dependencies:   HTTPClient-SSL MbedJSONValue

Dependents:   df-2014-salesforce-hrm-k64f

Fork of SalesforceInterface by Doug Anson

SalesforceInterface.cpp

Committer:
ansond
Date:
2015-12-07
Revision:
26:709a9dff666c
Parent:
22:3363752cd523

File content as of revision 26:709a9dff666c:

/* Copyright C2014 ARM, MIT License
 *
 * Author: Doug Anson (doug.anson@arm.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
 * and associated documentation files the "Software", to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
  
 // Tuneables
 #define  SF_OAUTH_TOKEN_URL    "https://login.salesforce.com/services/oauth2/token"
 #define  SF_OAUTH_REQUEST_BODY "grant_type=password&client_id=%s&client_secret=%s&username=%s&password=%s"
 #define  SF_HTTP_AUTH_HEADER   "Authorization: Bearer %s"
 
 // search tokens for URLS in salesforce token
 #define SF_URLS_START_TOKEN    "\"urls\":"
 #define SF_URLS_STOP_TOKEN     "},"
 
 // salesforce QUERY specifier within URL
 #define SF_QUERY_URL_SPECIFIER "q="
 
 // salesforce URL API version token
 #define SF_URL_API_VER_TOKEN   "{version}"
 
 // HTTP response code to give for errored out conditions
 #define SF_GEN_ERR_HTTP_CODE   500
 
 // include class definition
 #include "SalesforceInterface.h"
 
 // Supported DataTypes for HTTPClient
 #include "HTTPMap.h"
 #include "HTTPJson.h"
 
 // Logging
 #define LOG(...) { if (this->logger() != NULL) { this->logger()->log(__VA_ARGS__); } }
 #define LOG_CONSOLE(...) { if (this->logger() != NULL) { this->logger()->logConsole(__VA_ARGS__); } }
 
 // default constructor
 SalesforceInterface::SalesforceInterface(HTTPClient *http,RawSerial *pc) {
     Logger *logger = NULL;
     if (pc != NULL) {
         logger = new Logger(pc,NULL);
         this->init(http,logger,true);
     }
     else {
         this->init(http,NULL,false);
     }
 }
 
 // alternative constructor
 SalesforceInterface::SalesforceInterface(HTTPClient *http,Logger *logger) {
     this->init(http,logger,false);
 }
 
 // initialize
 void SalesforceInterface::init(HTTPClient *http,Logger *logger,bool logger_internal) {
     this->m_logger = logger;
     this->m_logger_internal = logger_internal;
     this->m_http = http;
     this->m_username = NULL;
     this->m_password = NULL;
     this->m_client_id = NULL;
     this->m_client_secret = NULL;
     this->m_have_creds = false;
     this->m_http_status = HTTP_OK;
     this->m_http_response_code = -1;
     RESET_BUFFER(this->m_http_redirection_url);
     RESET_SML_BUFFER(this->m_error_buffer);
     memset(this->m_salesforce_api,0,SALESFORCE_API_VERSION_LENGTH);
     strcpy(this->m_salesforce_api,SALESFORCE_API_VERSION);
     this->resetSalesforceToken();
 }
 
 // destructor
 SalesforceInterface::~SalesforceInterface() {
     if (this->m_logger_internal == true && this->m_logger != NULL) delete this->m_logger;
 }
 
 // set credentials
 void SalesforceInterface::setCredentials(char *username,char *password,char *client_id,char *client_secret) {
     this->m_username = NULL;
     this->m_password = NULL;
     this->m_client_id = NULL;
     this->m_client_secret = NULL;
     this->m_have_creds = false;
     
     if (username != NULL) {
        this->m_username = username;
        if (password != NULL) {
            this->m_password = password;
            if (client_id != NULL) {
                this->m_client_id = client_id;
                if (client_secret != NULL) {
                    this->m_client_secret = client_secret;
                    this->m_have_creds = true;
                }
            }
        }
     }
 }
 
 // convenience accessors
 Logger *SalesforceInterface::logger() { return this->m_logger; }
 HTTPClient *SalesforceInterface::http() { return this->m_http; }
 OauthToken *SalesforceInterface::oauth() { return &this->m_oauth_token; }
 bool SalesforceInterface::haveCreds() { return this->m_have_creds; }
 HTTPResult SalesforceInterface::httpStatus() { return this->m_http_status; }
 int SalesforceInterface::httpResponseCode() { return this->m_http_response_code; }
 void SalesforceInterface::setSalesforceAPIVersion(int version) { sprintf(this->m_salesforce_api,"%d",version); }
 void SalesforceInterface::setSalesforceAPIVersion(char *version) { if (version != NULL && strlen(version) > 0 && strlen(version) < SALESFORCE_API_VERSION_LENGTH) strcpy(this->m_salesforce_api,version); }
 char *SalesforceInterface::getSalesforceAPIVersion() { return this->m_salesforce_api; }
 
 // reset our oauth token
 void SalesforceInterface::resetOauthToken() {
     DEBUG("resetting OAUTH token...");
     this->m_oauth_token.valid              = false;
     this->m_oauth_token.id                 = "";
     this->m_oauth_token.issued_at          = "";
     this->m_oauth_token.token_type         = "";
     this->m_oauth_token.instance_url       = "";
     this->m_oauth_token.signature          = "";
     this->m_oauth_token.access_token       = "";
     if (this->http() != NULL) this->http()->oauthToken(NULL);
 }
 
 // fill our oauth token
 void SalesforceInterface::fillOauthToken(char *token) {
     if (token != NULL && strlen(token) > 0) {
         // parse JSON
         MbedJSONValue parsed_token;
         parse(parsed_token,token);
         
         // fill our OAUTH token
         this->m_oauth_token.id             = parsed_token["id"].get<std::string>();
         this->m_oauth_token.issued_at      = parsed_token["issued_at"].get<std::string>();
         this->m_oauth_token.token_type     = parsed_token["token_type"].get<std::string>();
         this->m_oauth_token.instance_url   = parsed_token["instance_url"].get<std::string>();
         this->m_oauth_token.signature      = parsed_token["signature"].get<std::string>();
         this->m_oauth_token.access_token   = parsed_token["access_token"].get<std::string>();
         
         // we have an OAUTH token now
         this->m_oauth_token.valid = true;
         DEBUG("valid OAUTH token acquired.");
         return;
     }
     LOG_CONSOLE("error: invalid or null OAUTH token fill attempt.");
 }
 
 // is our OAUTH token valid?
 bool SalesforceInterface::validOauthToken(bool fetch) {         
    // make sure we have a valid OAUTH Token
    this->checkAndGetOauthToken(fetch);
    return this->m_oauth_token.valid; 
 }
 
 // reset our salesforce token and OAUTH tokens
 void SalesforceInterface::resetSalesforceToken() {
     this->resetOauthToken();
     RESET_BUFFER(this->m_salesforce_id);
 }

 // do we have a valid salesforce.com token?
 bool SalesforceInterface::haveSalesforceToken(bool fetch) {
     if (this->m_salesforce_id != NULL && strlen(this->m_salesforce_id) > 0) return true;
     if (fetch) {
        LOG("Fetching Salesforce Token...");
        this->getSalesforceToken();
        return this->haveSalesforceToken(false);
     }
     return false;
 }
 
 // check and get our OAUTH token
 void SalesforceInterface::checkAndGetOauthToken(bool fetch) {
     DEBUG("checking for valid OAUTH token...");
     
     // reset the token structure for sanity...
     if (this->m_oauth_token.valid == false) this->resetOauthToken();   
     
     // should go fetch our token if we dont have one?
     // just pass through if we already have a token
     if (this->m_oauth_token.valid == true) {
        DEBUG("valid OAUTH token found.");
     }
     else if (this->m_oauth_token.valid == false && fetch == true) {
         // get our OAUTH token
         DEBUG("No OAUTH token found. Acquiring OAUTH token...");
         ALLOC_BUFFER(output_buffer);
         char *token = this->getOauthToken(output_buffer,MAX_BUFFER_LENGTH);
         if (token != NULL && strlen(token) > 0) {
            // fill
            DEBUG("Saving OAUTH token...");
            this->fillOauthToken(token);
            return;
         }
         else {
             // unable to get the token (reset for sanity)
             LOG_CONSOLE("error in acquiring OAUTH token http_code=%d status=%d",this->httpResponseCode(),this->httpStatus());
             this->resetOauthToken();
         }
     }
     
     // else report that we dont have a token
     else {
         LOG_CONSOLE("No OAUTH token found (fetch=false).");
     }
 }  
 
 //
 // get OAUTH2 Token - taken from here: 
 // https://developer.salesforce.com/page/Digging_Deeper_into_OAuth_2.0_on_Force.com#Obtaining_a_Token_in_an_Autonomous_Client_.28Username_and_Password_Flow.29
 //
 char *SalesforceInterface::getOauthToken(char *output_buffer,int output_buffer_length) {
     if (this->haveCreds()) { 
         // construct the OAUTH2 Token request body
         HTTPMap input;
         
         //
         // FORMAT: Taken from URL above method signature:
         //
         // grant_type=password&client_id=<your_client_id>&client_secret=<your_client_secret>&username=<your_username>&password=<your_password>
         //
         // ContentType: application/x-www-form-urlencoded
         //
         input.put("grant_type","password");
         input.put("client_id",this->m_client_id);
         input.put("client_secret",this->m_client_secret);
         input.put("username",this->m_username);
         input.put("password",this->m_password);
                  
         // prepare the output buffer
         HTTPText output(output_buffer,output_buffer_length);
         
         // HTTP POST call to gett he token 
         DEBUG("Getting OAUTH Token...");
         this->m_http_status = this->http()->post(SF_OAUTH_TOKEN_URL,input,&output);

         // check the result and return the token
         if (this->httpStatus() == HTTP_OK || this->httpResponseCode() == 200) return output_buffer;
         LOG_CONSOLE("acquire oauth FAILED. URL: %s http_code=%d status=%d",SF_OAUTH_TOKEN_URL,this->httpResponseCode(),this->httpStatus());
     }
     else {
         // no credentials
         LOG_CONSOLE("no/incomplete salesforce.com credentials provided. Unable to acquire OAUTH2 token...");
     }
     return NULL;
 }
 
 // Salesforce.com: Get our token
 char *SalesforceInterface::getSalesforceToken(bool fetch) {
    // proceed only if we have a valid OAUTH Token
    if (this->validOauthToken(fetch) == true) {
        // pull the token from salesforce
        RESET_BUFFER(this->m_salesforce_id);
        char *id = this->invoke(this->oauth()->id.c_str(),this->m_salesforce_id,MAX_BUFFER_LENGTH);
        
        // log any error status and return what we have...
        if (this->httpStatus() != HTTP_OK) LOG_CONSOLE("Unable to get Salesforce Token: status=%d httpCode=%d",this->httpStatus(),this->httpResponseCode());
        return id;
    }
    else {
        // unable to get token - no OAUTH token
        LOG_CONSOLE("Unable to get Salesforce Token: no valid OAUTH token.");
    }
    return NULL;
 }
 
 // QUERY: Salesforce.com
 char *SalesforceInterface::query(char *query_str,char *output_buffer,int output_buffer_length) {
     // first we have to ensure that we have valid salesforce token
     if (this->haveSalesforceToken()) {        
        // get the query url
        ALLOC_BUFFER(url);
        char *sf_url = this->getSalesforceURL("query",url,MAX_BUFFER_LENGTH);
        if (sf_url != NULL && strlen(sf_url) > 0) {
            // make sure that the query string is ready to ship...
            ALLOC_SML_BUFFER(tmp_query);
                                      
            // replace all spaces in query with "+"
            strcpy(tmp_query,query_str);
            this->replace(tmp_query,' ','+');   // will modify tmp_query directly...
                         
            // customize the URL with our (formatted) query string
            string str_url(sf_url);
            str_url[str_url.length()-1] = '?';                           // remove the slash and add a ?
            str_url = str_url + SF_QUERY_URL_SPECIFIER + tmp_query;      // add the query specifier
            
            // DEBUG - show the query URL
            DEBUG("query URL: %s",str_url.c_str());
            
            // invoke with GET
            return this->invoke((const char *)str_url.c_str(),output_buffer,output_buffer_length);
        }
        else {
            // unable to find the query URL...
            LOG_CONSOLE("query: error - unable to find query URL in salesforce token...");
        }
     }
     else {
         // dont have a valid salesforce token
         LOG_CONSOLE("query: error - no valid salesforce token was found...");
     }
     return NULL;
 }
 
 // CREATE: a record in Salesforce.com
 MbedJSONValue SalesforceInterface::createRecord(char *object_name,MbedJSONValue &record) { 
    ALLOC_BUFFER(output_buffer);
    char *reply = this->createRecord(object_name,(char *)record.serialize().c_str(),output_buffer,MAX_BUFFER_LENGTH);
    MbedJSONValue response;
    if (reply != NULL && strlen(reply) > 0) parse(response,reply);
    return response;
 }

 // READ: a specific record in Salesforce.com
 MbedJSONValue SalesforceInterface::readRecord(char *object_name,char *record_id,char *record_value) {
    ALLOC_BUFFER(output_buffer);
    char *reply = this->readRecord(object_name,record_id,record_value,output_buffer,MAX_BUFFER_LENGTH);
    MbedJSONValue response;
    if (reply != NULL && strlen(reply) > 0) parse(response,reply);
    return response; 
 }

 // UPDATE: a specific record in Salesforce.com
 bool SalesforceInterface::updateRecord(char *object_name,char *record_id,MbedJSONValue &record) {
    RESET_SML_BUFFER(this->m_error_buffer);
    return this->updateRecord(object_name,record_id,(char *)record.serialize().c_str(),this->m_error_buffer,MAX_SMALL_BUFFER_LENGTH);
 }
 
 // UPSERT: update/insert an External ID record in Salesforce.com
 bool SalesforceInterface::upsertRecord(char *object_name,char *external_id_field_name,char *external_id_field_value,MbedJSONValue &record) {
     RESET_SML_BUFFER(this->m_error_buffer);
     return this->upsertRecord(object_name,external_id_field_name,external_id_field_value,(char *)record.serialize().c_str(),this->m_error_buffer,MAX_SMALL_BUFFER_LENGTH);
 }
 
 // DELETE: a specific record in Salesforce.com
 bool SalesforceInterface::deleteRecord(char *object_name,char *record_id) {
      RESET_SML_BUFFER(this->m_error_buffer);
      return this->deleteRecord(object_name,record_id,this->m_error_buffer,MAX_SMALL_BUFFER_LENGTH);
 }
 
 // ERROR: get last error result
 MbedJSONValue SalesforceInterface::getLastError() {
     MbedJSONValue error;
     if (strlen(this->m_error_buffer) > 0) parse(error,this->m_error_buffer);
     return error;
 }
 
 // CREATE: a record in Salesforce.com
 char *SalesforceInterface::createRecord(char *object_name,char *json_data,char *output_buffer,int output_buffer_length) {
     // parameter check
     if (object_name != NULL && strlen(object_name) > 0 && json_data != NULL && strlen(json_data) > 0 && output_buffer != NULL && output_buffer_length > 0) {
         // first we have to ensure that we have valid salesforce token
         if (this->haveSalesforceToken()) {        
            // get the sobjects url
            ALLOC_BUFFER(url);
            char *sf_url = this->getSalesforceURL("sobjects",url,MAX_BUFFER_LENGTH);
            if (sf_url != NULL && strlen(sf_url) > 0) {   
                // convert to string
                string str_url(sf_url);
                         
                // add object name that we want to create a record in
                str_url += object_name;
                
                // DEBUG
                DEBUG("createRecord: URL: %s  DATA: %s",str_url.c_str(),json_data);
                
                // now invoke with POST with JSON data type
                return this->invoke(str_url.c_str(),json_data,strlen(json_data)+1,output_buffer,output_buffer_length);
            }
         }
         else {
             // dont have a valid salesforce token
             LOG_CONSOLE("createRecord: error - no valid salesforce token was found...");
         }
     }
     else {
         // invalid or NULL parameters
         LOG_CONSOLE("createRecord: error - invalid or NULL parameters...");
     }
     this->m_http_response_code = SF_GEN_ERR_HTTP_CODE;
     return NULL;
 }

 // READ: a specific record in Salesforce.com
 char *SalesforceInterface::readRecord(char *object_name,char *record_id,char *record_value,char *output_buffer,int output_buffer_length) {
     // parameter check
     if (object_name != NULL && strlen(object_name) > 0 && record_id != NULL && strlen(record_id) > 0 && output_buffer != NULL && output_buffer_length > 0) {
         // first we have to ensure that we have valid salesforce token
         if (this->haveSalesforceToken()) {        
            // get the sobjects url
            ALLOC_BUFFER(url);
            char *sf_url = this->getSalesforceURL("sobjects",url,MAX_BUFFER_LENGTH);
            if (sf_url != NULL && strlen(sf_url) > 0) {   
                // convert to string
                string str_url(sf_url);
                         
                // add object name that we want to create a record in
                str_url += object_name;
                
                // add the record token
                str_url += "/";
                str_url += record_id;
                
                // add the record value (if present)
                if (record_value != NULL && strlen(record_value) > 0) {
                    str_url += "/";
                    str_url += record_value;
                }
                
                // DEBUG
                DEBUG("readRecord: URL: %s",str_url.c_str());
                
                // now invoke with GET with JSON data type
                return this->invoke(str_url.c_str(),output_buffer,output_buffer_length);
            }
         }
         else {
             // dont have a valid salesforce token
             LOG_CONSOLE("readRecord: error - no valid salesforce token was found...");
         }
     }
     else {
         // invalid or NULL parameters
         LOG_CONSOLE("readRecord: error - invalid or NULL parameters...");
     }
     this->m_http_response_code = SF_GEN_ERR_HTTP_CODE;
     return NULL;
 }

 // UPDATE: a specific record in Salesforce.com
 bool SalesforceInterface::updateRecord(char *object_name,char *record_id,char *json_data,char *output_buffer,int output_buffer_length) { 
     // reset the error buffer
     RESET_SML_BUFFER(this->m_error_buffer);
      
     // parameter check
     if (object_name != NULL && strlen(object_name) > 0 && json_data != NULL && strlen(json_data) > 0) {
         // first we have to ensure that we have valid salesforce token
         if (this->haveSalesforceToken()) {        
            // get the sobjects url
            ALLOC_BUFFER(url);
            char *sf_url = this->getSalesforceURL("sobjects",url,MAX_BUFFER_LENGTH);
            if (sf_url != NULL && strlen(sf_url) > 0) {   
                // convert to string
                string str_url(sf_url);
                         
                // add object name that we want to create a record in
                str_url += object_name;
                
                // add the record token
                str_url += "/";
                str_url += record_id;
                
                // HTTPClient does not support PATCH, so we have to use POST with a special added parameter
                str_url += "?_HttpMethod=PATCH";
                
                // DEBUG
                DEBUG("updateRecord: URL: %s DATA: %s",str_url.c_str(),json_data);
                
                // now invoke with POST with JSON data type
                char *reply = this->invoke(str_url.c_str(),json_data,strlen(json_data)+1,output_buffer,output_buffer_length);
                
                // DEBUG
                DEBUG("updateRecord: http status=%d",this->httpResponseCode());
                
                // return our status
                if (this->httpResponseCodeInRange(200)) return true;
                
                // we are in error - so copy the result if we have one and return false
                if (reply != NULL && strlen(reply) > 0) strncpy(this->m_error_buffer,reply,this->min(strlen(reply),MAX_SMALL_BUFFER_LENGTH));
                return false;
            }
         }
         else {
             // dont have a valid salesforce token
             LOG_CONSOLE("updateRecord: error - no valid salesforce token was found...");
         }
     }
     else {
         // invalid or NULL parameters
         LOG_CONSOLE("updateRecord: error - invalid or NULL parameters...");
     }
     this->m_http_response_code = SF_GEN_ERR_HTTP_CODE;
     return false;  
 }
 
 // UPSERT: update/insert a specific External record in Salesforce.com
 bool SalesforceInterface::upsertRecord(char *object_name,char *external_id_field_name,char *external_id_field_value,char *json_data,char *output_buffer,int output_buffer_length) {  
     // reset the error buffer
     RESET_SML_BUFFER(this->m_error_buffer);

     // parameter check
     if (object_name != NULL && strlen(object_name) > 0 && json_data != NULL && strlen(json_data) > 0) {
         // first we have to ensure that we have valid salesforce token
         if (this->haveSalesforceToken()) {        
            // get the sobjects url
            ALLOC_BUFFER(url);
            char *sf_url = this->getSalesforceURL("sobjects",url,MAX_BUFFER_LENGTH);
            if (sf_url != NULL && strlen(sf_url) > 0) {   
                // convert to string
                string str_url(sf_url);
                         
                // add object name that we want to create a record in
                str_url += object_name;
                
                // add the external field name token
                str_url += "/";
                str_url += external_id_field_name;
                
                // add the external field value token (if not NULL)
                if (external_id_field_value != NULL && strlen(external_id_field_value) > 0) {
                    str_url += "/";
                    str_url += external_id_field_value;
                }
                
                // HTTPClient does not support PATCH, so we have to use POST with a special added parameter
                str_url += "?_HttpMethod=PATCH";
                
                // DEBUG
                DEBUG("upsertRecord: URL: %s DATA: %s",str_url.c_str(),json_data);
                
                // now invoke with POST with JSON data type
                char *reply = this->invoke(str_url.c_str(),json_data,strlen(json_data)+1,output_buffer,output_buffer_length);
                
                // DEBUG
                DEBUG("upsertRecord: http status=%d",this->httpResponseCode());
                
                // return our status
                if (this->httpResponseCodeInRange(200)) return true;
                
                // we are in error - so copy the result if we have one and return false
                if (reply != NULL && strlen(reply) > 0) strncpy(this->m_error_buffer,reply,this->min(strlen(reply),MAX_SMALL_BUFFER_LENGTH));
                return false;
            }
         }
         else {
             // dont have a valid salesforce token
             LOG_CONSOLE("upsertRecord: error - no valid salesforce token was found...");
         }
     }
     else {
         // invalid or NULL parameters
         LOG_CONSOLE("upsertRecord: error - invalid or NULL parameters...");
     }
     this->m_http_response_code = SF_GEN_ERR_HTTP_CODE;
     return false;  
 }
  
 // DELETE: a specific record in Salesforce.com
 bool SalesforceInterface::deleteRecord(char *object_name,char *record_id,char *output_buffer,int output_buffer_length) {
     // reset the error buffer
     RESET_SML_BUFFER(this->m_error_buffer);

     // parameter check
     if (object_name != NULL && strlen(object_name) > 0 && record_id != NULL && strlen(record_id) > 0) {
         // first we have to ensure that we have valid salesforce token
         if (this->haveSalesforceToken()) {        
            // get the sobjects url
            ALLOC_BUFFER(url);
            char *sf_url = this->getSalesforceURL("sobjects",url,MAX_BUFFER_LENGTH);
            if (sf_url != NULL && strlen(sf_url) > 0) {   
                // convert to string
                string str_url(sf_url);
                         
                // add object name that we want to create a record in
                str_url += object_name;
                
                // add the record token
                str_url += "/";
                str_url += record_id;
                
                // DEBUG
                LOG_CONSOLE("deleteRecord: URL: %s",str_url.c_str());
                
                // now invoke with DELETE 
                ALLOC_SML_BUFFER(output_buffer);
                char *reply = this->invoke(str_url.c_str(),output_buffer,output_buffer_length,DELETE);
                
                // DEBUG
                DEBUG("deleteRecord: http status=%d",this->httpResponseCode());
                
                // return our status
                if (this->httpResponseCodeInRange(200)) return true;
                
                // we are in error - so copy the result if we have one and return false
                if (reply != NULL && strlen(reply) > 0) strncpy(this->m_error_buffer,reply,this->min(strlen(reply),MAX_SMALL_BUFFER_LENGTH));
                return false;
            }
         }
         else {
             // dont have a valid salesforce token
             LOG_CONSOLE("deleteRecord: error - no valid salesforce token was found...");
         }
     }
     else {
         // invalid or NULL parameters
         LOG_CONSOLE("deleteRecord: error - invalid or NULL parameters...");
     }
     this->m_http_response_code = SF_GEN_ERR_HTTP_CODE;
     return false;
 }
 
 // Salesforce.com Invoke: defaults to GET
 char *SalesforceInterface::invoke(const char *url,char *output_buffer,int output_buffer_length) { 
    return this->invoke(url,output_buffer,output_buffer_length,GET); 
 }
 
 // Salesforce.com Invoke: GET or DELETE with simple output
 char *SalesforceInterface::invoke(const char *url,char *output_buffer,int output_buffer_length,HttpVerb verb) { 
    char *response = NULL;
    switch(verb) {
        case GET:
        case DELETE:
            // GET and DELETE only require an output buffer...
            response = this->invoke(url,NUM_TYPES,NULL,0,output_buffer,output_buffer_length,verb); 
            break;
        default:
            // wrong verb for this call interface...
            LOG_CONSOLE("invoke: invalid call: must be either GET or DELETE verb if only output buffer is provided");
            break;
    }
    return response;
 }
 
 // Salesforce.com Invoke: defaults to POST with JSON input data type                                                  
 char *SalesforceInterface::invoke(const char *url,const char *input_data,const int input_data_len,char *output_buffer,int output_buffer_length) { 
    return this->invoke(url,JSON,input_data,input_data_len,output_buffer,output_buffer_length); 
 }
 
 // Salesforce.com Invoke: defaults to POST with variable input data type                                                  
 char *SalesforceInterface::invoke(const char *url,const InputDataTypes input_type,const char *input_data,const int input_data_len,char *output_buffer,int output_buffer_length) { 
    return this->invoke(url,input_type,input_data,input_data_len,output_buffer,output_buffer_length,POST); 
 }
 
 // Salesforce.com Invoke: full fidelity method
 char *SalesforceInterface::invoke(const char *url,const InputDataTypes input_type,const char *input_data,const int input_data_len,char *output_buffer,int output_buffer_length,const HttpVerb verb) {     
     // initialize our invocation status and response code
     this->m_http_response_code = -1;
     this->m_http_status = HTTP_ERROR;
                 
     // param check: make sure that we at least have an output buffer and URL
     if (url != NULL && strlen(url) > 0 && output_buffer != NULL && output_buffer_length > 0) {         
        // proceed only if we have a valid OAUTH Token
        if (this->validOauthToken() == true) {                  
            // use OAUTH headers
            this->http()->oauthToken(this->oauth()->access_token.c_str());
            
            // reset the redirection url buffer in case we get a redirect...
            RESET_BUFFER(this->m_http_redirection_url);
            this->http()->setLocationBuf((char *)this->m_http_redirection_url,MAX_BUFFER_LENGTH);

            // create our output/response buffer
            HTTPText output(output_buffer,output_buffer_length);
            
            // now make the HTTP(S) request
            switch(verb) {
                case GET:
                    DEBUG("invoke (GET) URL: %s...",url);
                    this->m_http_status = this->http()->get(url,&output);
                    this->m_http_response_code = this->http()->getHTTPResponseCode();
                    break;
                case DELETE:
                    DEBUG("invoke (DEL) URL: %s...",url);
                    this->m_http_status = this->http()->del(url,&output);
                    this->m_http_response_code = this->http()->getHTTPResponseCode();
                    break;
                case POST:
                    if (input_data != NULL && input_data_len > 0) {
                        if (input_type == JSON) {
                            DEBUG("invoke (POST-JSON) URL: %s...",url);
                            HTTPJson input_json((char *)input_data,(int)input_data_len);
                            this->m_http_status = this->http()->post(url,input_json,&output);
                            this->m_http_response_code = this->http()->getHTTPResponseCode();
                        }
                        else {
                            DEBUG("invoke (POST-TEXT) URL: %s...",url);
                            HTTPText input_text((char *)input_data,(int)input_data_len);
                            this->m_http_status = this->http()->post(url,input_text,&output);
                            this->m_http_response_code = this->http()->getHTTPResponseCode();
                        }
                    }
                    else {
                        // no input buffer!
                        LOG_CONSOLE("invoke: ERROR HTTP(POST) requested but no input data provided... returning NULL");
                    }
                    break;
                case PUT:
                    if (input_data != NULL && input_data_len > 0) {
                        if (input_type == JSON) {
                            DEBUG("invoke (PUT-JSON) URL: %s...",url);
                            HTTPJson input_json((char *)input_data,(int)input_data_len);
                            this->m_http_status = this->http()->put(url,input_json,&output);
                            this->m_http_response_code = this->http()->getHTTPResponseCode();
                        }
                        else {
                            DEBUG("invoke (PUT-TEXT) URL: %s...",url);
                            HTTPText input_text((char *)input_data,(int)input_data_len);
                            this->m_http_status = this->http()->put(url,input_text,&output);
                            this->m_http_response_code = this->http()->getHTTPResponseCode();
                        }
                    }
                    else {
                        // no input buffer!
                        LOG_CONSOLE("invoke: ERROR HTTP(PUT) requested but no input data provided... returning NULL");
                    }
                    break;
                default:
                    // invalid HTTP verb
                    LOG_CONSOLE("invoke: ERROR invalid HTTP verb (%d) provided... returning NULL",verb);
                    break;
            }
        }
        else {
            // no OAUTH Token
            LOG_CONSOLE("unable to acquire OAUTH token for credentials provided. Unable to invoke API...");
        }
     }
     else {
         // no credentials
         LOG_CONSOLE("no/incomplete salesforce.com credentials provided. Unable to invoke API...");
     }
     
     // process any return results that we have
     if (this->httpStatus() == HTTP_OK || this->httpStatus() == HTTP_REDIRECT) {
         // do we have any redirections?
         if (this->httpResponseCodeInRange(300) /* REDIRECT */ && strlen(this->m_http_redirection_url) > 0) {
            // we have a redirect - so reset the output buffer
            memset(output_buffer,0,output_buffer_length);
            
            // we have to make a copy of the redirection URL - this is because the subsequent invoke() will wipe our current one clean
            ALLOC_BUFFER(redirect_url);
            strcpy(redirect_url,this->m_http_redirection_url);
                        
            // repeat with the redirection URL      
            DEBUG("invoke: redirecting to: %s",redirect_url);  
            return this->invoke((const char *)redirect_url,input_type,input_data,input_data_len,output_buffer,output_buffer_length,verb);
         }
         else if (this->httpResponseCodeInRange(300) /* REDIRECT */) {
            // error - got a redirect but have no URL
            LOG_CONSOLE("invoke error: received redirect but no URL...");
            this->m_http_status = HTTP_ERROR;
         }
     }
          
     // return the response in the output buffer
     if (this->httpStatus() == HTTP_OK || this->httpStatus() == HTTP_REDIRECT) return output_buffer;
     else LOG_CONSOLE("invocation failed with HTTP error code=%d status=%d",this->httpResponseCode(),this->httpStatus());
     return NULL;
 }
 
 // find the specific URL in our salesforce token
 char *SalesforceInterface::getSalesforceURL(char *key,char *url_buffer,int url_buffer_length) {
     // due to MbedJSONValue limitations - we have to manually pull out the specific JSON from our salesforce token
     int start = (int)strstr(this->m_salesforce_id,SF_URLS_START_TOKEN);
     if (start >= 0) {
         start += strlen(SF_URLS_START_TOKEN);
         int stop = (int)strstr((char *)start,SF_URLS_STOP_TOKEN);
         if (stop >= 0 && stop > start) {
             // calculate the length
             int length = stop - start + 1;
            
             // copy over the "urls" json from the salesforce token
             ALLOC_BUFFER(urls);
             int start_index = (start - (int)this->m_salesforce_id);
             for(int i=0;i<length;++i) urls[i] = this->m_salesforce_id[start_index+i];
                          
             // use MbedJSONValue to parse the "urls" json 
             MbedJSONValue parsed_urls;
             parse(parsed_urls,urls);
             
             // find the appropriate URL and copy it
             string target_url = parsed_urls[key].get<std::string>();
             
             // replace the version of the string with our selected salesforce API version
             string sf_version(this->getSalesforceAPIVersion());
             string version_tag(SF_URL_API_VER_TOKEN);
             this->replace(target_url,version_tag,sf_version);
             
             // copy the final URL to our putput
             memset(url_buffer,0,url_buffer_length);
             strcpy(url_buffer,target_url.c_str()); 
             
             // return the URL
             return url_buffer;
         }
     }
     return NULL;
 }
 
 // simple char array replacement (modifies input string!)
 void SalesforceInterface::replace(char *str,char orig_char,char new_char) {
    int length = strlen(str);
    for(int i=0;i<length;++i) if (str[i] == orig_char) str[i] = new_char;
 }
 
 //
 // substring replacement
 // Credit: http://stackoverflow.com/questions/4643512/replace-substring-with-another-substring-c
 //
 void SalesforceInterface::replace(string& line, string& oldString, string& newString) {
    const size_t oldSize = oldString.length();

    // do nothing if line is shorter than the string to find
    if( oldSize > line.length() ) return;

    const size_t newSize = newString.length();
    for( size_t pos = 0; ; pos += newSize ) {

        // Locate the substring to replace
        pos = line.find( oldString, pos );
        if( pos == string::npos ) return;
        if( oldSize == newSize ) {

            // if they're same size, use std::string::replace
            line.replace( pos, oldSize, newString );
        } 
        else {

            // if not same size, replace by erasing and inserting
            line.erase( pos, oldSize );
            line.insert( pos, newString );
        }
    }
 }

 // validate that a given HTTP result code is in the "n" range 
 bool SalesforceInterface::httpResponseCodeInRange(int n) {
     int http_response = this->httpResponseCode();
     int diff = http_response - n;
     if (diff >= 0 && diff < 100) return true;
     return false;
 }
 
 // min() method
 int SalesforceInterface::min(int value1,int value2) {
     if (value1 < value2) return value1;
     return value2;
 }
 
 // look for a specific key value pair in a json message
 bool SalesforceInterface::contains(char *json,char *key,char *value) {
     bool has_it = false;
     
     if (json != NULL && key != NULL && value != NULL) {
         // parse the json and look for a specific <key,value> pair...
         MbedJSONValue parsed;
         parse(parsed,json);
         char *answer = (char *)parsed[key].get<std::string>().c_str();
         if (strcmp(answer,value) == 0) has_it = true;
     }
     
     return has_it;
 }