Created
February 14, 2012 16:21
-
-
Save danwatt/1827874 to your computer and use it in GitHub Desktop.
Wrapper for the SalesForce REST API, for ColdFusion 9
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
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); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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