Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save mjclemente/824f58d52ed907b1fcf18789fdee80a2 to your computer and use it in GitHub Desktop.
Save mjclemente/824f58d52ed907b1fcf18789fdee80a2 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
// adapted from https://gist.github.com/jeremyhalliwell/3be545da6f4ebd07d741, by Jeremy Halliwell
public any function init(
required string client_id,
required string client_secret,
required string refresh_token,
string accessTokenEndpoint = 'https://login.salesforce.com/services/oauth2/token',
string apiVersion = '29.0',
string salesforceInstance = '',
boolean logCalls = false,
string pathLogs = ""
) {
variables.append( arguments );
variables.access_token = '';
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.access_token.len() && !force ) return;
lock scope="application" type="exclusive" timeout="60" {
var httpResult = '';
var method = 'POST';
cfhttp( method=method, timeout=4, url=variables.accessTokenEndpoint, result="httpResult" ){
cfhttpparam(type="formfield", name="grant_type", value="refresh_token");
cfhttpparam(type="formfield", name="client_id", value=variables.client_id);
cfhttpparam(type="formfield", name="client_secret", value=variables.client_secret);
cfhttpparam(type="formfield", name="refresh_token", value=variables.refresh_token);
}
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.access_token = json.access_token;
logCall( method=local.method,url=variables.accessTokenEndpoint );
} else {
variables.access_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 getAccessToken() {
return variables.access_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='Bearer ' & getAccessToken();
}
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