Interface for invoking Salesforce.com REST calls over SSL with OAUTH authentication. This interface is designed to simplify the interaction between mbed devices and salesforce.com web services.

Dependencies:   HTTPClient-SSL MbedJSONValue

Dependents:   StatusReporter

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers SalesforceInterface.cpp Source File

SalesforceInterface.cpp

00001 /* Copyright C2014 ARM, MIT License
00002  *
00003  * Author: Doug Anson (doug.anson@arm.com)
00004  *
00005  * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
00006  * and associated documentation files the "Software", to deal in the Software without restriction,
00007  * including without limitation the rights to use, copy, modify, merge, publish, distribute,
00008  * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
00009  * furnished to do so, subject to the following conditions:
00010  *
00011  * The above copyright notice and this permission notice shall be included in all copies or
00012  * substantial portions of the Software.
00013  *
00014  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
00015  * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
00016  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
00017  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
00018  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
00019  */
00020   
00021  // Tuneables
00022  #define  SF_OAUTH_TOKEN_URL    "https://login.salesforce.com/services/oauth2/token"
00023  #define  SF_OAUTH_REQUEST_BODY "grant_type=password&client_id=%s&client_secret=%s&username=%s&password=%s"
00024  #define  SF_HTTP_AUTH_HEADER   "Authorization: Bearer %s"
00025  
00026  // search tokens for URLS in salesforce token
00027  #define SF_URLS_START_TOKEN    "\"urls\":"
00028  #define SF_URLS_STOP_TOKEN     "},"
00029  
00030  // salesforce QUERY specifier within URL
00031  #define SF_QUERY_URL_SPECIFIER "q="
00032  
00033  // salesforce URL API version token
00034  #define SF_URL_API_VER_TOKEN   "{version}"
00035  
00036  // HTTP response code to give for errored out conditions
00037  #define SF_GEN_ERR_HTTP_CODE   500
00038  
00039  // include class definition
00040  #include "SalesforceInterface.h"
00041  
00042  // Supported DataTypes for HTTPClient
00043  #include "HTTPMap.h"
00044  #include "HTTPJson.h"
00045  
00046  // Logging
00047  #define LOG(...) { if (this->logger() != NULL) { this->logger()->log(__VA_ARGS__); } }
00048  #define LOG_CONSOLE(...) { if (this->logger() != NULL) { this->logger()->logConsole(__VA_ARGS__); } }
00049  
00050  // default constructor
00051  SalesforceInterface::SalesforceInterface(HTTPClient *http,RawSerial *pc) {
00052      Logger *logger = NULL;
00053      if (pc != NULL) {
00054          logger = new Logger(pc,NULL);
00055          this->init(http,logger,true);
00056      }
00057      else {
00058          this->init(http,NULL,false);
00059      }
00060  }
00061  
00062  // alternative constructor
00063  SalesforceInterface::SalesforceInterface(HTTPClient *http,Logger *logger) {
00064      this->init(http,logger,false);
00065  }
00066  
00067  // initialize
00068  void SalesforceInterface::init(HTTPClient *http,Logger *logger,bool logger_internal) {
00069      this->m_logger = logger;
00070      this->m_logger_internal = logger_internal;
00071      this->m_http = http;
00072      this->m_username = NULL;
00073      this->m_password = NULL;
00074      this->m_client_id = NULL;
00075      this->m_client_secret = NULL;
00076      this->m_have_creds = false;
00077      this->m_http_status = HTTP_OK;
00078      this->m_http_response_code = -1;
00079      RESET_BUFFER(this->m_http_redirection_url);
00080      RESET_SML_BUFFER(this->m_error_buffer);
00081      memset(this->m_salesforce_api,0,SALESFORCE_API_VERSION_LENGTH);
00082      strcpy(this->m_salesforce_api,SALESFORCE_API_VERSION);
00083      this->resetSalesforceToken();
00084  }
00085  
00086  // destructor
00087  SalesforceInterface::~SalesforceInterface() {
00088      if (this->m_logger_internal == true && this->m_logger != NULL) delete this->m_logger;
00089  }
00090  
00091  // set credentials
00092  void SalesforceInterface::setCredentials(char *username,char *password,char *client_id,char *client_secret) {
00093      this->m_username = NULL;
00094      this->m_password = NULL;
00095      this->m_client_id = NULL;
00096      this->m_client_secret = NULL;
00097      this->m_have_creds = false;
00098      
00099      if (username != NULL) {
00100         this->m_username = username;
00101         if (password != NULL) {
00102             this->m_password = password;
00103             if (client_id != NULL) {
00104                 this->m_client_id = client_id;
00105                 if (client_secret != NULL) {
00106                     this->m_client_secret = client_secret;
00107                     this->m_have_creds = true;
00108                 }
00109             }
00110         }
00111      }
00112  }
00113  
00114  // convenience accessors
00115  Logger *SalesforceInterface::logger() { return this->m_logger; }
00116  HTTPClient *SalesforceInterface::http() { return this->m_http; }
00117  OauthToken *SalesforceInterface::oauth() { return &this->m_oauth_token; }
00118  bool SalesforceInterface::haveCreds() { return this->m_have_creds; }
00119  HTTPResult SalesforceInterface::httpStatus() { return this->m_http_status; }
00120  int SalesforceInterface::httpResponseCode() { return this->m_http_response_code; }
00121  void SalesforceInterface::setSalesforceAPIVersion(int version) { sprintf(this->m_salesforce_api,"%d",version); }
00122  void SalesforceInterface::setSalesforceAPIVersion(char *version) { if (version != NULL && strlen(version) > 0 && strlen(version) < SALESFORCE_API_VERSION_LENGTH) strcpy(this->m_salesforce_api,version); }
00123  char *SalesforceInterface::getSalesforceAPIVersion() { return this->m_salesforce_api; }
00124  
00125  // reset our oauth token
00126  void SalesforceInterface::resetOauthToken() {
00127      DEBUG("resetting OAUTH token...");
00128      this->m_oauth_token.valid              = false;
00129      this->m_oauth_token.id                 = "";
00130      this->m_oauth_token.issued_at          = "";
00131      this->m_oauth_token.token_type         = "";
00132      this->m_oauth_token.instance_url       = "";
00133      this->m_oauth_token.signature          = "";
00134      this->m_oauth_token.access_token       = "";
00135      if (this->http() != NULL) this->http()->oauthToken(NULL);
00136  }
00137  
00138  // fill our oauth token
00139  void SalesforceInterface::fillOauthToken(char *token) {
00140      if (token != NULL && strlen(token) > 0) {
00141          // parse JSON
00142          MbedJSONValue parsed_token;
00143          parse(parsed_token,token);
00144          
00145          // fill our OAUTH token
00146          this->m_oauth_token.id             = parsed_token["id"].get<std::string>();
00147          this->m_oauth_token.issued_at      = parsed_token["issued_at"].get<std::string>();
00148          this->m_oauth_token.token_type     = parsed_token["token_type"].get<std::string>();
00149          this->m_oauth_token.instance_url   = parsed_token["instance_url"].get<std::string>();
00150          this->m_oauth_token.signature      = parsed_token["signature"].get<std::string>();
00151          this->m_oauth_token.access_token   = parsed_token["access_token"].get<std::string>();
00152          
00153          // we have an OAUTH token now
00154          this->m_oauth_token.valid = true;
00155          DEBUG("valid OAUTH token acquired.");
00156          return;
00157      }
00158      LOG_CONSOLE("error: invalid or null OAUTH token fill attempt.");
00159  }
00160  
00161  // is our OAUTH token valid?
00162  bool SalesforceInterface::validOauthToken(bool fetch) {         
00163     // make sure we have a valid OAUTH Token
00164     this->checkAndGetOauthToken(fetch);
00165     return this->m_oauth_token.valid; 
00166  }
00167  
00168  // reset our salesforce token and OAUTH tokens
00169  void SalesforceInterface::resetSalesforceToken() {
00170      this->resetOauthToken();
00171      RESET_BUFFER(this->m_salesforce_id);
00172  }
00173 
00174  // do we have a valid salesforce.com token?
00175  bool SalesforceInterface::haveSalesforceToken(bool fetch) {
00176      if (this->m_salesforce_id != NULL && strlen(this->m_salesforce_id) > 0) return true;
00177      if (fetch) {
00178         LOG("Fetching Salesforce Token...");
00179         this->getSalesforceToken();
00180         return this->haveSalesforceToken(false);
00181      }
00182      return false;
00183  }
00184  
00185  // check and get our OAUTH token
00186  void SalesforceInterface::checkAndGetOauthToken(bool fetch) {
00187      DEBUG("checking for valid OAUTH token...");
00188      
00189      // reset the token structure for sanity...
00190      if (this->m_oauth_token.valid == false) this->resetOauthToken();   
00191      
00192      // should go fetch our token if we dont have one?
00193      // just pass through if we already have a token
00194      if (this->m_oauth_token.valid == true) {
00195         DEBUG("valid OAUTH token found.");
00196      }
00197      else if (this->m_oauth_token.valid == false && fetch == true) {
00198          // get our OAUTH token
00199          DEBUG("No OAUTH token found. Acquiring OAUTH token...");
00200          ALLOC_BUFFER(output_buffer);
00201          char *token = this->getOauthToken(output_buffer,MAX_BUFFER_LENGTH);
00202          if (token != NULL && strlen(token) > 0) {
00203             // fill
00204             DEBUG("Saving OAUTH token...");
00205             this->fillOauthToken(token);
00206             return;
00207          }
00208          else {
00209              // unable to get the token (reset for sanity)
00210              LOG_CONSOLE("error in acquiring OAUTH token http_code=%d status=%d",this->httpResponseCode(),this->httpStatus());
00211              this->resetOauthToken();
00212          }
00213      }
00214      
00215      // else report that we dont have a token
00216      else {
00217          LOG_CONSOLE("No OAUTH token found (fetch=false).");
00218      }
00219  }  
00220  
00221  //
00222  // get OAUTH2 Token - taken from here: 
00223  // 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
00224  //
00225  char *SalesforceInterface::getOauthToken(char *output_buffer,int output_buffer_length) {
00226      if (this->haveCreds()) { 
00227          // construct the OAUTH2 Token request body
00228          HTTPMap input;
00229          
00230          //
00231          // FORMAT: Taken from URL above method signature:
00232          //
00233          // grant_type=password&client_id=<your_client_id>&client_secret=<your_client_secret>&username=<your_username>&password=<your_password>
00234          //
00235          // ContentType: application/x-www-form-urlencoded
00236          //
00237          input.put("grant_type","password");
00238          input.put("client_id",this->m_client_id);
00239          input.put("client_secret",this->m_client_secret);
00240          input.put("username",this->m_username);
00241          input.put("password",this->m_password);
00242                   
00243          // prepare the output buffer
00244          HTTPText output(output_buffer,output_buffer_length);
00245          
00246          // HTTP POST call to gett he token 
00247          DEBUG("Getting OAUTH Token...");
00248          this->m_http_status = this->http()->post(SF_OAUTH_TOKEN_URL,input,&output);
00249 
00250          // check the result and return the token
00251          if (this->httpStatus() == HTTP_OK || this->httpResponseCode() == 200) return output_buffer;
00252          LOG_CONSOLE("acquire oauth FAILED. URL: %s http_code=%d status=%d",SF_OAUTH_TOKEN_URL,this->httpResponseCode(),this->httpStatus());
00253      }
00254      else {
00255          // no credentials
00256          LOG_CONSOLE("no/incomplete salesforce.com credentials provided. Unable to acquire OAUTH2 token...");
00257      }
00258      return NULL;
00259  }
00260  
00261  // Salesforce.com: Get our token
00262  char *SalesforceInterface::getSalesforceToken(bool fetch) {
00263     // proceed only if we have a valid OAUTH Token
00264     if (this->validOauthToken(fetch) == true) {
00265         // pull the token from salesforce
00266         RESET_BUFFER(this->m_salesforce_id);
00267         char *id = this->invoke(this->oauth()->id.c_str(),this->m_salesforce_id,MAX_BUFFER_LENGTH);
00268         
00269         // log any error status and return what we have...
00270         if (this->httpStatus() != HTTP_OK) LOG_CONSOLE("Unable to get Salesforce Token: status=%d httpCode=%d",this->httpStatus(),this->httpResponseCode());
00271         return id;
00272     }
00273     else {
00274         // unable to get token - no OAUTH token
00275         LOG_CONSOLE("Unable to get Salesforce Token: no valid OAUTH token.");
00276     }
00277     return NULL;
00278  }
00279  
00280  // QUERY: Salesforce.com
00281  char *SalesforceInterface::query(char *query_str,char *output_buffer,int output_buffer_length) {
00282      // first we have to ensure that we have valid salesforce token
00283      if (this->haveSalesforceToken()) {        
00284         // get the query url
00285         ALLOC_BUFFER(url);
00286         char *sf_url = this->getSalesforceURL("query",url,MAX_BUFFER_LENGTH);
00287         if (sf_url != NULL && strlen(sf_url) > 0) {
00288             // make sure that the query string is ready to ship...
00289             ALLOC_SML_BUFFER(tmp_query);
00290                                       
00291             // replace all spaces in query with "+"
00292             strcpy(tmp_query,query_str);
00293             this->replace(tmp_query,' ','+');   // will modify tmp_query directly...
00294                          
00295             // customize the URL with our (formatted) query string
00296             string str_url(sf_url);
00297             str_url[str_url.length()-1] = '?';                           // remove the slash and add a ?
00298             str_url = str_url + SF_QUERY_URL_SPECIFIER + tmp_query;      // add the query specifier
00299             
00300             // DEBUG - show the query URL
00301             DEBUG("query URL: %s",str_url.c_str());
00302             
00303             // invoke with GET
00304             return this->invoke((const char *)str_url.c_str(),output_buffer,output_buffer_length);
00305         }
00306         else {
00307             // unable to find the query URL...
00308             LOG_CONSOLE("query: error - unable to find query URL in salesforce token...");
00309         }
00310      }
00311      else {
00312          // dont have a valid salesforce token
00313          LOG_CONSOLE("query: error - no valid salesforce token was found...");
00314      }
00315      return NULL;
00316  }
00317  
00318  // CREATE: a record in Salesforce.com
00319  MbedJSONValue SalesforceInterface::createRecord(char *object_name,MbedJSONValue &record) { 
00320     ALLOC_BUFFER(output_buffer);
00321     char *reply = this->createRecord(object_name,(char *)record.serialize().c_str(),output_buffer,MAX_BUFFER_LENGTH);
00322     MbedJSONValue response;
00323     if (reply != NULL && strlen(reply) > 0) parse(response,reply);
00324     return response;
00325  }
00326 
00327  // READ: a specific record in Salesforce.com
00328  MbedJSONValue SalesforceInterface::readRecord(char *object_name,char *record_id,char *record_value) {
00329     ALLOC_BUFFER(output_buffer);
00330     char *reply = this->readRecord(object_name,record_id,record_value,output_buffer,MAX_BUFFER_LENGTH);
00331     MbedJSONValue response;
00332     if (reply != NULL && strlen(reply) > 0) parse(response,reply);
00333     return response; 
00334  }
00335 
00336  // UPDATE: a specific record in Salesforce.com
00337  bool SalesforceInterface::updateRecord(char *object_name,char *record_id,MbedJSONValue &record) {
00338     RESET_SML_BUFFER(this->m_error_buffer);
00339     return this->updateRecord(object_name,record_id,(char *)record.serialize().c_str(),this->m_error_buffer,MAX_SMALL_BUFFER_LENGTH);
00340  }
00341  
00342  // UPSERT: update/insert an External ID record in Salesforce.com
00343  bool SalesforceInterface::upsertRecord(char *object_name,char *external_id_field_name,char *external_id_field_value,MbedJSONValue &record) {
00344      RESET_SML_BUFFER(this->m_error_buffer);
00345      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);
00346  }
00347  
00348  // DELETE: a specific record in Salesforce.com
00349  bool SalesforceInterface::deleteRecord(char *object_name,char *record_id) {
00350       RESET_SML_BUFFER(this->m_error_buffer);
00351       return this->deleteRecord(object_name,record_id,this->m_error_buffer,MAX_SMALL_BUFFER_LENGTH);
00352  }
00353  
00354  // ERROR: get last error result
00355  MbedJSONValue SalesforceInterface::getLastError() {
00356      MbedJSONValue error;
00357      if (strlen(this->m_error_buffer) > 0) parse(error,this->m_error_buffer);
00358      return error;
00359  }
00360  
00361  // CREATE: a record in Salesforce.com
00362  char *SalesforceInterface::createRecord(char *object_name,char *json_data,char *output_buffer,int output_buffer_length) {
00363      // parameter check
00364      if (object_name != NULL && strlen(object_name) > 0 && json_data != NULL && strlen(json_data) > 0 && output_buffer != NULL && output_buffer_length > 0) {
00365          // first we have to ensure that we have valid salesforce token
00366          if (this->haveSalesforceToken()) {        
00367             // get the sobjects url
00368             ALLOC_BUFFER(url);
00369             char *sf_url = this->getSalesforceURL("sobjects",url,MAX_BUFFER_LENGTH);
00370             if (sf_url != NULL && strlen(sf_url) > 0) {   
00371                 // convert to string
00372                 string str_url(sf_url);
00373                          
00374                 // add object name that we want to create a record in
00375                 str_url += object_name;
00376                 
00377                 // DEBUG
00378                 DEBUG("createRecord: URL: %s  DATA: %s",str_url.c_str(),json_data);
00379                 
00380                 // now invoke with POST with JSON data type
00381                 return this->invoke(str_url.c_str(),json_data,strlen(json_data)+1,output_buffer,output_buffer_length);
00382             }
00383          }
00384          else {
00385              // dont have a valid salesforce token
00386              LOG_CONSOLE("createRecord: error - no valid salesforce token was found...");
00387          }
00388      }
00389      else {
00390          // invalid or NULL parameters
00391          LOG_CONSOLE("createRecord: error - invalid or NULL parameters...");
00392      }
00393      this->m_http_response_code = SF_GEN_ERR_HTTP_CODE;
00394      return NULL;
00395  }
00396 
00397  // READ: a specific record in Salesforce.com
00398  char *SalesforceInterface::readRecord(char *object_name,char *record_id,char *record_value,char *output_buffer,int output_buffer_length) {
00399      // parameter check
00400      if (object_name != NULL && strlen(object_name) > 0 && record_id != NULL && strlen(record_id) > 0 && output_buffer != NULL && output_buffer_length > 0) {
00401          // first we have to ensure that we have valid salesforce token
00402          if (this->haveSalesforceToken()) {        
00403             // get the sobjects url
00404             ALLOC_BUFFER(url);
00405             char *sf_url = this->getSalesforceURL("sobjects",url,MAX_BUFFER_LENGTH);
00406             if (sf_url != NULL && strlen(sf_url) > 0) {   
00407                 // convert to string
00408                 string str_url(sf_url);
00409                          
00410                 // add object name that we want to create a record in
00411                 str_url += object_name;
00412                 
00413                 // add the record token
00414                 str_url += "/";
00415                 str_url += record_id;
00416                 
00417                 // add the record value (if present)
00418                 if (record_value != NULL && strlen(record_value) > 0) {
00419                     str_url += "/";
00420                     str_url += record_value;
00421                 }
00422                 
00423                 // DEBUG
00424                 DEBUG("readRecord: URL: %s",str_url.c_str());
00425                 
00426                 // now invoke with GET with JSON data type
00427                 return this->invoke(str_url.c_str(),output_buffer,output_buffer_length);
00428             }
00429          }
00430          else {
00431              // dont have a valid salesforce token
00432              LOG_CONSOLE("readRecord: error - no valid salesforce token was found...");
00433          }
00434      }
00435      else {
00436          // invalid or NULL parameters
00437          LOG_CONSOLE("readRecord: error - invalid or NULL parameters...");
00438      }
00439      this->m_http_response_code = SF_GEN_ERR_HTTP_CODE;
00440      return NULL;
00441  }
00442 
00443  // UPDATE: a specific record in Salesforce.com
00444  bool SalesforceInterface::updateRecord(char *object_name,char *record_id,char *json_data,char *output_buffer,int output_buffer_length) { 
00445      // reset the error buffer
00446      RESET_SML_BUFFER(this->m_error_buffer);
00447       
00448      // parameter check
00449      if (object_name != NULL && strlen(object_name) > 0 && json_data != NULL && strlen(json_data) > 0) {
00450          // first we have to ensure that we have valid salesforce token
00451          if (this->haveSalesforceToken()) {        
00452             // get the sobjects url
00453             ALLOC_BUFFER(url);
00454             char *sf_url = this->getSalesforceURL("sobjects",url,MAX_BUFFER_LENGTH);
00455             if (sf_url != NULL && strlen(sf_url) > 0) {   
00456                 // convert to string
00457                 string str_url(sf_url);
00458                          
00459                 // add object name that we want to create a record in
00460                 str_url += object_name;
00461                 
00462                 // add the record token
00463                 str_url += "/";
00464                 str_url += record_id;
00465                 
00466                 // HTTPClient does not support PATCH, so we have to use POST with a special added parameter
00467                 str_url += "?_HttpMethod=PATCH";
00468                 
00469                 // DEBUG
00470                 DEBUG("updateRecord: URL: %s DATA: %s",str_url.c_str(),json_data);
00471                 
00472                 // now invoke with POST with JSON data type
00473                 char *reply = this->invoke(str_url.c_str(),json_data,strlen(json_data)+1,output_buffer,output_buffer_length);
00474                 
00475                 // DEBUG
00476                 DEBUG("updateRecord: http status=%d",this->httpResponseCode());
00477                 
00478                 // return our status
00479                 if (this->httpResponseCodeInRange(200)) return true;
00480                 
00481                 // we are in error - so copy the result if we have one and return false
00482                 if (reply != NULL && strlen(reply) > 0) strncpy(this->m_error_buffer,reply,this->min(strlen(reply),MAX_SMALL_BUFFER_LENGTH));
00483                 return false;
00484             }
00485          }
00486          else {
00487              // dont have a valid salesforce token
00488              LOG_CONSOLE("updateRecord: error - no valid salesforce token was found...");
00489          }
00490      }
00491      else {
00492          // invalid or NULL parameters
00493          LOG_CONSOLE("updateRecord: error - invalid or NULL parameters...");
00494      }
00495      this->m_http_response_code = SF_GEN_ERR_HTTP_CODE;
00496      return false;  
00497  }
00498  
00499  // UPSERT: update/insert a specific External record in Salesforce.com
00500  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) {  
00501      // reset the error buffer
00502      RESET_SML_BUFFER(this->m_error_buffer);
00503 
00504      // parameter check
00505      if (object_name != NULL && strlen(object_name) > 0 && json_data != NULL && strlen(json_data) > 0) {
00506          // first we have to ensure that we have valid salesforce token
00507          if (this->haveSalesforceToken()) {        
00508             // get the sobjects url
00509             ALLOC_BUFFER(url);
00510             char *sf_url = this->getSalesforceURL("sobjects",url,MAX_BUFFER_LENGTH);
00511             if (sf_url != NULL && strlen(sf_url) > 0) {   
00512                 // convert to string
00513                 string str_url(sf_url);
00514                          
00515                 // add object name that we want to create a record in
00516                 str_url += object_name;
00517                 
00518                 // add the external field name token
00519                 str_url += "/";
00520                 str_url += external_id_field_name;
00521                 
00522                 // add the external field value token (if not NULL)
00523                 if (external_id_field_value != NULL && strlen(external_id_field_value) > 0) {
00524                     str_url += "/";
00525                     str_url += external_id_field_value;
00526                 }
00527                 
00528                 // HTTPClient does not support PATCH, so we have to use POST with a special added parameter
00529                 str_url += "?_HttpMethod=PATCH";
00530                 
00531                 // DEBUG
00532                 DEBUG("upsertRecord: URL: %s DATA: %s",str_url.c_str(),json_data);
00533                 
00534                 // now invoke with POST with JSON data type
00535                 char *reply = this->invoke(str_url.c_str(),json_data,strlen(json_data)+1,output_buffer,output_buffer_length);
00536                 
00537                 // DEBUG
00538                 DEBUG("upsertRecord: http status=%d",this->httpResponseCode());
00539                 
00540                 // return our status
00541                 if (this->httpResponseCodeInRange(200)) return true;
00542                 
00543                 // we are in error - so copy the result if we have one and return false
00544                 if (reply != NULL && strlen(reply) > 0) strncpy(this->m_error_buffer,reply,this->min(strlen(reply),MAX_SMALL_BUFFER_LENGTH));
00545                 return false;
00546             }
00547          }
00548          else {
00549              // dont have a valid salesforce token
00550              LOG_CONSOLE("upsertRecord: error - no valid salesforce token was found...");
00551          }
00552      }
00553      else {
00554          // invalid or NULL parameters
00555          LOG_CONSOLE("upsertRecord: error - invalid or NULL parameters...");
00556      }
00557      this->m_http_response_code = SF_GEN_ERR_HTTP_CODE;
00558      return false;  
00559  }
00560   
00561  // DELETE: a specific record in Salesforce.com
00562  bool SalesforceInterface::deleteRecord(char *object_name,char *record_id,char *output_buffer,int output_buffer_length) {
00563      // reset the error buffer
00564      RESET_SML_BUFFER(this->m_error_buffer);
00565 
00566      // parameter check
00567      if (object_name != NULL && strlen(object_name) > 0 && record_id != NULL && strlen(record_id) > 0) {
00568          // first we have to ensure that we have valid salesforce token
00569          if (this->haveSalesforceToken()) {        
00570             // get the sobjects url
00571             ALLOC_BUFFER(url);
00572             char *sf_url = this->getSalesforceURL("sobjects",url,MAX_BUFFER_LENGTH);
00573             if (sf_url != NULL && strlen(sf_url) > 0) {   
00574                 // convert to string
00575                 string str_url(sf_url);
00576                          
00577                 // add object name that we want to create a record in
00578                 str_url += object_name;
00579                 
00580                 // add the record token
00581                 str_url += "/";
00582                 str_url += record_id;
00583                 
00584                 // DEBUG
00585                 LOG_CONSOLE("deleteRecord: URL: %s",str_url.c_str());
00586                 
00587                 // now invoke with DELETE 
00588                 ALLOC_SML_BUFFER(output_buffer);
00589                 char *reply = this->invoke(str_url.c_str(),output_buffer,output_buffer_length,DELETE);
00590                 
00591                 // DEBUG
00592                 DEBUG("deleteRecord: http status=%d",this->httpResponseCode());
00593                 
00594                 // return our status
00595                 if (this->httpResponseCodeInRange(200)) return true;
00596                 
00597                 // we are in error - so copy the result if we have one and return false
00598                 if (reply != NULL && strlen(reply) > 0) strncpy(this->m_error_buffer,reply,this->min(strlen(reply),MAX_SMALL_BUFFER_LENGTH));
00599                 return false;
00600             }
00601          }
00602          else {
00603              // dont have a valid salesforce token
00604              LOG_CONSOLE("deleteRecord: error - no valid salesforce token was found...");
00605          }
00606      }
00607      else {
00608          // invalid or NULL parameters
00609          LOG_CONSOLE("deleteRecord: error - invalid or NULL parameters...");
00610      }
00611      this->m_http_response_code = SF_GEN_ERR_HTTP_CODE;
00612      return false;
00613  }
00614  
00615  // Salesforce.com Invoke: defaults to GET
00616  char *SalesforceInterface::invoke(const char *url,char *output_buffer,int output_buffer_length) { 
00617     return this->invoke(url,output_buffer,output_buffer_length,GET); 
00618  }
00619  
00620  // Salesforce.com Invoke: GET or DELETE with simple output
00621  char *SalesforceInterface::invoke(const char *url,char *output_buffer,int output_buffer_length,HttpVerb verb) { 
00622     char *response = NULL;
00623     switch(verb) {
00624         case GET:
00625         case DELETE:
00626             // GET and DELETE only require an output buffer...
00627             response = this->invoke(url,NUM_TYPES,NULL,0,output_buffer,output_buffer_length,verb); 
00628             break;
00629         default:
00630             // wrong verb for this call interface...
00631             LOG_CONSOLE("invoke: invalid call: must be either GET or DELETE verb if only output buffer is provided");
00632             break;
00633     }
00634     return response;
00635  }
00636  
00637  // Salesforce.com Invoke: defaults to POST with JSON input data type                                                  
00638  char *SalesforceInterface::invoke(const char *url,const char *input_data,const int input_data_len,char *output_buffer,int output_buffer_length) { 
00639     return this->invoke(url,JSON,input_data,input_data_len,output_buffer,output_buffer_length); 
00640  }
00641  
00642  // Salesforce.com Invoke: defaults to POST with variable input data type                                                  
00643  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) { 
00644     return this->invoke(url,input_type,input_data,input_data_len,output_buffer,output_buffer_length,POST); 
00645  }
00646  
00647  // Salesforce.com Invoke: full fidelity method
00648  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) {     
00649      // initialize our invocation status and response code
00650      this->m_http_response_code = -1;
00651      this->m_http_status = HTTP_ERROR;
00652                  
00653      // param check: make sure that we at least have an output buffer and URL
00654      if (url != NULL && strlen(url) > 0 && output_buffer != NULL && output_buffer_length > 0) {         
00655         // proceed only if we have a valid OAUTH Token
00656         if (this->validOauthToken() == true) {                  
00657             // use OAUTH headers
00658             this->http()->oauthToken(this->oauth()->access_token.c_str());
00659             
00660             // reset the redirection url buffer in case we get a redirect...
00661             RESET_BUFFER(this->m_http_redirection_url);
00662             this->http()->setLocationBuf((char *)this->m_http_redirection_url,MAX_BUFFER_LENGTH);
00663 
00664             // create our output/response buffer
00665             HTTPText output(output_buffer,output_buffer_length);
00666             
00667             // now make the HTTP(S) request
00668             switch(verb) {
00669                 case GET:
00670                     DEBUG("invoke (GET) URL: %s...",url);
00671                     this->m_http_status = this->http()->get(url,&output);
00672                     this->m_http_response_code = this->http()->getHTTPResponseCode();
00673                     break;
00674                 case DELETE:
00675                     DEBUG("invoke (DEL) URL: %s...",url);
00676                     this->m_http_status = this->http()->del(url,&output);
00677                     this->m_http_response_code = this->http()->getHTTPResponseCode();
00678                     break;
00679                 case POST:
00680                     if (input_data != NULL && input_data_len > 0) {
00681                         if (input_type == JSON) {
00682                             DEBUG("invoke (POST-JSON) URL: %s...",url);
00683                             HTTPJson input_json((char *)input_data,(int)input_data_len);
00684                             this->m_http_status = this->http()->post(url,input_json,&output);
00685                             this->m_http_response_code = this->http()->getHTTPResponseCode();
00686                         }
00687                         else {
00688                             DEBUG("invoke (POST-TEXT) URL: %s...",url);
00689                             HTTPText input_text((char *)input_data,(int)input_data_len);
00690                             this->m_http_status = this->http()->post(url,input_text,&output);
00691                             this->m_http_response_code = this->http()->getHTTPResponseCode();
00692                         }
00693                     }
00694                     else {
00695                         // no input buffer!
00696                         LOG_CONSOLE("invoke: ERROR HTTP(POST) requested but no input data provided... returning NULL");
00697                     }
00698                     break;
00699                 case PUT:
00700                     if (input_data != NULL && input_data_len > 0) {
00701                         if (input_type == JSON) {
00702                             DEBUG("invoke (PUT-JSON) URL: %s...",url);
00703                             HTTPJson input_json((char *)input_data,(int)input_data_len);
00704                             this->m_http_status = this->http()->put(url,input_json,&output);
00705                             this->m_http_response_code = this->http()->getHTTPResponseCode();
00706                         }
00707                         else {
00708                             DEBUG("invoke (PUT-TEXT) URL: %s...",url);
00709                             HTTPText input_text((char *)input_data,(int)input_data_len);
00710                             this->m_http_status = this->http()->put(url,input_text,&output);
00711                             this->m_http_response_code = this->http()->getHTTPResponseCode();
00712                         }
00713                     }
00714                     else {
00715                         // no input buffer!
00716                         LOG_CONSOLE("invoke: ERROR HTTP(PUT) requested but no input data provided... returning NULL");
00717                     }
00718                     break;
00719                 default:
00720                     // invalid HTTP verb
00721                     LOG_CONSOLE("invoke: ERROR invalid HTTP verb (%d) provided... returning NULL",verb);
00722                     break;
00723             }
00724         }
00725         else {
00726             // no OAUTH Token
00727             LOG_CONSOLE("unable to acquire OAUTH token for credentials provided. Unable to invoke API...");
00728         }
00729      }
00730      else {
00731          // no credentials
00732          LOG_CONSOLE("no/incomplete salesforce.com credentials provided. Unable to invoke API...");
00733      }
00734      
00735      // process any return results that we have
00736      if (this->httpStatus() == HTTP_OK || this->httpStatus() == HTTP_REDIRECT) {
00737          // do we have any redirections?
00738          if (this->httpResponseCodeInRange(300) /* REDIRECT */ && strlen(this->m_http_redirection_url) > 0) {
00739             // we have a redirect - so reset the output buffer
00740             memset(output_buffer,0,output_buffer_length);
00741             
00742             // we have to make a copy of the redirection URL - this is because the subsequent invoke() will wipe our current one clean
00743             ALLOC_BUFFER(redirect_url);
00744             strcpy(redirect_url,this->m_http_redirection_url);
00745                         
00746             // repeat with the redirection URL      
00747             DEBUG("invoke: redirecting to: %s",redirect_url);  
00748             return this->invoke((const char *)redirect_url,input_type,input_data,input_data_len,output_buffer,output_buffer_length,verb);
00749          }
00750          else if (this->httpResponseCodeInRange(300) /* REDIRECT */) {
00751             // error - got a redirect but have no URL
00752             LOG_CONSOLE("invoke error: received redirect but no URL...");
00753             this->m_http_status = HTTP_ERROR;
00754          }
00755      }
00756           
00757      // return the response in the output buffer
00758      if (this->httpStatus() == HTTP_OK || this->httpStatus() == HTTP_REDIRECT) return output_buffer;
00759      else LOG_CONSOLE("invocation failed with HTTP error code=%d status=%d",this->httpResponseCode(),this->httpStatus());
00760      return NULL;
00761  }
00762  
00763  // find the specific URL in our salesforce token
00764  char *SalesforceInterface::getSalesforceURL(char *key,char *url_buffer,int url_buffer_length) {
00765      // due to MbedJSONValue limitations - we have to manually pull out the specific JSON from our salesforce token
00766      int start = (int)strstr(this->m_salesforce_id,SF_URLS_START_TOKEN);
00767      if (start >= 0) {
00768          start += strlen(SF_URLS_START_TOKEN);
00769          int stop = (int)strstr((char *)start,SF_URLS_STOP_TOKEN);
00770          if (stop >= 0 && stop > start) {
00771              // calculate the length
00772              int length = stop - start + 1;
00773             
00774              // copy over the "urls" json from the salesforce token
00775              ALLOC_BUFFER(urls);
00776              int start_index = (start - (int)this->m_salesforce_id);
00777              for(int i=0;i<length;++i) urls[i] = this->m_salesforce_id[start_index+i];
00778                           
00779              // use MbedJSONValue to parse the "urls" json 
00780              MbedJSONValue parsed_urls;
00781              parse(parsed_urls,urls);
00782              
00783              // find the appropriate URL and copy it
00784              string target_url = parsed_urls[key].get<std::string>();
00785              
00786              // replace the version of the string with our selected salesforce API version
00787              string sf_version(this->getSalesforceAPIVersion());
00788              string version_tag(SF_URL_API_VER_TOKEN);
00789              this->replace(target_url,version_tag,sf_version);
00790              
00791              // copy the final URL to our putput
00792              memset(url_buffer,0,url_buffer_length);
00793              strcpy(url_buffer,target_url.c_str()); 
00794              
00795              // return the URL
00796              return url_buffer;
00797          }
00798      }
00799      return NULL;
00800  }
00801  
00802  // simple char array replacement (modifies input string!)
00803  void SalesforceInterface::replace(char *str,char orig_char,char new_char) {
00804     int length = strlen(str);
00805     for(int i=0;i<length;++i) if (str[i] == orig_char) str[i] = new_char;
00806  }
00807  
00808  //
00809  // substring replacement
00810  // Credit: http://stackoverflow.com/questions/4643512/replace-substring-with-another-substring-c
00811  //
00812  void SalesforceInterface::replace(string& line, string& oldString, string& newString) {
00813     const size_t oldSize = oldString.length();
00814 
00815     // do nothing if line is shorter than the string to find
00816     if( oldSize > line.length() ) return;
00817 
00818     const size_t newSize = newString.length();
00819     for( size_t pos = 0; ; pos += newSize ) {
00820 
00821         // Locate the substring to replace
00822         pos = line.find( oldString, pos );
00823         if( pos == string::npos ) return;
00824         if( oldSize == newSize ) {
00825 
00826             // if they're same size, use std::string::replace
00827             line.replace( pos, oldSize, newString );
00828         } 
00829         else {
00830 
00831             // if not same size, replace by erasing and inserting
00832             line.erase( pos, oldSize );
00833             line.insert( pos, newString );
00834         }
00835     }
00836  }
00837 
00838  // validate that a given HTTP result code is in the "n" range 
00839  bool SalesforceInterface::httpResponseCodeInRange(int n) {
00840      int http_response = this->httpResponseCode();
00841      int diff = http_response - n;
00842      if (diff >= 0 && diff < 100) return true;
00843      return false;
00844  }
00845  
00846  // min() method
00847  int SalesforceInterface::min(int value1,int value2) {
00848      if (value1 < value2) return value1;
00849      return value2;
00850  }
00851  
00852  // look for a specific key value pair in a json message
00853  bool SalesforceInterface::contains(char *json,char *key,char *value) {
00854      bool has_it = false;
00855      
00856      if (json != NULL && key != NULL && value != NULL) {
00857          // parse the json and look for a specific <key,value> pair...
00858          MbedJSONValue parsed;
00859          parse(parsed,json);
00860          char *answer = (char *)parsed[key].get<std::string>().c_str();
00861          if (strcmp(answer,value) == 0) has_it = true;
00862      }
00863      
00864      return has_it;
00865  }