Skip to content

Instantly share code, notes, and snippets.

@danwatt
Created February 14, 2012 16:21
Show Gist options
  • Save danwatt/1827874 to your computer and use it in GitHub Desktop.
Save danwatt/1827874 to your computer and use it in GitHub Desktop.
Wrapper for the SalesForce REST API, for ColdFusion 9
/**
Copyright (C) 2012 Daniel Watt
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
/*
Not implemented/tested yet:
SOSL Search
SObject Blob Retrieve
Any API version other than 22 (most everything here works against 21 though)
Apex REST (http://www.salesforce.com/us/developer/docs/apex_rest/api_apex_rest.pdf)
* Probably other things
* Author: Dan Watt
Example usage:
private void function createTask(required string body, required string whatId, required string whoId) {
local.sfTask = {
'Subject' = 'Email',
'WhatId' = arguments.whatId,
'WhoId' = arguments.whoId,
'Priority' = 'Normal',
'Description' = arguments.body,
'Status' = 'Completed'
};
local.taskId = variables.salesforceApi.createTask(local.sfTask);
}
private string function createOpportunityContactRole(required string sfOpportunityId, required string sfContactId) {
local.OpportunityContactRole = {
'OpportunityId'= arguments.sfOpportunityId,
'ContactId'= arguments.sfContactId,
'IsPrimary'= 'true',
'Role'= 'Customer'
};
variables.salesforceApi.createOpportunityContactRole(local.OpportunityContactRole);
}
*/
component hint="Wrapper for the SalesForce RESTful API" {
variables.token = '';
variables.apiVersion = '22.0';
/*
* jsonParser needs two methods:
* public function String toJSON(any);
* public function any toObject(String);
*/
public any function init(required any jsonParser) {
variables.json = arguments.jsonParser;
variables.salesforceInstance = 'http://_your_salesforce_instance';
variables.clientId = 'your_client_id';
variables.clientSecret = 'your_client_secret';
variables.username = 'your_sf_username';
variables.password = 'your_sf_password';
return this;
}
private function gatherResponseString(required any httpresult) {
writeDump(var=arguments.httpresult,output='console');
if (isbinary(arguments.httpresult.filecontent)) {
return trim(toString(arguments.httpresult.filecontent,'UTF-8'));
}
return trim(httpresult.FileContent);
}
public boolean function logIn(boolean force = false) {
if (variables.token EQ '' OR arguments.force) {
//TODO: Smaller timeout, retry loop
lock scope="application" type="exclusive" timeout="60" {
if (variables.token EQ '' OR arguments.force) {
local.http = new Http(url=variables.salesforceInstance & '/services/oauth2/token',method='post');
local.http.addParam(type='formField',name='grant_type', value='password');
local.http.addParam(type='formField',name='client_id', value=variables.CLIENTID);
local.http.addParam(type='formField',name='client_secret', value=variables.CLIENTSECRET);
local.http.addParam(type='formField',name='username', value=variables.USERNAME);
local.http.addParam(type='formField',name='password', value=variables.PASSWORD);
local.http.addParam(type='formField',name='format', value='json');
local.httpSendResult = local.http.send();
local.httpResult = httpSendResult.getPrefix();
if (StructKeyExists(local.httpResult, "responseHeader")
&& StructKeyExists(local.httpResult.responseHeader, "status_code")
&& local.httpResult.responseHeader.status_code EQ 200) {
local.stringResult = gatherResponseString(local.httpResult);
local.json = variables.json.toObject(local.stringResult);
variables.token = local.json.access_token;
} else {
variables.token = '';
local.errorString = gatherResponseString(local.httpResult);
if (StructKeyExists(local.httpResult, "ErrorDetail")) {
local.errorString &= ", detail: " & local.httpResult.ErrorDetail;
}
throw (message='Unable to authenticate to SalesForce: ' & local.errorString, type='salesforce.loginerror');
}
}
}
}
return true;
}
public String function getToken() {
return variables.token;
}
/**
* @returns Struct of JSON data from Salesforce, if object was found
* @throws salesforce.objectNotFound otherwise
**/
public any function getObject(required string sfid, required string type, array fields) {
local.url=variables.salesforceInstance & '/services/data/v' & variables.apiVersion &'/sobjects/'&arguments.type&'/' & arguments.sfid;
if (NOT isNull(fields) AND ArrayLen(fields) GT 0) {
local.url = local.url & '?fields=' & ArrayToList(fields);
}
return makeJsonGetCall(url=local.url,errorMessage='Unable to find ' & arguments.type & ' with sfid ' & arguments.sfid);
}
private any function makeJsonGetCall(required string url, required string errorMessage) {
local.httpResult = makeCall(arguments.url,'GET',{});
if (local.httpResult.responseHeader.status_code EQ 200) {
local.fromJson = variables.json.toObject(gatherResponseString(local.httpResult));
return local.fromJson;
} else {
throw (message=arguments.errorMessage & ' (status: '& local.httpResult.responseHeader.status_code &')',type='salesforce.objectNotFound') ;
}
}
/**
* @returns Struct of JSON data from Salesforce, if object was found
* @throws salesforce.objectNotFound otherwise
**/
public any function getObjectExternalId(required string type, required String field, required String value, array fields) {
local.url=variables.salesforceInstance & '/services/data/v' & variables.apiVersion &'/sobjects/'&arguments.type&'/' & arguments.field &'/'&arguments.value;
if (NOT isNull(fields) AND ArrayLen(fields) GT 0) {
local.url = local.url & '?fields=' & ArrayToList(fields);
}
return makeJsonGetCall(url=local.url,errorMessage='Unable to find ' & arguments.type & ' with external id ' & arguments.value);
}
public any function describeObject(required string type) {
local.url=variables.salesforceInstance & '/services/data/v' & variables.apiVersion &'/sobjects/'&arguments.type&'/describe/';
return makeJsonGetCall(url=local.url,errorMessage='Unable to find ' & arguments.type);
}
public any function simpleDescribeObject(required string type) {
local.fullDesc = describeObject(arguments.type);
local.simpleDesc = {
'fields'=[],
'name'=local.fullDesc.name,
'label'=local.fullDesc.label,
'childRelationships' = local.fullDesc.childRelationships,
'recordTypeInfos' = local.fullDesc.recordTypeInfos
};
for (local.i = 1; local.i LTE Arraylen(local.fullDesc.fields); local.i++) {
local.fullField = local.fullDesc.fields[local.i];
local.field = {
'type'= local.fullField.type,
'name'= local.fullField.name,
'label'= local.fullField.label
};
if (arrayLen(local.fullField.picklistValues) GT 0) {
local.field['picklistValues']= local.fullField.picklistValues;
}
ArrayAppend(local.simpleDesc.fields,local.field);
}
return local.simpleDesc;
}
public any function metadata(required string type) {
local.url=variables.salesforceInstance & '/services/data/v' & variables.apiVersion &'/sobjects/'&arguments.type&'/';
return makeJsonGetCall(url=local.url,errorMessage='Unable to find ' & arguments.type);
}
private struct function copyStructAndRemoveSalesForceSpecialFields(required struct obj){
local.toSave = structcopy(arguments.obj);
if (structkeyexists(local.toSave,'attributes')) {
Structdelete(local.toSave,'attributes');
}
if (structkeyexists(local.toSave,'Id')) {
Structdelete(local.toSave,'Id');
}
return local.toSave;
}
private any function makeCall(required string url, required string method,required struct params, any attempt = 0) {
logIn(force=(arguments.attempt GT 0));
local.http = new Http(url=arguments.url,method=arguments.method);
for (paramType in params) {
for (paramKey in params[paramType]) {
if (paramType NEQ 'header' or paramType neq 'Authorization') {
local.http.addParam(type=paramType,name=paramKey,value=params[paramType][paramKey]);
}
}
}
local.http.setGetAsBinary('YES');
local.http.addParam(type='header',name='Authorization',value='OAuth ' & variables.token);
local.httpSendResult = local.http.send().getPrefix();
if (local.httpSendResult.responseHeader.status_code EQ 401) {
if (NOT isdefined('arguments.attempt') or arguments.attempt EQ 0) {
return makeCall(arguments.url,arguments.method,arguments.params,attempt + 1);
} else {
throw (message = 'Unable to log into SalesForce: ' & local.httpSendResult.responseHeader.status_code & '('&gatherResponseString(local.httpSendResult)&')',detail=gatherResponseString(local.httpSendResult),type='salesforce.loginFailure');
}
} else {
return local.httpSendResult;
}
}
private boolean function patchObject(required string sfid, required string type, required struct obj) {
local.params = {header={'Content-Type'='application/json'},body={'body' = variables.json.toJSON(copyStructAndRemoveSalesForceSpecialFields(arguments.obj))}};
local.url = variables.salesforceInstance & '/services/data/v' & variables.apiVersion &'/sobjects/' & arguments.type & '/' & arguments.sfid &'?_HttpMethod=PATCH';
local.httpResult = makeCall(url=local.url,method='POST',params=local.params);
if (local.httpResult.responseHeader.status_code EQ 200 OR local.httpResult.responseHeader.status_code EQ 204) {
return true;
} else {
throw (message = 'Unhandled status code from server: ' & local.httpResult.responseHeader.status_code & '('&gatherResponseString(local.httpResult)&')',detail=gatherResponseString(local.httpResult),type='salesforce.objectNotFound');
}
}
private string function upsertObject(required string type,required string externalField, required struct obj) {
local.externalValue = arguments.obj[arguments.externalField];
local.tempStruct = copyStructAndRemoveSalesForceSpecialFields(arguments.obj);
Structdelete(local.tempStruct,arguments.externalField);
local.params = {header={'Content-Type'='application/json'},body={'body' = variables.json.toJSON(local.tempStruct)}};
local.url = variables.salesforceInstance & '/services/data/v' & variables.apiVersion &'/sobjects/' & arguments.type & '/' & arguments.externalField &'/' & local.externalValue &'?_HttpMethod=PATCH';
local.httpResult = makeCall(url=local.url,method='POST',params=local.params);
if (local.httpResult.responseHeader.status_code EQ 204) {
//204 doesnt save an ID, anywhere, including a location header
return getObjectExternalId(arguments.type, arguments.externalField, local.externalValue, ['Id']).Id;
} else if (local.httpResult.responseHeader.status_code EQ 201 ) {
local.obj = variables.json.toObject(gatherResponseString(local.httpResult));
if (local.obj.success) {
return local.obj.id;
} else {
throw (message='Unhandled response from server: ' + gatherResponseString(local.httpResult),type='salesforce.unknownCreateError');
}
} else {
throw (message = 'Unhandled status code from server: ' & local.httpResult.responseHeader.status_code & '('&gatherResponseString(local.httpResult)&')',detail=gatherResponseString(local.httpResult),type='salesforce.objectNotFound');
}
}
public String function createSfObject(required string type, required struct obj) {
local.url = variables.salesforceInstance & '/services/data/v' & variables.apiVersion &'/sobjects/' & arguments.type & '/';
local.toSave = copyStructAndRemoveSalesForceSpecialFields(arguments.obj);
local.httpResult = makeCall(local.url,'POST',{header={'Content-Type'='application/json'},body={'body'=variables.json.toJSON(local.toSave)}});
if (local.httpResult.responseHeader.status_code EQ 201) {
local.obj = variables.json.toObject(gatherResponseString(local.httpResult));
if (local.obj.success) {
return local.obj.id;
} else {
throw (message='Unhandled response from server: ' + gatherResponseString(local.httpResult),type='salesforce.unknownCreateError');
}
} else {
throw (message = 'Unhandled status code from server: ' & local.httpResult.responseHeader.status_code & '('&gatherResponseString(local.httpResult)&')',detail=gatherResponseString(local.httpResult),type='salesforce.objectNotFound');
}
}
private boolean function deleteObject(required string sfid, required string type) {
local.url = variables.salesforceInstance & '/services/data/v' & variables.apiVersion &'/sobjects/' & arguments.type & '/' & arguments.sfid;
local.httpResult = makeCall(local.url,'DELETE',{});
if (local.httpResult.responseHeader.status_code EQ 200 OR local.httpResult.responseHeader.status_code EQ 204) {
return true;
} else {
throw (message = 'Unhandled status code from server: ' & local.httpResult.responseHeader.status_code & '('&gatherResponseString(local.httpResult)&') while deleting ' & type & ' ' & sfid,detail=gatherResponseString(httpResult),type='salesforce.objectNotFound');
}
}
public any function queryObjects(required string queryString) {
local.records = queryObjectsAsStructs(queryString);
if (Arraylen(local.records) LT 1) {
return QueryNew('');
}
local.first = structcopy(local.records[1]);
structdelete(local.first,'attributes');
local.keys = 'type,url,' & arraytolist(structkeyarray(local.first));
local.rst = QueryNew(local.keys);
QueryAddRow(local.rst,Arraylen(local.records));
for (i = 1; i LTE Arraylen(local.records); i++) {
local.record =local.records[i];
local.keys = Arraytolist(structkeyarray(record));
for (j = 1; j LTE Listlen(keys); j++) {
local.key = Listgetat(keys,j);
if ('attributes' EQ key) {
QuerySetCell(local.rst,'type',record.attributes.type,i);
QuerySetCell(local.rst,'url',record.attributes.url,i);
} else {
QuerySetCell(local.rst,key,record[key],i);
}
}
}
return local.rst;
}
private any function queryObjectsAsStructsWithRetry(required string queryString) {
local.url=variables.salesforceInstance & '/services/data/v' & variables.apiVersion &'/query/?q=' & urlencodedformat(arguments.queryString);
local.httpResult = makeCall(local.url,'GET',{header={'Content-Type'='application/json'}});
if (local.httpResult.responseHeader.status_code EQ 200 OR local.httpResult.responseHeader.status_code EQ 204) {
local.results = variables.json.toObject(gatherResponseString(local.httpResult));
local.records = local.results.records;
while (Structkeyexists(local.results,'nextRecordsUrl')) {
local.httpResult = makeCall(local.results.nextRecordsUrl,'GET',{header={'Content-Type'='application/json'}});
if (local.httpResult.responseHeader.status_code EQ 200 OR local.httpResult.responseHeader.status_code EQ 204) {
local.results = variables.json.toObject(gatherResponseString(local.httpResult));
local.records.addAll(local.results.records);
} else {
throw (message = 'Unhandled status code from server: ' & local.httpResult.responseHeader.status_code & '('&gatherResponseString(local.httpResult)&')',detail=gatherResponseString(local.httpResult),type='salesforce.queryError');
}
}
return local.records;
} else {
throw (message = 'Unhandled status code from server: ' & local.httpResult.responseHeader.status_code & '('&gatherResponseString(local.httpResult)&')',detail=gatherResponseString(local.httpResult),type='salesforce.queryError');
}
return variables.json.toObject(gatherResponseString(local.httpResult)).records;
}
public any function queryObjectsAsStructs(required string queryString) {
return queryObjectsAsStructsWithRetry(arguments.queryString,0);
}
public any function listObjects() {
local.url=variables.salesforceInstance & '/services/data/v' & variables.apiVersion &'/sobjects/';
return makeJsonGetCall(url=local.url,errorMessage='Unhandled status code from server');
}
public any function fetchBlob(required string ObjectType, required String field, required string sfid) {
local.url=variables.salesforceInstance & '/services/data/v' & variables.apiVersion &'/sobjects/' & arguments.ObjectType & '/' & arguments.sfid & '/' & arguments.field;
local.httpResult = makeCall(local.url,'GET',{});
if (local.httpResult.responseHeader.status_code EQ 200) {
return local.httpResult.Filecontent.toByteArray();
}
throw (message = 'Unhandled status code from server: ' & local.httpResult.responseHeader.status_code,type='salesforce.objectNotFound');
}
public any function OnMissingMethod(required String MissingMethodName, required struct MissingMethodArguments) {
local.args = {};
if (find('get',arguments.MissingMethodName) EQ 1) {
local.args.type=right(arguments.MissingMethodName,len(arguments.MissingMethodName)-3);
local.args.sfid = arguments.MissingMethodArguments[1];
local.args.fields = [];
if (ArrayLen(arguments.MissingMethodArguments) GT 1) {
local.args.fields = arguments.MissingMethodArguments[2];
}
return getObject(argumentCollection = local.args);
} else if (find('update',arguments.MissingMethodName) EQ 1) {
local.args.type=right(arguments.MissingMethodName,len(arguments.MissingMethodName)-6);
local.args.sfid = arguments.MissingMethodArguments[1];
local.args.obj =arguments.MissingMethodArguments[2];
return patchObject(argumentCollection = local.args);
} else if (find('upsert',arguments.MissingMethodName) EQ 1) {
local.args.type=right(arguments.MissingMethodName,len(arguments.MissingMethodName)-6);
local.args.obj =arguments.MissingMethodArguments[1];
local.args.externalField = arguments.MissingMethodArguments[2];
return upsertObject(argumentCollection = local.args);
} else if (find('create',arguments.MissingMethodName) EQ 1) {
local.args.type=right(arguments.MissingMethodName,len(arguments.MissingMethodName)-6);
local.args.obj = arguments.MissingMethodArguments[1];
return createSfObject(argumentCollection = local.args);
} else if (find('delete',arguments.MissingMethodName) EQ 1) {
local.args.type=right(arguments.MissingMethodName,len(arguments.MissingMethodName)-6);
local.args.sfid = arguments.MissingMethodArguments[1];
return deleteObject(argumentCollection = local.args);
} else if (find('describe',arguments.MissingMethodName) EQ 1) {
local.args.type=right(arguments.MissingMethodName,len(arguments.MissingMethodName)-8);
return describeObject(argumentCollection=local.args);
}
}
}
@malik415
Copy link

malik415 commented Aug 9, 2014

Thanks for this code Dan.

For the jsonparser piece, do you have something you can share that would work with this SalesforceAPIWrapper.cfc? I noticed the init() method accepted an argument for a jsonparser

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment