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

Files at this revision

API Documentation at this revision

Comitter:
ansond
Date:
Tue Sep 23 20:26:24 2014 +0000
Parent:
15:89044c68ad36
Child:
17:6c774354b599
Commit message:
added external ID support

Changed in this revision

SalesforceInterface.cpp Show annotated file Show diff for this revision Revisions of this file
SalesforceInterface.h Show annotated file Show diff for this revision Revisions of this file
--- a/SalesforceInterface.cpp	Tue Sep 23 17:35:34 2014 +0000
+++ b/SalesforceInterface.cpp	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,
@@ -298,9 +298,9 @@
  }
 
  // READ: a specific record in Salesforce.com
- MbedJSONValue SalesforceInterface::readRecord(char *object_name,char *record_id) {
+ MbedJSONValue SalesforceInterface::readRecord(char *object_name,char *record_id,char *record_value) {
     ALLOC_BUFFER(output_buffer);
-    char *reply = this->readRecord(object_name,record_id,output_buffer,MAX_BUFFER_LENGTH);
+    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; 
@@ -311,6 +311,11 @@
     return this->updateRecord(object_name,record_id,(char *)record.serialize().c_str());
  }
  
+ // 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) {
+     return this->upsertRecord(object_name,external_id_field_name,external_id_field_value,(char *)record.serialize().c_str());
+ }
+ 
  // CREATE: a record in Salesforce.com
  char *SalesforceInterface::createRecord(char *object_name,char *json_data,char *output_buffer,int output_buffer_length) {
      // parameter check
@@ -347,7 +352,7 @@
  }
 
  // READ: a specific record in Salesforce.com
- char *SalesforceInterface::readRecord(char *object_name,char *record_id,char *output_buffer,int output_buffer_length) {
+ 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
@@ -366,6 +371,12 @@
                 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());
                 
@@ -434,6 +445,62 @@
      }
      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) {  
+     // 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
+                ALLOC_SML_BUFFER(output_buffer);
+                char *reply = this->invoke(str_url.c_str(),json_data,strlen(json_data)+1,output_buffer,MAX_SMALL_BUFFER_LENGTH);
+                
+                // DEBUG
+                DEBUG("upsertRecord: http status=%d",this->httpResponseCode());
+                
+                // return our status
+                if (this->httpResponseCode() == 204) return true;
+                return false;
+            }
+         }
+         else {
+             // dont have a valid salesforce token
+             this->logger()->log("upsertRecord: error - no valid salesforced token was found...");
+         }
+     }
+     else {
+         // invalid or NULL parameters
+         this->logger()->log("upsertRecord: error - invalid or NULL parameters...");
+     }
+     return false;  
+ }
   
  // DELETE: a specific record in Salesforce.com
  bool SalesforceInterface::deleteRecord(char *object_name,char *record_id) {
--- 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