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

Revision:
16:3d160f224084
Parent:
15:89044c68ad36
Child:
17:6c774354b599
--- a/SalesforceInterface.h	Tue Sep 23 17:35:34 2014 +0000
+++ b/SalesforceInterface.h	Tue Sep 23 20:26:24 2014 +0000
@@ -11,7 +11,7 @@
  * The above copyright notice and this permission notice shall be included in all copies or
  * substantial portions of the Software.
  *
- * THE SOFTWARE IS PROVtokenED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
+ * 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,
@@ -87,268 +87,330 @@
  * Example Project: http://mbed.org/users/ansond/code/df-2014-salesforce-testharness-k64f/ 
  *
  * @code
- * #include "Definitions.h"     // definitions including platform specifics...
- * #include "ErrorHandler.h"
- *  
- * // include salesforce.com credentials
- * #include "sf_creds.h"
- *   
- * // our Serial port
- * #include "BufferedSerial.h"
- * BufferedSerial pc(USBTX, USBRX);
- * 
- * // Ethernet
- * #include "EthernetInterface.h"
- * EthernetInterface ethernet;
- *  
- * // HTTP 
- * #include "HTTPClient.h"
- * HTTPClient http;
- * 
- * // Salesforce.com Interface
- * #include "SalesforceInterface.h"
- *  
- * // test case persistence
- * char *object_name = NULL;
- * char *account_name = NULL;
- * char *updated_account_name = NULL;
- * DEFINE_SML_BUFFER(record_id);
- *    
- * // *************** Test Cases ************************
- * 
- * void Test_getSalesforceToken(ErrorHandler *logger,SalesforceInterface *sf) {
- *     logger->log("\r\n\r\nGetting Salesforce Token...");
- *     logger->turnLEDPurple();
- *     
- *     // get the salesforce token     
- *     char *id = sf->getSalesforceToken();
- *     if (id != NULL && strlen(id) > 0)
- *         logger->log("Saleforce token: %s",id);
- *     else
- *         logger->log("Unable to get Saleforce token");
- *     logger->turnLEDGreen();
- * }
- * 
- * void Test_query(ErrorHandler *logger,SalesforceInterface *sf,char *query_str) {
- *     logger->log("\r\n\r\nExecuting test query: %s",query_str);
- *     logger->turnLEDPurple();
- *     if (query_str != NULL && strlen(query_str) > 0) { 
- *         ALLOC_BUFFER(response);
- *         char *answer = sf->query(query_str,response,MAX_BUFFER_LENGTH);
- *         if (answer != NULL) logger->log("query result: %s",answer);
- *         else logger->log("query - NULL result");
- *     }
- *     else {
- *        logger->log("Unable to perform query as we do not have our salesforce token");
- *     }
- *     logger->turnLEDGreen();
- * }
- * 
- * void Test_create(ErrorHandler *logger,SalesforceInterface *sf) {
- *     logger->log("\r\n\r\nExecuting create()");
- *     logger->turnLEDPurple();
- *     
- *     // create a new record
- *     MbedJSONValue new_record;
- *     new_record["name"] = account_name;
- *     
- *     // DEBUG
- *     logger->log("Create: new record: %s",new_record.serialize().c_str());
- *     
- *     // create...
- *     MbedJSONValue response = sf->createRecord(object_name,new_record);
- *     
- *     // display the result
- *     char *result = (char *)response.serialize().c_str();
- *     if (result != NULL && strlen(result) > 0 && strcmp(result,"null") != 0) {
- *        // save off the token if we succeeded
- *        logger->log("Create: result: %s",result);
- *        logger->log("Create: http_code=%d",sf->httpResponseCode());
- *        RESET_SML_BUFFER(record_id);
- *        strcpy(record_id,(char *)response["id"].get<std::string>().c_str());
- *     }
- *     else {
- *        // failure
- *        logger->log("Create: FAILED http_code=%d",sf->httpResponseCode());
- *     }
- *     logger->turnLEDGreen();
- * }
- * 
- * void Test_read(ErrorHandler *logger,SalesforceInterface *sf) {
- *     logger->log("\r\n\r\nExecuting read()");
- *     logger->turnLEDPurple();
- *          
- *     // DEBUG
- *     logger->log("Read: reading: %s from %s",record_id,object_name);
- *     
- *     // read...
- *     MbedJSONValue response = sf->readRecord(object_name,record_id);
- *     
- *     // display the result
- *     char *result = (char *)response.serialize().c_str();
- *     if (result != NULL && strlen(result) > 0 && strcmp(result,"null") != 0) {
- *        // save off the token if we succeeded
- *        logger->log("Read: result: %s",result);
- *        logger->log("Read: http_code=%d",sf->httpResponseCode());
- *     }
- *     else {
- *        // failure
- *        logger->log("Read: FAILED http_code=%d",sf->httpResponseCode());
- *     }
- *     
- *     logger->turnLEDGreen();
- * }
- * 
- * void Test_update(ErrorHandler *logger,SalesforceInterface *sf) {
- *     logger->log("\r\n\r\nExecuting update()");
- *     logger->turnLEDPurple();
- *     
- *     // update am existing record - assume "name" is the proper key for the record you wish to update...
- *     MbedJSONValue changed_record;
- *     changed_record["name"] = updated_account_name;
- *     
- *     // DEBUG
- *     logger->log("Update: updated record: %s",changed_record.serialize().c_str());
- *     
- *     // update...
- *     bool updated = sf->updateRecord(object_name,record_id,changed_record);
- *     
- *     // display the result
- *     if (updated) {
- *        // SUCCESS
- *        logger->log("Update: successful! http_code=%d",sf->httpResponseCode());
- *     }
- *     else {
- *        // failure
- *        logger->log("Update: FAILED http_code=%d",sf->httpResponseCode());
- *     }
- *     logger->turnLEDGreen();
- * }
- * 
- * void Test_delete(ErrorHandler *logger,SalesforceInterface *sf) {
- *     logger->log("\r\n\r\nExecuting delete()");
- *     logger->turnLEDPurple();
- *     
- *     // DEBUG
- *     logger->log("Delete: deleting: %s from %s",record_id,object_name);
- *     
- *     // delete...
- *     bool deleted = sf->deleteRecord(object_name,record_id);
- *     
- *     // display the result
- *     if (deleted) {
- *        // SUCCESS
- *        logger->log("Delete: successful! http_code=%d",sf->httpResponseCode());
- *     }
- *     else {
- *        // failure
- *        logger->log("Delete: FAILED http_code=%d",sf->httpResponseCode());
- *    }
- *     
- *     logger->turnLEDGreen();
- * }
- *  
- * void Test_reset_auth(ErrorHandler *logger,SalesforceInterface *sf) {
- *     logger->log("\r\n\r\nForcing API to reset OAUTH token and Salesforce Token...");
- *     logger->turnLEDPurple();
- *     sf->resetSalesforceToken();
- *     logger->turnLEDGreen();
- * }
- * 
- * // *************** Test Cases ************************
- * 
- * // Main Task...
- * void mainTask(void const *v) {
- *        
- *    // create our object instances 
- *    ErrorHandler logger(&pc,NULL);
- *    SalesforceInterface *sf = NULL;
- *    
- *    // announce
- *    logger.log("\r\n\r\nARM Salesforce Interface TestHarness v%s",APP_VERSION);
- *    logger.turnLEDBlue();
- *    
- *    // initialize Ethernet
- *    logger.log("Initializing Ethernet...");
- *    ethernet.init();
- *    
- *    // get a DHCP address and bring the network interface up
- *    logger.log("Getting IP Address...");
- *    logger.turnLEDOrange();
- *    if (ethernet.connect() == 0) {
- *        // log our IP address (DHCP)
- *        logger.log("IP Address: %s",ethernet.getIPAddress());
- *        
- *        // allocate the Salesforce.com interface
- *        logger.log("Allocating Saleforce.com interface...");
- *        sf = new SalesforceInterface(&logger,&http);
- *        
- *        // set our Salesforce.com credentials
- *        sf->setCredentials(username,password,client_id,client_secret);
- *        
- *        // *************** BEGIN TEST CASES *****************        
- * 
- *        // configuration for the test cases        
- *        object_name          = "Account";       // use the account object
- *        account_name         = "ARM";           // add this record (name)
- *        updated_account_name = "ARM Holdings";  // update the existing record's name to this
- *        RESET_SML_BUFFER(record_id);             // buffer for the record's token
- *               
- *        // Perform a Create
- *        Test_create(&logger,sf);
- *                
- *        // Perform a Read
- *        Test_read(&logger,sf);
- *
- *        // Perform a Query
- *        Test_query(&logger,sf,"SELECT Id,Name FROM Account LIMIT 5");
- *        
- *        // Perform an Update
- *        Test_update(&logger,sf);
- *        
- *        // Perform a second Read to visually confirm the update above...
- *        Test_read(&logger,sf);
- *        
- *        // force the API to re-acquire the OAUTH token and Salesforce Token 
- *        Test_reset_auth(&logger,sf);
- *        
- *        // Perform a Read (should re-acquire the OAUTH token and Salesforce Token)
- *        Test_read(&logger,sf);
- *        
- *        // Perform a Delete
- *        Test_delete(&logger,sf);
- *                
- *        // reset the record token buffer
- *        // RESET_SML_BUFFER(record_id);
- *        
- *        // Perform a Read - should error out
- *        Test_read(&logger,sf);
- *                        
- *        // reset the record token buffer
- *        RESET_SML_BUFFER(record_id);
- *        
- *        // *************** BEGIN TEST CASES *****************      
- *        
- *        // entering main loop
- *        logger.log("All tests complete...\r\nExiting...");
- *        logger.turnLEDBlue();
- *        exit(0);
- *     }
- *     else {
- *         logger.log("No Network... Exiting...");
- *         logger.turnLEDRed();
- *         exit(1);
- *     }
- *
- *  }
- *  
- *  // main entry
- *  int main() {
- *     Thread workerTask(mainTask, NULL, osPriorityNormal, STACK_SIZE);
- *     while (true) {
- *         Thread::wait(10*WAIT_TIME_MS);
- *     }
- *  }  
+ 
+ #include "Definitions.h"       // definitions including platform specifics...
+ #include "ErrorHandler.h"
+ 
+ // include salesforce.com credentials
+ #include "sf_creds.h"
+ 
+ // our Serial port
+ #include "BufferedSerial.h"
+ BufferedSerial pc(USBTX, USBRX);
+ 
+ // Ethernet
+ #include "EthernetInterface.h"
+ EthernetInterface ethernet;
+ 
+ // HTTP 
+ #include "HTTPClient.h"
+ HTTPClient http;
+ 
+ // Salesforce.com Interface
+ #include "SalesforceInterface.h"
+  
+ // test case persistence
+ char *object_name = NULL;
+ char *account_name = NULL;
+ char *updated_account_name = NULL;
+ char *external_id_field_name = NULL;
+ char *external_id_field_value = NULL;
+ DEFINE_SML_BUFFER(record_id);
+    
+ // *************** Test Cases ************************
+ 
+ void Test_getSalesforceToken(ErrorHandler *logger,SalesforceInterface *sf) {
+     logger->log("\r\n\r\nGetting Salesforce Token...");
+     logger->turnLEDPurple();
+     
+     // get the salesforce token     
+     char *id = sf->getSalesforceToken();
+     if (id != NULL && strlen(id) > 0)
+         logger->log("Saleforce token: %s",id);
+     else
+         logger->log("Unable to get Saleforce token");
+     logger->turnLEDGreen();
+ }
+ 
+ void Test_query(ErrorHandler *logger,SalesforceInterface *sf,char *query_str) {
+     logger->log("\r\n\r\nExecuting test query: %s",query_str);
+     logger->turnLEDPurple();
+     if (query_str != NULL && strlen(query_str) > 0) { 
+         ALLOC_BUFFER(response);
+         char *answer = sf->query(query_str,response,MAX_BUFFER_LENGTH);
+         if (answer != NULL) logger->log("query result: %s",answer);
+         else logger->log("query - NULL result");
+     }
+     else {
+        logger->log("Unable to perform query as we do not have our salesforce token");
+     }
+     logger->turnLEDGreen();
+ }
+ 
+ void Test_create(ErrorHandler *logger,SalesforceInterface *sf) {
+     logger->log("\r\n\r\nExecuting create()");
+     logger->turnLEDPurple();
+     
+     // create a new record
+     MbedJSONValue new_record;
+     new_record["name"] = account_name;
+     
+     // DEBUG
+     logger->log("Create: new record: %s",new_record.serialize().c_str());
+     
+     // create...
+     MbedJSONValue response = sf->createRecord(object_name,new_record);
+     
+     // display the result
+     char *result = (char *)response.serialize().c_str();
+     if (result != NULL && strlen(result) > 0 && strcmp(result,"null") != 0) {
+        // save off the token if we succeeded
+        logger->log("Create: result: %s",result);
+        logger->log("Create: http_code=%d",sf->httpResponseCode());
+        RESET_SML_BUFFER(record_id);
+        strcpy(record_id,(char *)response["id"].get<std::string>().c_str());
+     }
+     else {
+        // failure
+        logger->log("Create: FAILED http_code=%d",sf->httpResponseCode());
+     }
+     logger->turnLEDGreen();
+ }
+ 
+ void Test_read(ErrorHandler *logger,SalesforceInterface *sf) {
+     logger->log("\r\n\r\nExecuting read()");
+     logger->turnLEDPurple();
+          
+     // DEBUG
+     logger->log("Read: reading: %s from %s",record_id,object_name);
+     
+     // read...
+     MbedJSONValue response = sf->readRecord(object_name,record_id);
+     
+     // display the result
+     char *result = (char *)response.serialize().c_str();
+     if (result != NULL && strlen(result) > 0 && strcmp(result,"null") != 0) {
+        // save off the token if we succeeded
+        logger->log("Read: result: %s",result);
+        logger->log("Read: http_code=%d",sf->httpResponseCode());
+     }
+     else {
+        // failure
+        logger->log("Read: FAILED http_code=%d",sf->httpResponseCode());
+     }
+     
+     logger->turnLEDGreen();
+ }
+ 
+ void Test_read_by_external_id_and_value(ErrorHandler *logger,SalesforceInterface *sf) {
+     logger->log("\r\n\r\nExecuting Read(externalID)...");
+     logger->turnLEDPurple();
+          
+     // DEBUG
+     logger->log("Read: reading: %s from %s with value %s",object_name,external_id_field_name,external_id_field_value);
+     
+     // read (external ID)...
+     MbedJSONValue response = sf->readRecord(object_name,external_id_field_name,external_id_field_value);
+     
+     // display the result
+     char *result = (char *)response.serialize().c_str();
+     if (result != NULL && strlen(result) > 0 && strcmp(result,"null") != 0) {
+        // save off the token if we succeeded
+        logger->log("Read(externalID): result: %s",result);
+        logger->log("Read(externalID): http_code=%d",sf->httpResponseCode());
+     }
+     else {
+        // failure
+        logger->log("Read(externalID): FAILED http_code=%d",sf->httpResponseCode());
+     }
+     
+     logger->turnLEDGreen();
+ }
+ 
+ void Test_update(ErrorHandler *logger,SalesforceInterface *sf) {
+     logger->log("\r\n\r\nExecuting update()");
+     logger->turnLEDPurple();
+     
+     // update am existing record - assume "name" is the proper key for the record you wish to update...
+     MbedJSONValue changed_record;
+     changed_record["name"] = updated_account_name;
+     
+     // DEBUG
+     logger->log("Update: updated record: %s",changed_record.serialize().c_str());
+     
+     // update...
+     bool updated = sf->updateRecord(object_name,record_id,changed_record);
+     
+     // display the result
+     if (updated) {
+        // SUCCESS
+        logger->log("Update: successful! http_code=%d",sf->httpResponseCode());
+     }
+     else {
+        // failure
+        logger->log("Update: FAILED http_code=%d",sf->httpResponseCode());
+     }
+     logger->turnLEDGreen();
+ }
+ 
+ void Test_upsert(ErrorHandler *logger,SalesforceInterface *sf) {
+     logger->log("\r\n\r\nExecuting upsert()");
+     logger->turnLEDPurple();
+     
+     // update am existing record - assume "name" is the proper key for the record you wish to update...
+     MbedJSONValue changed_record;
+     changed_record["name"] = updated_account_name;
+     
+     // DEBUG
+     logger->log("Upsert: upserted record: %s",changed_record.serialize().c_str());
+     
+     // Upsert...
+     bool updated = sf->upsertRecord(object_name,external_id_field_name,external_id_field_value,changed_record);
+     
+     // display the result
+     if (updated) {
+        // SUCCESS
+        logger->log("Upsert: successful! http_code=%d",sf->httpResponseCode());
+     }
+     else {
+        // failure
+        logger->log("Upsert: FAILED http_code=%d",sf->httpResponseCode());
+     }
+     logger->turnLEDGreen();
+ }
+ 
+ void Test_delete(ErrorHandler *logger,SalesforceInterface *sf) {
+     logger->log("\r\n\r\nExecuting delete()");
+     logger->turnLEDPurple();
+     
+     // DEBUG
+     logger->log("Delete: deleting: %s from %s",record_id,object_name);
+     
+     // delete...
+     bool deleted = sf->deleteRecord(object_name,record_id);
+     
+     // display the result
+     if (deleted) {
+        // SUCCESS
+        logger->log("Delete: successful! http_code=%d",sf->httpResponseCode());
+     }
+     else {
+        // failure
+        logger->log("Delete: FAILED http_code=%d",sf->httpResponseCode());
+     }
+     
+     logger->turnLEDGreen();
+ }
+ 
+ void Test_reset_auth(ErrorHandler *logger,SalesforceInterface *sf) {
+     logger->log("\r\n\r\nForcing API to reset OAUTH token and Salesforce Token...");
+     logger->turnLEDPurple();
+     sf->resetSalesforceToken();
+     logger->turnLEDGreen();
+ }
+ 
+ // *************** Test Cases ************************
+ 
+ // Main Task...
+ void mainTask(void const *v) {
+        
+    // create our object instances 
+    ErrorHandler logger(&pc,NULL);    
+    SalesforceInterface *sf = NULL;
+    
+    // announce
+    logger.log("\r\n\r\nARM Salesforce Interface Testharness v%s",APP_VERSION);
+    logger.turnLEDBlue();
+    
+    // initialize Ethernet
+    logger.log("Initializing Ethernet...");
+    ethernet.init();
+    
+    // get a DHCP address and bring the network interface up
+    logger.log("Getting IP Address...");
+    logger.turnLEDOrange();
+    if (ethernet.connect() == 0) {
+        // log our IP address (DHCP)
+        logger.log("IP Address: %s",ethernet.getIPAddress());
+        
+        // allocate the Salesforce.com interface
+        logger.log("Allocating Saleforce.com interface...");
+        sf = new SalesforceInterface(&logger,&http);
+        
+        // set our Salesforce.com credentials
+        sf->setCredentials(username,password,client_id,client_secret);
+        
+        // *************** BEGIN TEST CASES *****************        
+
+        // configuration for the test cases        
+        object_name             = "Account";       // use the account object
+        account_name            = "ARM";           // add this record (name)
+        updated_account_name    = "ARM Holdings";  // update the existing record's name to this
+        external_id_field_name  = "Device_c";      // External ID field name
+        external_id_field_value = "ABC123";        // External ID field value
+        RESET_SML_BUFFER(record_id);               // buffer for the record's token
+        
+        // Perform a Create
+        Test_create(&logger,sf);
+                
+        // Perform a Read
+        Test_read(&logger,sf);
+        
+        // Perform a Query
+        Test_query(&logger,sf,"SELECT Id,Name FROM Account LIMIT 5");
+        
+        // Perform an Update
+        Test_update(&logger,sf);
+                
+        // Perform a second Read to visually confirm the update above...
+        Test_read(&logger,sf);
+        
+        // Perform an Upsert
+        Test_update(&logger,sf);
+        
+        // Perform a read of the external ID'ed specified by a given value
+        Test_read_by_external_id_and_value(&logger,sf);
+        
+        // force the API to re-acquire the OAUTH token and Salesforce Token 
+        Test_reset_auth(&logger,sf);
+        
+        // Perform a Read (should re-acquire the OAUTH token and Salesforce Token)
+        Test_read(&logger,sf);
+        
+        // Perform a Delete
+        Test_delete(&logger,sf);
+                
+        // reset the record token buffer
+        // RESET_SML_BUFFER(record_id);
+        
+        // Perform a Read - should error out
+        Test_read(&logger,sf);
+                        
+        // reset the record token buffer
+        RESET_SML_BUFFER(record_id);
+        
+        // *************** BEGIN TEST CASES *****************      
+        
+        // entering main loop
+        logger.log("All tests complete...\r\nExiting...");
+        logger.turnLEDBlue();
+        exit(0);
+     }
+     else {
+         logger.log("No Network... Exiting...");
+         logger.turnLEDRed();
+         exit(1);
+     }     
+  }
+  
+  // main entry
+  int main() {
+     Thread workerTask(mainTask, NULL, osPriorityNormal, STACK_SIZE);
+     while (true) {
+        Thread::wait(10*WAIT_TIME_MS);
+     }
+  }
+ 
  * @endcode
  *
  */       
@@ -439,25 +501,36 @@
         
         /**
         Salesforce.com API record read method to read a record within a salesforce.com object
-        @param object_name name of the salesforce.com object to create the record in (i.e. "Account")
-        @param record_id salesforce.com token of the record instance to read 
+        @param object_name name of the salesforce.com object to read the record in (i.e. "Account")
+        @param record_id salesforce.com id of the record instance to read 
+        @param record_value salesforce.com id value of the record instance to read (for external ID usage - default is NULL for non-external IDs)
         @return MbedJSONValue structure with the results of the read operation in JSON format
         */
-        MbedJSONValue readRecord(char *object_name,char *record_id);
+        MbedJSONValue readRecord(char *object_name,char *record_id,char *record_value = NULL);
         
         /**
         Salesforce.com API record update method to update a record within a salesforce.com object
-        @param object_name name of the salesforce.com object to create the record in (i.e. "Account")
-        @param record_id salesforce.com token of the record instance to read 
+        @param object_name name of the salesforce.com object to update the record in (i.e. "Account")
+        @param record_id salesforce.com id of the record instance to read 
         @param record MbedJSONValue instance with updated data for the record
         @return true - success, false - failure
         */
         bool updateRecord(char *object_name,char *record_id,MbedJSONValue &record);
         
         /**
+        Salesforce.com API record upsert (update/insert) method to update a record within a salesforce.com object for External IDs
+        @param object_name name of the salesforce.com External object to upsert the record in (i.e. "FooBar_c")
+        @param external_id_field_name salesforce.com id of the External record instance to upsert 
+        @param external_id_field_value salesforce.com id value of the External record instance to upsert 
+        @param record MbedJSONValue instance with updated data for the record
+        @return true - success, false - failure
+        */
+        bool upsertRecord(char *object_name,char *external_id_field_name,char *external_id_field_value,MbedJSONValue &record);
+        
+        /**
         Salesforce.com API record delete method to delete a record within a salesforce.com object
-        @param object_name name of the salesforce.com object to create the record in (i.e. "Account")
-        @param record_id salesforce.com token of the record instance to delete 
+        @param object_name name of the salesforce.com object to delete the record in (i.e. "Account")
+        @param record_id salesforce.com id of the record instance to delete 
         @return true - success, false - failure
         */
         bool deleteRecord(char *object_name,char *record_id);
@@ -477,10 +550,13 @@
         char *createRecord(char *object_name,char *json_data,char *output_buffer,int output_buffer_length);
         
         // READ: a specific record in Salesforce.com
-        char *readRecord(char *object_name,char *record_id,char *output_buffer,int output_buffer_length);
+        char *readRecord(char *object_name,char *record_id,char *record_value,char *output_buffer,int output_buffer_length);
         
         // UPDATE: a specific record in Salesforce.com
         bool updateRecord(char *object_name,char *record_id,char *json_data);
+        
+        // UPSERT: update/insert a specific External record in Salesforce.com
+        bool upsertRecord(char *object_name,char *external_id_field_name,char *external_id_field_value,char *json_data);
                             
         // raw invocation of REST calls into Salesforce.com
         char *invoke(const char *url,char *output_buffer,int output_buffer_length);                                                                                                        // defaults to GET