Skip to content

Instantly share code, notes, and snippets.

@jeremyhalliwell
Forked from danwatt/SalesforceApiWrapper.cfc
Last active June 25, 2020 20:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jeremyhalliwell/3be545da6f4ebd07d741 to your computer and use it in GitHub Desktop.
Save jeremyhalliwell/3be545da6f4ebd07d741 to your computer and use it in GitHub Desktop.
component hint="wrapper for Salesforce REST 2.0 API" {
pageEncoding "utf-8";
/**
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.
*/
// adapted from https://gist.github.com/danwatt/1827874
variables.token = '';
public any function init(
required string apiVersion
,required string callbackurl
,required string clientId
,required string clientSecret
,string logindomain = 'https://login.salesforce.com'
,boolean logCalls = false
,required string password
,string pathLogs = ""
,string salesforceInstance = ''
,required string securitytoken
,required string username
) {
variables.apiVersion = arguments.apiVersion;
variables.callbackurl = arguments.callbackurl;
variables.clientId = arguments.clientId;
variables.clientSecret = arguments.clientSecret;
variables.logCalls = arguments.logCalls;
variables.logindomain = arguments.logindomain;
variables.password = arguments.password;
variables.pathLogs = arguments.pathLogs;
variables.salesforceInstance = arguments.salesforceInstance;
variables.securitytoken = arguments.securitytoken;
variables.username = arguments.username;
return this;
}
private function gatherResponseString( required any httpresult ) {
if( Isbinary( httpresult.filecontent ) )
return Trim( ToString( httpresult.filecontent,'UTF-8' ) );
return Trim( httpresult.FileContent );
}
public void function logIn( boolean force=false ) {
if( variables.token != '' && !force ) return;
local.url = variables.logindomain & '/services/oauth2/token';
local.method = "post";
lock scope="application" type="exclusive" timeout="60" {
http method=local.method timeout=10 url=local.url result="httpResult" {
httpparam type='formField' name='grant_type' value='password';
httpparam type='formField' name='client_id' value=variables.clientid;
httpparam type='formField' name='client_secret' value=variables.clientsecret;
httpparam type='formField' name='username' value=variables.username;
httpparam type='formField' name='password' value=variables.password & variables.securitytoken;
httpparam type='formField' name='format' value='json';
}
if( httpResult.keyExists( "responseHeader" )
&& httpResult.responseHeader.keyExists( "status_code" )
&& httpResult.responseHeader.status_code == 200 ) {
var stringResult = gatherResponseString( httpResult );
var json = DeserializeJSON( stringResult );
variables.salesforceInstance = json.instance_url;
variables.token = json.access_token;
logCall( method=local.method,url=local.url );
} else {
variables.token = '';
var errorString = gatherResponseString( httpResult );
if( httpResult.keyExists( "ErrorDetail" ) )
errorString &= ", detail: " & httpResult.ErrorDetail;
throw( message='Unable to authenticate to SalesForce: ' & errorString,type='salesforce.loginerror' );
}
}
}
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 ) {
var url = '/services/data/v' & variables.apiVersion &'/sobjects/'&type&'/' & sfid;
if( !isNull( fields ) && ArrayLen( fields ) > 0)
local.url = local.url & '?fields=' & ArrayToList( fields );
return makeJsonGetCall( url=local.url,errorMessage='Unable to find ' & type & ' with sfid ' & sfid );
}
private any function makeJsonGetCall( required string url,required string errorMessage ) {
var httpResult = makeCall( arguments.url,'GET',{} );
if( httpResult.responseHeader.status_code == 200 ) {
var fromJson = DeserializeJSON( gatherResponseString( httpResult ) );
return fromJson;
} else {
// WriteDump( var="#httpresult#" abort=true );
throw( message=errorMessage & ' (status: '& 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 ) {
var url='/services/data/v' & variables.apiVersion &'/sobjects/'&type&'/' & field &'/'&value;
if( !isNull( fields ) && ArrayLen( fields ) > 0 )
local.url = local.url & '?fields=' & ArrayToList( fields );
return makeJsonGetCall( url=local.url,errorMessage='Unable to find ' & type & ' with external id ' & value );
}
public any function getUpdated( required string type,date end="#Now()#",required date start ) {
start = DateTimeFormat( start,"ISO8601" );
end = DateTimeFormat( end,"ISO8601" );
var queryString = "start=" & start & "&end=" & end;
var url='/services/data/v' & variables.apiVersion & '/sobjects/' & type &'/updated/?' & queryString;
//WriteDump( var="#local.url#" abort=true );
return makeJsonGetCall( url=local.url,errorMessage='Unable to find ' & type );
}
public any function describeObject( required string type ) {
var url='/services/data/v' & variables.apiVersion &'/sobjects/'&type&'/describe/';
return makeJsonGetCall( url=local.url,errorMessage='Unable to find ' & type );
}
public any function simpleDescribeObject( required string type ) {
var fullDesc = describeObject( type );
var simpleDesc = {
'fields'=[],
'name'=fullDesc.name,
'label'=fullDesc.label,
'childRelationships' = fullDesc.childRelationships,
'recordTypeInfos' = fullDesc.recordTypeInfos
};
for( var i=1; i <= Arraylen( fullDesc.fields );i++ ) {
var fullField = fullDesc.fields[ i ];
var field = {
'label'= fullField.label
,'name'= fullField.name
,'type'= fullField.type
};
if( ArrayLen( fullField.picklistValues ) > 0 )
field[ 'picklistValues' ]= fullField.picklistValues;
ArrayAppend( simpleDesc.fields,field );
}
return simpleDesc;
}
public any function metadata( required string type ) {
var url='/services/data/v' & variables.apiVersion &'/sobjects/'& type&'/';
return makeJsonGetCall( url=local.url,errorMessage='Unable to find ' & type );
}
private struct function copyStructAndRemoveSalesForceSpecialFields( required struct obj ){
var toSave = Structcopy( obj );
if( Structkeyexists( toSave,'attributes' ) )
Structdelete( toSave,'attributes' );
if( Structkeyexists( toSave,'Id' ) )
Structdelete( toSave,'Id' );
return toSave;
}
private any function makeCall( required string url,required string method,required struct params,any attempt=0 ) {
logIn( force=( attempt > 0) );
arguments.url = variables.salesforceInstance & arguments.url;
http url=arguments.url getasbinary="yes" method=method timeout=10 result="httpResult" {
for( var paramType in params ) {
for( var paramKey in params[ paramType ] ) {
if( paramType != 'header' || paramType != 'Authorization') {
httpparam type=paramType name=paramKey value=params[ paramType ][ paramKey ];
}
}
}
httpparam type='header' name='Authorization' value='OAuth ' & variables.token;
}
if( IsDefined( 'httpResult.responseHeader.status_code' ) && httpResult.responseHeader.status_code == 401 ) {
if( !IsNull( attempt ) || attempt == 0)
return makeCall( arguments.url,method,params,attempt + 1 );
else
throw( message='Unable to log into SalesForce: ' & httpResult.responseHeader.status_code & '('&gatherResponseString( httpResult )&')',detail=gatherResponseString( httpResult ),type='salesforce.loginFailure' );
} else {
logCall( argumentCollection=arguments );
return httpResult;
}
}
private void function logCall( any attempt=0,required string method,struct params={},required string url ) {
if( !variables.logCalls ) return;
var file = "#variables.pathLogs#salesforce-api-calls-#DateFormat( Now(),"yyyy-mm-dd" )#.log";
var data = [
Date: DateTimeFormat( Now(),'yyyy-mm-dd HH:nn' )
,endpoint: UrlDecode( arguments.url )
,method: arguments.method
// ,params: arguments.params
,attempt: arguments.attempt
];
data = SerializeJSON( data );
data &= NewLine();
FileAppend( data=data,file=file );
}
private boolean function patchObject( required string sfid,required string type,required struct obj ) {
obj = copyStructAndRemoveSalesForceSpecialFields( obj );
obj = SerializeJSON( var=obj );
//WriteDump( var="#obj#",abort=true );
var params = { header={ 'Content-Type' = 'application/json' },body={ 'body' = obj } };
var url = '/services/data/v' & variables.apiVersion &'/sobjects/' & type & '/' & sfid &'?_HttpMethod=PATCH';
params.body.body = Replace( params.body.body,'"null"','null',"ALL" ); // null must not be in quotes
var httpResult = makeCall( url=local.url,method='POST',params=params );
//WriteDump( var="#gatherResponseString( httpResult )#",abort=true );
if( httpResult.responseHeader.status_code == 200 || httpResult.responseHeader.status_code == 204 ) {
return true;
} else {
var errorstring = gatherResponseString( httpResult );
throw( message='Unhandled status code from server: ' & httpResult.responseHeader.status_code & '('&gatherResponseString( httpResult )&')',detail=gatherResponseString( httpResult ),type='salesforce.objectNotFound' );
}
}
private string function upsertObject( required string type,required string externalField,required struct obj ) {
var externalValue = obj[ externalField ];
var tempStruct = copyStructAndRemoveSalesForceSpecialFields( obj );
Structdelete( tempStruct,externalField );
var params = { header={ 'Content-Type'='application/json' },body={ 'body' = SerializeJSON( var=tempStruct ) } };
params.body.body = Replace( params.body.body,'"null"','null',"ALL" ); // null must not be in quotes
var url = '/services/data/v' & variables.apiVersion &'/sobjects/' & type & '/' & externalField &'/' & externalValue &'?_HttpMethod=PATCH';
var httpResult = makeCall( url=local.url,method='POST',params=params );
if( httpResult.responseHeader.status_code == 204 ) {
//204 doesnt save an ID, anywhere, including a location header
return getObjectExternalId( type,externalField,externalValue,[ 'Id' ]).Id;
} else if( httpResult.responseHeader.status_code == 201 ) {
obj = DeserializeJSON( gatherResponseString( httpResult ) );
if( obj.success ) {
return obj.id;
} else {
throw( message='Unhandled response from server: ' + gatherResponseString( httpResult ),type='salesforce.unknownCreateError' );
}
} else {
throw( message='Unhandled status code from server: ' & httpResult.responseHeader.status_code & '('&gatherResponseString( httpResult )&')',detail=gatherResponseString( httpResult ),type='salesforce.objectNotFound' );
}
}
public String function createSfObject( required string type,required struct obj ) {
var url = '/services/data/v' & variables.apiVersion &'/sobjects/' & type & '/';
var toSave = copyStructAndRemoveSalesForceSpecialFields( obj );
var params = { header={ 'Content-Type'='application/json' },body={ 'body'=SerializeJSON( var=toSave ) } };
params.body.body = Replace( params.body.body,'"null"','null',"ALL" ); // null must not be in quotes
var httpResult = makeCall( url=local.url,method='POST',params=params );
if( httpResult.responseHeader.status_code == 201 ) {
obj = DeserializeJSON( gatherResponseString( httpResult ) );
if( obj.success ) {
return obj.id;
} else {
var errorstring = gatherResponseString( httpResult );
throw( message='Unhandled response from server: ' + gatherResponseString( httpResult ),type='salesforce.unknownCreateError' );
}
} else {
var errorstring = gatherResponseString( httpResult );
throw( message='Unhandled status code from server: ' & httpResult.responseHeader.status_code & '('&gatherResponseString( httpResult )&')',detail=gatherResponseString( httpResult ),type='salesforce.objectNotFound' );
}
}
private boolean function deleteObject(required string sfid,required string type) {
var url = '/services/data/v' & variables.apiVersion &'/sobjects/' & type & '/' & sfid;
var httpResult = makeCall( local.url,'DELETE',{} );
if( httpResult.responseHeader.status_code == 200 || httpResult.responseHeader.status_code == 204 )
return true;
else
throw( message='Unhandled status code from server: ' & httpResult.responseHeader.status_code & '('&gatherResponseString( httpResult )&') while deleting ' & type & ' ' & sfid,detail=gatherResponseString( httpResult ),type='salesforce.objectNotFound' );
}
public any function queryObjects( required string queryString ) {
var records = queryObjectsAsStructsWithRetry( queryString );
if( IsNull( records ) || Arraylen( records ) < 1) return QueryNew( '' );
var first = Structcopy( records[ 1 ] );
Structdelete( first,'attributes' );
var keys = 'type,url,' & Arraytolist( Structkeyarray( first ) );
var rst = QueryNew( keys );
QueryAddRow( rst,Arraylen( records ) );
loop array=records index="local.row" item="record" {
keys = Arraytolist( StructKeyArray( record ) );
loop list=keys index="key" {
if( 'attributes' == key ) {
QuerySetCell( rst,'type',record.attributes.type,local.row );
QuerySetCell( rst,'url',record.attributes.url,local.row );
} else {
QuerySetCell( rst,key,record[ key ]?:"",local.row ); // set null values to an empty string
}
}
}
return rst;
}
private any function queryObjectsAsStructsWithRetry( required string queryString ) {
try {
return queryObjectsAsStructs( queryString );
} catch( any exception ) {
// refresh access token if we get an error
if( FindNoCase( "Session expired or invalid",exception.detail ) ) {
login( force=true );
return queryObjectsAsStructs( queryString );
}
}
}
private any function queryObjectsAsStructs( required string queryString ) {
var url='/services/data/v' & variables.apiVersion &'/query/?q=' & urlencodedformat( queryString );
var httpResult = makeCall( local.url,'GET',{ header={ 'Content-Type'='application/json' } } );
if( IsDefined( "httpResult.responseHeader.status_code" ) && ( httpResult.responseHeader.status_code == 200 || httpResult.responseHeader.status_code == 204 ) ){
var results = DeserializeJSON( gatherResponseString( httpResult ) );
var records = results.records;
while( Structkeyexists( results,'nextRecordsUrl' ) ) {
httpResult = makeCall( results.nextRecordsUrl,'GET',{ header={ 'Content-Type'='application/json' } } );
if( httpResult.responseHeader.status_code == 200 || httpResult.responseHeader.status_code == 204 ) {
results = DeserializeJSON( gatherResponseString( httpResult ) );
records.addAll( results.records );
} else {
var errorstring = gatherResponseString( httpResult );
throw( message='Unhandled status code from server: ' & httpResult.responseHeader.status_code & '('&gatherResponseString( httpResult )&')',detail=gatherResponseString( httpResult ),type='salesforce.queryError');
}
}
return records;
} else {
var errorstring = gatherResponseString( httpResult );
throw( message='Unhandled status code from server: ' & httpResult.responseHeader.status_code & '('&gatherResponseString( httpResult )&')',detail=gatherResponseString( httpResult ),type='salesforce.queryError');
}
return DeserializeJSON( gatherResponseString( httpResult ) ).records;
}
public any function listObjects() {
var url='/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 ) {
var url='/services/data/v' & variables.apiVersion &'/sobjects/' & ObjectType & '/' & sfid & '/' & field;
var httpResult = makeCall( local.url,'GET',{} );
if( httpResult.responseHeader.status_code == 200 )
return httpResult.Filecontent.toByteArray();
throw( message='Unhandled status code from server: ' & httpResult.responseHeader.status_code,type='salesforce.objectNotFound' );
}
public any function OnMissingMethod( required String MissingMethodName,required struct MissingMethodArguments ) {
var args = {};
if( find( 'get',arguments.MissingMethodName ) == 1 ) {
args.type=Right( MissingMethodName,Len( MissingMethodName )-3 );
args.sfid = MissingMethodArguments[ 1 ];
args.fields = [];
if( ArrayLen( MissingMethodArguments ) > 1) {
args.fields = MissingMethodArguments[ 2 ];
}
return getObject( argumentCollection=args);
} else if( find( 'update',MissingMethodName ) == 1) {
args.type=Right( MissingMethodName,Len( MissingMethodName )-6 );
args.sfid = MissingMethodArguments[ 1 ];
args.obj =MissingMethodArguments[ 2 ];
return patchObject( argumentCollection=args );
} else if( find( 'upsert',MissingMethodName ) == 1 ) {
args.type=Right( MissingMethodName,Len( MissingMethodName )-6 );
args.obj =MissingMethodArguments[ 1 ];
args.externalField = MissingMethodArguments[ 2 ];
return upsertObject( argumentCollection=args );
} else if( find( 'create',MissingMethodName ) == 1 ) {
args.type=Right( MissingMethodName,Len( MissingMethodName )-6 );
args.obj = MissingMethodArguments[ 1 ];
return createSfObject( argumentCollection=args );
} else if( find( 'delete',MissingMethodName) == 1 ) {
args.type=Right( MissingMethodName,Len( MissingMethodName )-6 );
args.sfid = MissingMethodArguments[ 1 ];
return deleteObject( argumentCollection=args );
} else if( find( 'describe',MissingMethodName) == 1 ) {
args.type=Right( MissingMethodName,Len( MissingMethodName )-8 );
return describeObject( argumentCollection=args );
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment