Configuring Salesforce for Service Board-to-Salesforce Real-Time Sync for Custom Objects
To configure Salesforce for Service Board-to-Salesforce real-time sync for custom objects, you first define Apex classes and a trigger on the Salesforce side. You then create transform templates and HTTP Notification Request records for Create/Update/Delete events and define a transform rules Setting on the Service Board side.
|
• You must configure real-time sync separately for each custom object in your Service Board tenant.
|
To configure Salesforce for Service Board-to-Salesforce real-time sync for custom objects:
1. Log into Salesforce, and then create the following Apex classes:
SB_PBSecurityUtils
public with sharing class SB_PBSecurityUtils {
private static final String STRING_ATTRIBUTES = 'attributes';
private static final String STRING_TYPE = 'type';
private static SB_PBSecurityUtils instance = new SB_PBSecurityUtils();
private Boolean ignoreSelectFields = false;
private Map<String, Map<String,Schema.SObjectField>> objectSobjectFieldMap = new Map<String, Map<String, Schema.SObjectField>>();
@TestVisible private Boolean isSecurityCheckEnabled = false;
/**
* Set of system fields to be ignored while verifying field access.
*/
@TestVisible private Set<String> systemFieldsSet = new Set<String> {
'name',
'id',
'isdeleted',
'createdbyid',
'createddate',
'lastmodifiedbyid',
'lastmodifiedby',
'lastmodifieddate',
'systemmodstamp',
'lastactivitydate',
'ownerid',
'lastreferenceddate',
'lastvieweddate',
'recordtypeid',
'recordtype',
'isprivate',
'contenttype',
'bodylength',
'parentid',
'currencyisocode',
'contact',
'account'
};
/**
* Enum class to define supported access types.
*/
public enum Access { Accessible, Createable, Updateable, Upsertable }
/**
* This method is used to retrieve instance of this class.
* @return Returns instance of SB_PBSecurityUtils.
*/
public static SB_PBSecurityUtils getInstance() {
return instance;
}
/**
* This method is used to verify object access for Createable operation.
* @param objectAPIName Object API name for which Createable operation access to be verified.
* @return Returns true if the object has Createable access, Otherwise, returns false.
*/
public Boolean isCreateable( String objectAPIName ) {
System.debug( LoggingLevel.DEBUG, 'isCreateable() - enter; objectName: ' + objectAPIName );
Boolean returnValue = false;
if( isSecurityCheckEnabled ) {
Map<String, Schema.SObjectType> globalDescribeMap = Schema.getGlobalDescribe();
Schema.SObjectType sobjectType = globalDescribeMap.get(objectAPIName);
// Retrieve the object type from global describe and verify whether the object has
// Accessible access or not.
if( sobjectType != null ) {
returnValue = sobjectType.getDescribe().isCreateable();
}
} else {
returnValue = true;
}
System.debug( LoggingLevel.DEBUG, 'isCreateable() - exit; returnValue: ' + returnValue );
return returnValue;
}
/**
* This method is used to verify object access for delete operation.
* @param objectAPIName Object API name for which delete operation access to be verified.
* @return Returns true if the object has deletable access, Otherwise, returns false.
*/
public Boolean isDeletableObject( String objectAPIName ) {
System.debug( LoggingLevel.DEBUG, 'isDeletableObject() - enter; objectName: ' + objectAPIName );
Boolean returnValue = false;
if( isSecurityCheckEnabled ) {
Map<String, Schema.SObjectType> globalDescribeMap = Schema.getGlobalDescribe();
Schema.SObjectType sobjectType = globalDescribeMap.get(objectAPIName);
// Retrieve the object type from global describe and verify whether the object has
// delete access or not.
if( sobjectType != null ) {
returnValue = sobjectType.getDescribe().isDeletable();
}
} else {
returnValue = true;
}
System.debug( LoggingLevel.DEBUG, 'isDeletableObject() - exit; returnValue: ' + returnValue );
return returnValue;
}
/**
* This method is used to verify object access for Accessible operation.
* @param objectAPIName Object API name for which Accessible operation access to be verified.
* @return Returns true if the object has Accessible access, Otherwise, returns false.
*/
public Boolean isAccessible( String objectAPIName ) {
System.debug( LoggingLevel.DEBUG, 'isAccessible() - enter; objectName: ' + objectAPIName );
Boolean returnValue = false;
if( isSecurityCheckEnabled ) {
Map<String, Schema.SObjectType> globalDescribeMap = Schema.getGlobalDescribe();
Schema.SObjectType sobjectType = globalDescribeMap.get(objectAPIName);
// Retrieve the object type from global describe and verify whether the object has
// Accessible access or not.
if( sobjectType != null ) {
returnValue = sobjectType.getDescribe().isAccessible();
}
} else {
returnValue = true;
}
System.debug( LoggingLevel.DEBUG, 'isAccessible() - exit; returnValue: ' + returnValue );
return returnValue;
}
/**
* This method is used to verify field access for requested sobject and access type.
* @param sourceObjectList List of SObject from which single instance field access to be verified.
* @param accessType Access type for which fields to be verified.
* @param ignoreSelectFields Flag to ignore check for fields which are queried in select query
* and used in update DML operation.
* @returns Returns true if all the fields has requested access, Otherwise, returns false.
*/
public Boolean verifyFieldAccess( List<SObject> sourceObjectList, Access accessType, Boolean ignoreSelectFields ) {
this.ignoreSelectFields = ignoreSelectFields;
return verifyFieldAccess( sourceObjectList, accessType );
}
/**
* This method is used to verify field access for requested sobject and access type.
* @param sourceObjectList List of SObject from which single instance field access to be verified.
* @param accessType Access type for which fields to be verified.
* @returns Returns true if all the fields has requested access, Otherwise, returns false.
*/
public Boolean verifyFieldAccess( List<SObject> sourceObjectList, Access accessType ) {
Boolean returnValue = true;
if( isSecurityCheckEnabled ) {
if( sourceObjectList != null && !sourceObjectList.isEmpty() ) {
returnValue = verifyFieldAccess( sourceObjectList.get(0), accessType );
}
}
return returnValue;
}
/**
* This method is used to verify field access for requested sobject and access type.
* @param sourceObject SObject instance of which field access to be verified.
* @param accessType Access type for which fields to be verified.
* @returns Returns true if all the fields has requested access, Otherwise, returns false.
*/
public Boolean verifyFieldAccess( SObject sourceObject, Access accessType ) {
System.debug( LoggingLevel.DEBUG, 'verifyFieldAccess() - enter; sObject: ' + sourceObject + '; accessType: ' + accessType );
Boolean returnValue = false;
try {
if( isSecurityCheckEnabled ) {
// Convert SObject to Map which will have only those field information which
// are accessed or configured.
Map<String, Object> objectFieldValues = ( Map<String, Object> ) JSON.deserializeUntyped( JSON.serialize( sourceObject ) );
System.debug('objectFieldValues '+objectFieldValues);
Set<String> fieldsPresent = objectFieldValues.keyset().clone();
System.debug('fieldsPresent '+fieldsPresent);
List<String> fieldAPINameList = new List<String>();
String objectAPIName = null;
for( String key : objectFieldValues.keySet() ) {
// The map will have additional element attributes other that the field values pairs. This will
// have map of properties of which one of the property is type. The type will have object API name
// from which this SObject instance has been created.
if( key.endsWithIgnoreCase( STRING_ATTRIBUTES ) ) {
objectAPIName = (String) ( (Map<String, Object>) objectFieldValues.get( key ) ).get( STRING_TYPE );
System.debug( LoggingLevel.DEBUG, 'Object API name: ' + objectAPIName );
continue;
}
// Ignore verification of system fields.
if( accessType != Access.Accessible && systemFieldsSet.contains( key.toLowerCase() ) ) {
returnValue = true;
continue;
}
if( !key.contains('.') && !key.contains('__r') ) {
// All the keys in this map, other than attributes, are field API names.
fieldAPINameList.add( key );
}
}
System.debug( LoggingLevel.DEBUG, 'fieldAPINameList: ' + fieldAPINameList );
for(String tempVal:fieldAPINameList){
System.debug('tempVal '+tempVal);
}
// Call overload method with list of field names to verify the access.
if( objectAPIName != null && fieldAPINameList != null && !fieldAPINameList.isEmpty() ) {
returnValue = verifyFieldAccess( objectAPIName, fieldAPINameList, accessType );
}
} else {
returnValue = true;
}
}
catch( Exception ex ) {
System.debug( LoggingLevel.ERROR, ex.getMessage() );
System.debug( LoggingLevel.ERROR, ex.getStackTraceString() );
}
finally {
System.debug( LoggingLevel.DEBUG, 'verifyFieldAccess() - exit; returnValue: ' + returnValue );
}
return returnValue;
}
/**
* This method is used to verify field access for requested list of field API names and access type.
*
* @param objectAPIName Source object name for which field access to be verified.
* @param fieldAPINameList List of field names for which access to be verified.
* @param accessType Access type for which requested list of fields to be verified.
*
* @return Returns true if all the fields has requested access, Otherwise, returns false.
*/
public Boolean verifyFieldAccess( String objectAPIName, List<String> fieldAPINameList, Access accessType ) {
System.debug( LoggingLevel.DEBUG, 'verifyFieldAccess() - enter; objectAPIName: ' + objectAPIName + '; fieldAPINameList: ' + fieldAPINameList + '; accessType: ' + accessType );
Boolean returnValue = false;
if( isSecurityCheckEnabled ) {
// Verify access of each field in the list.
for( String fieldAPIName : fieldAPINameList ) {
returnValue = verifyFieldAccess(objectAPIName, fieldAPIName, accessType );
if(!returnValue) {
System.debug( LoggingLevel.DEBUG, fieldAPIName + '; Insufficient permission to perform ' + accessType );
break;
}
}
} else {
returnValue = true;
}
System.debug( LoggingLevel.DEBUG, 'verifyFieldAccess() - exit; returnValue: ' + returnValue );
return returnValue;
}
/**
* This method is used to verify field access for requested list of field API names and access type.
*
* @param objectAPIName Source object name for which field access to be verified.
* @param fieldAPIName field names for which access to be verified.
* @param accessType Access type for which requested list of fields to be verified.
*
* @return Returns true if all the fields has requested access, Otherwise, returns false.
*/
public Boolean verifyFieldAccess( String objectAPIName, String fieldAPIName, Access accessType ) {
System.debug( LoggingLevel.DEBUG, 'verifyFieldAccess() - enter; objectAPIName: ' + objectAPIName + '; fieldAPIName: ' + fieldAPIName + '; accessType: ' + accessType );
Boolean returnValue = false;
if( isSecurityCheckEnabled ) {
Map<String, Schema.SObjectField> sobjectFieldMap = getSObjectFieldMap( objectAPIName );
// Continue verifing field accedd only if the object API name is valid. The map sobjectFieldMap
// will be null if the objectAPIName is invalid.
if( sobjectFieldMap != null ) {
fieldAPIName = fieldAPIName.trim();
// Return false if the list has invalid field API name.
if( sobjectFieldMap.containsKey( fieldAPIName ) ) {
// Ignore verification of system fields.
if( accessType == Access.Accessible || !systemFieldsSet.contains( fieldAPIName.toLowerCase() ) ) {
// Call overload method with list of field describe to verify the access.
returnValue = verifyFieldAccess( sobjectFieldMap.get(fieldAPIName).getDescribe(), accessType );
} else {
returnValue = true;
}
} else if( fieldAPIName.contains('.') ) {
String referenceField = fieldAPIName.substring(0, fieldAPIName.indexOf('.') ).replace('__r', '__c');
Schema.DescribeFieldResult describeFieldResult;
if(referenceField.contains('__c') && sobjectFieldMap.containsKey(referenceField) ) {
describeFieldResult = sobjectFieldMap.get(referenceField).getDescribe();
if( describeFieldResult.getType() == Schema.DisplayType.REFERENCE ) {
List<Schema.sObjectType> referenceList = describeFieldResult.getReferenceTo();
String referenceTo = null;
for( Schema.sObjectType objectType : referenceList ) {
Schema.DescribeSObjectResult result = objectType.getDescribe();
referenceTo = result.getName().replace('__r', '__c');
break;
}
System.debug( LoggingLevel.DEBUG, 'referenceField: ' + referenceField +'; referenceTo: ' + referenceTo );
if( referenceTo != null ) {
fieldAPIName = fieldAPIName.substring( fieldAPIName.indexOf('.') + 1 );
returnValue = verifyFieldAccess( referenceTo, fieldAPIName, accessType );
}
}
} else {
if( sobjectFieldMap.containsKey(referenceField+'Id') ) {
describeFieldResult = sobjectFieldMap.get(referenceField+'Id').getDescribe();
List<Schema.sObjectType> referenceList = describeFieldResult.getReferenceTo();
if(!referenceList.isEmpty()){
referenceField = referenceList[0].getDescribe().getName();
System.debug('referenceField: ' + referenceField );
}
}
fieldAPIName = fieldAPIName.substring( fieldAPIName.indexOf('.') + 1 );
returnValue = verifyFieldAccess( referenceField, fieldAPIName, accessType );
}
}
}
} else {
returnValue = true;
}
System.debug( LoggingLevel.DEBUG, 'verifyFieldAccess() - exit; returnValue: ' + returnValue );
return returnValue;
}
/**
* This method is used to verify field access for requested SObjectField and access type.
*
* @param sobjectField Schema.SObjectField for which field access to be verified.
* @param accessType Access type for which requested Schema.SObjectField to be verified.
*
* @return Returns true if all the fields has requested access, Otherwise, returns false.
*/
public Boolean verifyFieldAccess( Schema.SObjectField sobjectField, Access accessType ) {
System.debug( LoggingLevel.DEBUG, 'verifyFieldAccess() - enter; sobjectField: ' + sobjectField + '; accessType: ' + accessType );
Boolean returnValue = false;
if( isSecurityCheckEnabled ) {
if( sobjectField != null ) {
// Call overload method with list of field describe to verify the access.
returnValue = verifyFieldAccess( sobjectField.getDescribe(), accessType);
}
} else {
returnValue = true;
}
System.debug( LoggingLevel.DEBUG, 'verifyFieldAccess() - exit; returnValue: ' + returnValue );
return returnValue;
}
/**
* This method is used to verify field access for requested DescribeFieldResult and access type.
*
* @param describeFieldResult Schema.DescribeFieldResult for which field access to be verified.
* @param accessType Access type for which requested Schema.DescribeFieldResult to be verified.
*
* @return Returns true if all the fields has requested access, Otherwise, returns false.
*/
public Boolean verifyFieldAccess( Schema.DescribeFieldResult describeFieldResult, Access accessType ) {
System.debug( LoggingLevel.DEBUG, 'verifyFieldAccess() - enter; describeFieldResult: ' + describeFieldResult + '; accessType: ' + accessType );
Boolean returnValue = false;
if( isSecurityCheckEnabled ) {
if( describeFieldResult != null && describeFieldResult.getType() == Schema.DisplayType.REFERENCE && accessType == Access.Updateable ) {
returnValue = true;
} else if( describeFieldResult != null && ( describeFieldResult.isCalculated() || describeFieldResult.isAutoNumber() ) ) {
returnValue = true;
} else
// Return false if describeFieldResult or accessType is invalid.
if( describeFieldResult != null && accessType != null ) {
if( accessType == Access.Accessible ) {
returnValue = describeFieldResult.isAccessible();
} else if( accessType == Access.Createable ) {
returnValue = describeFieldResult.isCreateable();
} else if( accessType == Access.Updateable ) {
returnValue = describeFieldResult.isUpdateable();
if( ignoreSelectFields ) {
returnValue = describeFieldResult.isAccessible();
}
} else if( accessType == Access.Upsertable ) {
// For upsert operation, one of update and create access must be enabled.
returnValue = ( describeFieldResult.isUpdateable() || describeFieldResult.isCreateable() );
} else {
System.debug( LoggingLevel.ERROR, 'Unsupported access type to verify field access. accessType: ' + accessType.name() );
}
}
} else {
returnValue = true;
}
System.debug(LoggingLevel.DEBUG, 'verifyFieldAccess() - exit; returnValue: ' + returnValue );
return returnValue;
}
/**
* This method is used to retrieve cached Map of SObjectField for given object name.
*
* @param objectName Object API name for which map of SObjectField to be retrieved.
*
* @return If valid object name, then returns map of field API name and SObjectField
* for requested object name, Otherwise, returns null.
*/
@TestVisible private Map<String, Schema.SObjectField> getSObjectFieldMap( String objectName ) {
Map<String, Schema.SObjectType> globalDescribeMap = Schema.getGlobalDescribe();
// Describe fields only if the object name is valid and the cache map has no entry on it.
if( !objectSobjectFieldMap.containsKey( objectName ) && globalDescribeMap.containsKey( objectName ) ) {
objectSobjectFieldMap.put( objectName, globalDescribeMap.get( objectName ).getDescribe().fields.getMap() );
}
return objectSobjectFieldMap.get( objectName );
}
/**
* Private class constructor to create instance of this class.
*/
private SB_PBSecurityUtils() {
String settingValue = new SVMXC.COMM_Utils_ManageSettings().SVMX_getGlobalSettingList('GLOB001_GBL031');
if(settingValue != null){
isSecurityCheckEnabled = Boolean.valueOf(settingValue);
}
if(Test.isRunningTest()) {
isSecurityCheckEnabled = true;
}
}
}
SB_Custom_IntegrationHelper
public with sharing class SB_Custom_IntegrationHelper{
/*
Custom exception class which will be used to throw meaningful error messages
*/
public class IntegrationException extends Exception {}
public class IntegrationRequest{
public String eventName;
public Payload payload;
}
public class Payload{
public String io_uuid;
public String objectName;
public map<String, String> fields;
public map<String, String> fields_previous;
public map<String, String> systemFields;
public map<String, String> criteria;
public Set<String> modifiedFields;
}
public class IntegrationResponse{
public String status;
public Error errorLog;
public String retVal;
}
public class Error{
public String recordId;
public String errorType;
public String description;
}
public static Boolean isMaxTxn = false;
public static String isMaxTxnRecordId = '';
public static Boolean isVersionUpdated = false;
public static Map<String, Set<String>> mapIdToModifiedFields = new Map<String, Set<String>>();
public static Map<String, String> mapSettings = new Map<String, String>();
public static Integer responseStatusCode = 400;
public Set<String> setReferenceFields = new Set<String>();
public IntegrationResponse validateRequest(IntegrationRequest request){
String status = 'Success';
IntegrationResponse response = new IntegrationResponse();
Error errorLog = new Error();
if(String.isBlank(request.eventname)){
status = 'Fail';
errorLog.description = 'Event Name not specified';
response.errorLog = errorLog;
}else if(request.payload == null){
status = 'Fail';
errorLog.description = 'Payload/Criteria not provided';
response.errorLog = errorLog;
}else if(String.isNotBlank(request.eventname) && request.eventname.startsWithIgnoreCase('update_') && request.payload.criteria == null){
status = 'Fail';
errorLog.description = 'No Criteria provided for update';
response.errorLog = errorLog;
}
else if(String.isNotBlank(request.eventname) && request.eventname.startsWithIgnoreCase('update_') && String.isBlank(request.payload.criteria.get('Id'))){
status = 'Fail';
errorLog.description = 'Id not provided for update';
response.errorLog = errorLog;
}
else if(String.isNotBlank(request.eventname) && request.eventname.startsWithIgnoreCase('delete_') && String.isBlank(request.payload.criteria.get('Id'))){
status = 'Fail';
errorLog.description = 'Id not provided for delete';
response.errorLog = errorLog;
}
response.status = status;
response.errorLog = errorLog;
return response;
}
public IntegrationResponse processRequest(IntegrationRequest request){
system.debug(LoggingLevel.WARN, 'IntegrationRequest: ' + request);
IntegrationResponse response = new IntegrationResponse();
if(String.isNotBlank(request.eventname) && request.eventname.startsWithIgnoreCase('create_')){
response = insertRecord(request.payload, request.eventname);
}else if(String.isNotBlank(request.eventname) && request.eventname.startsWithIgnoreCase('update_')){
response = updateRecord(request.payload, request.eventname);
}else if(String.isNotBlank(request.eventname) && request.eventname.startsWithIgnoreCase('delete_')){
response = deleteRecords(request.payload, request.eventname);
}else{
Error errorLog = new Error();
errorLog.description = 'No Matching Event Name';
response.errorLog = errorLog;
}
return response;
}
public IntegrationResponse deleteRecords(Payload payload, String eventName){
map<String, String> criteria = payload.criteria;
Id recordId = criteria.get('Id');
isMaxTxnRecordId = recordId;
String errorMsg = '';
boolean isSuccess = true;
//SDL-SVMX-CREATE-UPDATE-FLS-ENFORCED
if(SB_PBSecurityUtils.getInstance().isDeletableObject(payload.objectName)){
Database.DeleteResult result = Database.delete(recordId, false);
if(result.isSuccess()){
recordId = result.getId();
} else {
isSuccess = false;
for(Database.Error err : result.getErrors()) {
system.debug(LoggingLevel.WARN, 'Error in delete operation = ' + err.getMessage());
errorMsg += err.getMessage();
if(err.getStatusCode() == StatusCode.UNABLE_TO_LOCK_ROW){
responseStatusCode = 500;
}
}
}
} else {
errorMsg = System.Label.SVMXC.COMM001_TAG142;
}
return buildResponse(isSuccess, recordId, errorMsg);
}
public IntegrationResponse insertRecord(Payload payload, String eventName){
boolean isSuccess = true;
String errorMsg = '';
String recordId;
try{
sObject sObj = Schema.getGlobalDescribe().get(payload.objectName).newSObject();
map<String, Object> mapFieldValue = payload.fields;
String uuid = payload.io_uuid;
List<SVMXC__SM_SB_Queue_Inbound__c> lstInboundData = new List<SVMXC__SM_SB_Queue_Inbound__c>();
lstInboundData = [Select Id, SVMXC__SM_External_UUID__c, SVMXC__SM_Event_type__c, SVMXC__SM_Record_to_dispatch__c from SVMXC__SM_SB_Queue_Inbound__c where SVMXC__SM_Event_type__c = :eventName and SVMXC__SM_External_UUID__c = :uuid];
if(!lstInboundData.isEmpty()){
recordId = lstInboundData[0].SVMXC__SM_Record_to_dispatch__c;
} else {
sObj = createsObject(mapFieldValue, payload.objectName, sObj);
system.debug(LoggingLevel.WARN, 'sObj for insert = ' + sObj);
//SDL-SVMX-CREATE-UPDATE-FLS-ENFORCED
if(SB_PBSecurityUtils.getInstance().isCreateable(payload.objectName)){
Database.SaveResult result = Database.insert(sObj, false);
if(result.isSuccess()){
recordId = result.getId();
insertIntoInboundQueue(eventName, recordId, errorMsg, uuid);
}else{
for(Database.Error err : result.getErrors()) {
isSuccess = false;
system.debug(LoggingLevel.WARN, 'Error in insert operation = ' + err.getMessage());
errorMsg += err.getMessage();
if(err.getStatusCode() == StatusCode.UNABLE_TO_LOCK_ROW){
responseStatusCode = 500;
}
}
}
}
else {
errorMsg = System.Label.SVMXC.COMM001_TAG142;
}
}
} catch(DMLException dmlEx){
isSuccess = false;
system.debug(LoggingLevel.WARN, ' DML Exception occurred = ' + dmlEx.getMessage());
errorMsg = dmlEx.getMessage();
if(dmlEx.getDmlType(0) == StatusCode.UNABLE_TO_LOCK_ROW){
responseStatusCode = 500;
}
} catch(Exception ex){
isSuccess = false;
system.debug(LoggingLevel.WARN, 'Exception occurred = ' + ex.getMessage());
errorMsg = ex.getMessage();
}
return buildResponse(isSuccess, recordId, errorMsg);
}
public IntegrationResponse updateRecord(Payload payload, String eventName){
boolean isSuccess = true;
String errorMsg = '';
String recordId;
try{
map<String, Object> mapFieldValue = payload.fields;
Id recId = Id.valueOf(payload.criteria.get('Id'));
isMaxTxnRecordId = recId;
sObject sObj = recId.getSobjectType().newSObject(recId);
String sObjectName = recId.getSobjectType().getDescribe().getName();
sObj = createsObject(mapFieldValue, sObjectName, sObj);
system.debug(LoggingLevel.WARN, 'sObj for update = ' + sObj);
//SDL-SVMX-CREATE-UPDATE-FLS-ENFORCED
if(SB_PBSecurityUtils.getInstance().verifyFieldAccess(sObj, SB_PBSecurityUtils.Access.Updateable)) {
Database.SaveResult result = Database.update(sObj, false);
if(result.isSuccess()){
recordId = result.getId();
}else{
for(Database.Error err : result.getErrors()) {
isSuccess = false;
system.debug(LoggingLevel.WARN, 'Error in update operation = ' + err.getMessage());
errorMsg += err.getMessage();
if(err.getStatusCode() == StatusCode.UNABLE_TO_LOCK_ROW){
responseStatusCode = 500;
}
}
}
} else {
errorMsg = System.Label.SVMXC.COMM001_TAG142;
}
} catch(DMLException dmlEx){
isSuccess = false;
system.debug(LoggingLevel.WARN, ' DML Exception occurred = ' + dmlEx.getMessage());
errorMsg = dmlEx.getMessage();
if(dmlEx.getDmlType(0) == StatusCode.UNABLE_TO_LOCK_ROW){
responseStatusCode = 500;
}
} catch(Exception ex){
isSuccess = false;
system.debug(LoggingLevel.WARN, 'Exception occurred = ' + ex.getMessage());
errorMsg = ex.getMessage();
}
return buildResponse(isSuccess, recordId, errorMsg);
}
private IntegrationResponse buildResponse(boolean isSuccess, String recordId, String errorMsg){
IntegrationResponse response = new IntegrationResponse();
response.status = 'Success';
response.retVal = recordId;
if(!isSuccess){
response.status = 'Error';
Error errorDescription = new Error();
errorDescription.description = errorMsg;
response.errorLog = errorDescription;
}
return response;
}
private sObject createsObject(map<String, Object> mapFieldValue, String objectName, sObject sobj){
SObjectType sObjectType = ((SObject)(Type.forName('Schema.' + objectName).newInstance())).getSObjectType();
Map<String, Schema.SObjectField> fieldMap = sObjectType.getDescribe().fields.getMap();
for(String eachField : mapFieldValue.keyset()){
if(fieldMap.containskey(eachField)){
Schema.DescribeFieldResult fieldDesc = fieldMap.get(eachField).getDescribe();
String fieldType = String.valueOf(fieldDesc.getType());
String strFieldValue = String.valueOf(mapFieldValue.get(eachField));
system.debug(LoggingLevel.WARN, 'eachField = ' + eachField + '; strFieldValue = ' + strFieldValue);
if(fieldType == 'DATETIME' && !String.isBlank(strFieldValue)){
strFieldValue = strFieldValue.replace('T', ' ');
strFieldValue = strFieldValue.replace('Z', ' ');
sObj.put(eachField, Datetime.valueOfGmt(strFieldValue));
}
else if(fieldType == 'DATE' && !String.isBlank(strFieldValue)){
sObj.put(eachField, Date.valueOf(strFieldValue));
}else if(fieldType == 'BOOLEAN' && !String.isBlank(strFieldValue)){
sObj.put(eachField, Boolean.valueOf(strFieldValue));
}else if(fieldType == 'DOUBLE' && !String.isBlank(strFieldValue)){
sObj.put(eachField, decimal.valueOf(strFieldValue));
}else if((fieldType == 'DECIMAL' || fieldType == 'CURRENCY' || fieldType == 'PERCENT') && !String.isBlank(strFieldValue)){
sObj.put(eachField, decimal.valueOf(strFieldValue));
}else if(fieldType == 'INTEGER' && !String.isBlank(strFieldValue)){
sObj.put(eachField, Integer.valueOf(strFieldValue));
}else if(fieldType == 'TIME' && !String.isBlank(strFieldValue)){
strFieldValue = strFieldValue.replace('Z', '');
List<String> timeComponent = strFieldValue.split(':');
List<String> secondsComponent = new List<String>();
if(!timeComponent.isEmpty() && timeComponent.size() == 3){
secondsComponent = timeComponent[2].split('\\.');
system.debug(LoggingLevel.WARN, 'strFieldValue = ' + timeComponent + '; secondsComponent = ' + secondsComponent);
sObj.put(eachField, Time.newInstance(Integer.valueOf(timeComponent[0]), Integer.valueOf(timeComponent[1]), Integer.valueOf(secondsComponent[0]), Integer.valueOf(secondsComponent[1])));
}
}else{
sObj.put(eachField, strFieldValue);
}
}
}
return sObj;
}
private void insertIntoInboundQueue(String eventName, String recordId, String errorMsg, String uuid){
system.debug(LoggingLevel.WARN, 'eventName = ' + eventName + '; recordId = ' + recordId + '; errorMsg = ' + errorMsg);
List<SVMXC__SM_SB_Queue_Inbound__c> lstInboundData = new List<SVMXC__SM_SB_Queue_Inbound__c>();
lstInboundData = [Select Id, SVMXC__SM_Event_type__c, SVMXC__SM_Record_to_dispatch__c from SVMXC__SM_SB_Queue_Inbound__c where SVMXC__SM_Record_to_dispatch__c = :recordId and SVMXC__SM_Event_type__c = :eventName];
if(lstInboundData.isEmpty()){
SVMXC__SM_SB_Queue_Inbound__c inboundData = new SVMXC__SM_SB_Queue_Inbound__c();
lstInboundData.add(inboundData);
}
lstInboundData[0].SVMXC__SM_External_UUID__c = uuid;
lstInboundData[0].SVMXC__SM_Event_type__c = eventname;
lstInboundData[0].SVMXC__SM_Record_to_dispatch__c = recordId;
lstInboundData[0].SVMXC__SM_Status__c = 'Queued';
lstInboundData[0].SVMXC__SM_Error_log__c = errorMsg;
//SDL-SVMX-CREATE-UPDATE-FLS-ENFORCED
if(SB_PBSecurityUtils.getInstance().verifyFieldAccess(lstInboundData, SB_PBSecurityUtils.Access.Upsertable)){
Database.UpsertResult result = Database.upsert(lstInboundData[0], false);
system.debug(LoggingLevel.WARN, 'Database Upsert result = ' + result);
} else {
system.debug(LoggingLevel.WARN, 'Upsert failed = ' + System.Label.SVMXC.COMM001_TAG142);
}
}
public map<String, String> buildPayloadCriteria(Sobject sobj, String eventName){
map<String,String> mapCriteria = new map<String, String>();
if(eventName.startsWithIgnoreCase('update') || eventName.startsWithIgnoreCase('remove')){
mapCriteria.put('Id', String.valueOf(sobj.get('Id')));
}
return mapCriteria;
}
public map<String, String> buildMapOfFields(Sobject sobj, Set<String> fields){
map<String, String> result = new map<String, String>();
for(String field : fields){
String value = String.valueof(sobj.get(field));
result.put(field, value);
}
return result;
}
private SVMXC__SM_SB_Queue_Outbound__c insertIntoOutboundQueue(String eventName, Id eachId, String status, String errorLog){
SVMXC__SM_SB_Queue_Outbound__c outboundRec = new SVMXC__SM_SB_Queue_Outbound__c();
if(!String.isEmpty(eventName)){
outboundRec.SVMXC__SM_Event_type__c = eventName;
}
if(eachId != null){
outboundRec.SVMXC__SM_Record_to_dispatch__c = eachId;
}
if(!String.isEmpty(status)){
outboundRec.SVMXC__SM_Status__c = status;
}
if(!String.isEmpty(errorLog)){
outboundRec.SVMXC__SM_Error_log__c = errorLog;
}
return outboundRec;
}
public void preparePlatformEvent(String eventName, String objectName, map<Id, sobject> mapIdToSObject){
List<SVMXC__SM_SB_Queue_Outbound__c> lstOutboundRecordToInsert = new List<SVMXC__SM_SB_Queue_Outbound__c>();
Set<String> setOfFields = getFieldSet(objectName);
if(mapIdToSObject.size() > 1){
publishPlatformEvent(eventName, objectName, setOfFields, mapIdToSObject.keySet(), lstOutboundRecordToInsert);
} else {
String namespace = 'SVMXC';
if(objectName == 'Event'){
namespace = 'SVMXSB';
} else{
namespace = 'SVMXC';
}
if(objectName == 'Account'){
map<string, Schema.SObjectField> accountFieldsMap = Schema.sObjectType.Account.fields.getMap();
if(accountFieldsMap.get('ispersonaccount') != null) {
system.debug(LoggingLevel.WARN, 'is Person Account');
setOfFields.add('FirstName');
setOfFields.add('LastName');
} else{
system.debug(LoggingLevel.WARN, 'NOT a Person Account');
}
}
for(Id eachId : mapIdToSObject.keySet()) {
system.debug(LoggingLevel.WARN, 'eventName = ' + eventName + '; eachId = ' + eachId);
if(isMaxTxn){
continue;
}
IntegrationRequest integrationRequest = prepareRequest(eventName, objectName, mapIdToSObject.get(eachId), setOfFields);
integrationRequest.eventName = eventName;
if(!isLimitReached()) {
publishPlatformEvent(eventName, objectName, eachId, integrationRequest, lstOutboundRecordToInsert);
} else {
String governorLimits = 'CPU Time consumed in ms = ' + Limits.getCpuTime() + ';SOQLs used = ' + Limits.getQueries()
+ ';Heap consumed in bytes = ' + Limits.getHeapSize() + ';DMLs used = ' + Limits.getDMLStatements();
system.debug(loggingLevel.WARN, 'Governor limit of SOQL, DML, CPU or Heap reached. ' + governorLimits);
lstOutboundRecordToInsert.add(insertIntoOutboundQueue(eventName, eachId, 'Queued', 'Governor limit of SOQL, DML, CPU or Heap reached. ' + governorLimits));
}
}
}
if(!lstOutboundRecordToInsert.isEmpty()) {
if(SB_PBSecurityUtils.getInstance().isCreateable('SVMXC__SM_SB_Queue_Outbound__c')
&& SB_PBSecurityUtils.getInstance().verifyFieldAccess(lstOutboundRecordToInsert, SB_PBSecurityUtils.Access.Createable)) {
Database.SaveResult [] saveResult = Database.insert(lstOutboundRecordToInsert, false);
} else {
system.debug(LoggingLevel.WARN, 'insert failed = ' + System.Label.SVMXC.COMM001_TAG142);
}
}
}
public void publishPlatformEvent(String eventName, String objectName, Id sfdcRecordId, IntegrationRequest integrationRequest, List<SVMXC__SM_SB_Queue_Outbound__c> lstOutboundRecordToInsert){
SVMXC__SM_Service_Board__e platformEvent = new SVMXC__SM_Service_Board__e();
platformEvent.SVMXC__SM_Event_Type__c = eventName;
platformEvent.SVMXC__SM_Object_Name__c = objectName;
platformEvent.SVMXC__SM_Payload__c = JSON.serialize(integrationRequest);
system.debug(LoggingLevel.WARN, 'Platform Event = ' + platformEvent);
if(String.valueOf(platformEvent).length() > Limits.getLimitHeapSize()/7) {
system.debug(LoggingLevel.WARN, 'Platform Event Size = ' + String.valueOf(platformEvent).length());
publishPlatformEvent(eventName, objectName, integrationRequest.payload.fields.keySet(), new Set<Id>{sfdcRecordId}, lstOutboundRecordToInsert);
} else {
Database.SaveResult publishedEventResult = EventBus.publish(platformEvent);
if(!publishedEventResult.isSuccess()) {
String errorMessage = '';
for(Database.Error err : publishedEventResult.getErrors()) {
errorMessage += err.getMessage();
system.debug(LoggingLevel.WARN, 'Error returned: ' + err.getStatusCode() + ' - ' + err.getMessage());
}
lstOutboundRecordToInsert.add(insertIntoOutboundQueue(eventName, sfdcRecordId, 'Error', errorMessage));
}
}
}
public void publishPlatformEvent(String eventName, String objectName, Set<String> setOfFields, Set<Id> setOfIds, List<SVMXC__SM_SB_Queue_Outbound__c> lstOutboundRecordToInsert){
List<String> lstOfFields = new List<String>(setOfFields);
lstOfFields.addAll(getSystemFields());
List<Id> lstOfIds = new List<Id>(setOfIds);
Boolean isBatchDelete = false;
String soql = 'Select ' + String.join(lstOfFields, ',') + ' from ' + objectName + ' where ID IN (\'' + String.join(lstOfIds, '\',\'') + '\')';
if(eventName.containsIgnoreCase('delete') || eventName.containsIgnoreCase('remove')){
soql += ' AND isDeleted = true';
isBatchDelete = true;
}
SVMXC__SM_Service_Board__e platformEvent = new SVMXC__SM_Service_Board__e(SVMXC__SM_Event_Type__c = eventName, SVMXC__SM_Object_Name__c = objectName, SVMXC__SM_Payload__c = soql, SVMXC__SM_Callback__c = true, SVMXC__SM_Batch_Delete__c = isBatchDelete);
system.debug(LoggingLevel.WARN, ' SOQL = ' + soql);
Database.SaveResult publishedEventResult = EventBus.publish(platformEvent);
if(!publishedEventResult.isSuccess()) {
String errorMessage = '';
for(Database.Error err : publishedEventResult.getErrors()) {
errorMessage += err.getMessage();
system.debug(LoggingLevel.WARN, 'Error returned: ' + err.getStatusCode() + ' - ' + err.getMessage());
}
lstOutboundRecordToInsert.add(insertIntoOutboundQueue(eventName, null, 'Error', errorMessage + '; soql = ' + soql));
}
}
public IntegrationRequest prepareRequest(String eventName, String objectName, sObject sobj, Set<String> setOfFields) {
system.debug(LoggingLevel.WARN, 'sobj to send = ' + sobj);
IntegrationRequest integrationRequest = new IntegrationRequest();
Payload payloadRequest = new Payload();
payloadRequest.objectName = objectName;
if(objectName == 'Event'){
if(sObj.get('WhatId') != null && Id.valueOf(String.valueOf(sObj.get('WhatId'))).getsObjectType().getDescribe().getName() != 'SVMXC__Service_Order__c')
setOfFields.remove('WhatId');
}else if(objectName == 'SVMXC__SVMX_Event__c'){
if(sObj.get('SVMXC__WhatId__c') != null && Id.valueOf(String.valueOf(sObj.get('SVMXC__WhatId__c'))).getsObjectType().getDescribe().getName() != 'SVMXC__Service_Order__c')
setOfFields.remove('SVMXC__WhatId__c');
}
payloadRequest.fields = buildMapOfFields(sobj, setOfFields);
if(eventName.contains('update')){
payloadRequest.modifiedFields = mapIdToModifiedFields.get((Id)sobj.get('Id'));
}
if(!UserInfo.isMultiCurrencyOrganization()){
payloadRequest.fields.put('CurrencyIsoCode', String.valueOf(UserInfo.getDefaultCurrency()));
}
payloadRequest.systemFields = buildMapOfFields(sobj, getSystemFields());
payloadRequest.criteria = buildPayloadCriteria(sobj, eventName);
integrationRequest.payload = payloadRequest;
system.debug(LoggingLevel.WARN, 'integrationRequest = ' + integrationRequest);
return integrationRequest;
}
public Set<String> getFieldSet(String objectName){
system.debug(LoggingLevel.WARN, 'CPU time at start of getting Field Sets = ' + Limits.getCpuTime());
Set<String> setOfFields = new Set<String>();
setOfFields.add('Id');
if(UserInfo.isMultiCurrencyOrganization()){
setOfFields.add('CurrencyIsoCode');
}
Schema.DescribeSobjectResult result = ((SObject)(Type.forName('Schema.' + objectName).newInstance())).getSObjectType().getDescribe();
Map<String, Schema.FieldSet> mapOfFieldSet = result.fieldSets.getMap();
setOfFields.addAll(getFieldSet(objectName, 'SVMXC__SM_Service_Board_Fields', mapOfFieldSet));
if(objectName == 'Event'){
setOfFields.addAll(getFieldSet(objectName, 'SVMXSB__SB_Service_Board_Fields', mapOfFieldSet));
}
system.debug(LoggingLevel.WARN, 'mapSettings = ' + mapSettings);
if(mapSettings.isEmpty()){
mapSettings = getSBSettings();
}
if(!mapSettings.isEmpty() && mapSettings.containsKey('DCON007_SET003') && mapSettings.get('DCON007_SET003')!= null){
String customFieldSet = String.valueOf(mapSettings.get('DCON007_SET003'));
setOfFields.addAll(getFieldSet(objectName, customFieldSet, mapOfFieldSet));
}
setReferenceFields.add('CreatedById');
setReferenceFields.add('LastModifiedById');
system.debug(LoggingLevel.WARN, 'CPU time at end of getting Field Sets = ' + Limits.getCpuTime() + 'Total fields = ' + setOfFields.size() + '; setReferenceFields = ' + setReferenceFields.size() + '; Set of fields = ' + setOfFields);
return setOfFields;
}
private Set<String> getFieldSet(String objectName, String fieldSetName, Map<String, Schema.FieldSet> mapOfFieldSet){
Set<String> setOfFields = new Set<String>();
FieldSet fsName = mapOfFieldSet.get(fieldSetName);
system.debug(LoggingLevel.WARN, 'Name of Field Set = ' + fsName);
if(fsName != null) {
for(FieldSetMember eachFieldName : fsName.getFields()){
system.debug(LoggingLevel.WARN, 'Each Field in field set = ' + eachFieldName.getfieldPath() + '; Field Type = ' + eachFieldName.getType());
setOfFields.add(eachFieldName.getfieldPath());
if(eachFieldName.getType() == Schema.DisplayType.Reference){
setReferenceFields.add(eachFieldName.getfieldPath());
}
}
}
return setOfFields;
}
private Set<String> getSystemFields(){
Set<String> systemFields = new Set<String>();
systemFields.add('CreatedDate');
systemFields.add('CreatedById');
systemFields.add('LastModifiedDate');
systemFields.add('LastModifiedById');
return systemFields;
}
private Boolean isLimitReached() {
if(Limits.getDMLStatements() < Limits.getLimitDMLStatements() - 2 && Limits.getQueries() < Limits.getLimitQueries() - 2 && Limits.getCpuTime() < Limits.getLimitCpuTime() - 1000 && Limits.getHeapSize() < Limits.getLimitHeapSize() - 1000000){
return false;
} else{
return true;
}
}
private Map<String, String> getSBSettings(){
Map<string,String> mapSettings = new Map<String,String>();
mapSettings = (new SVMXC.COMM_Utils_ManageSettings()).SVMX_getGlobalSettingList(new List<String>{'DCON007_SET001', 'DCON007_SET003'});
return mapSettings;
}
}
SB_Custom_Integration
@RestResource(urlMapping='/custom_integration/v1/*')
global with sharing class SB_Custom_Integration{
@HttpPost
global static void doPost(){
RestRequest request = RestContext.request;
RestResponse response = RestContext.response;
system.debug(LoggingLevel.WARN, 'Heap limit = ' + Limits.getHeapSize() + ', CPU limit = ' + Limits.getCPUTime() + ', SOQL limit = ' + Limits.getQueries() + ', Rows limit = ' + Limits.getQueryRows());
system.debug(LoggingLevel.WARN, 'JSON Request = ' + request.requestBody.toString());
String strURI = request.requestURI;
String strMethodName = strURI.substring(strURI.lastIndexOf('/') + 1);
system.debug(LoggingLevel.WARN, 'Method Name = ' + strMethodName + '; Request URI = ' + strURI);
if(strMethodName == 'txdata'){
try{
SB_Custom_IntegrationHelper.IntegrationRequest req = (SB_Custom_IntegrationHelper.IntegrationRequest)JSON.deserialize(request.requestBody.toString(), SB_Custom_IntegrationHelper.IntegrationRequest.Class);
system.debug(LoggingLevel.WARN, 'Deserialized Request = ' + req);
SB_Custom_IntegrationHelper.isMaxTxn = true;
SB_Custom_IntegrationHelper integrationHelper = new SB_Custom_IntegrationHelper();
SB_Custom_IntegrationHelper.IntegrationResponse integrationResponse = integrationHelper.validateRequest(req);
if(integrationResponse.status != 'Fail'){
integrationResponse = integrationHelper.processRequest(req);
if(integrationResponse.status == 'Error')
response.statusCode = SB_Custom_IntegrationHelper.responseStatusCode;
}else{
response.statusCode = SB_Custom_IntegrationHelper.responseStatusCode;
}
system.debug(LoggingLevel.WARN, 'integrationResponse = ' + integrationResponse);
response.responseBody = Blob.valueOf(JSON.serialize(integrationResponse));
}
catch(Exception ex){
system.debug(LoggingLevel.WARN, 'Exception = ' + ex);
response.statusCode = 400;
}
}
system.debug(LoggingLevel.WARN, 'Heap limit = ' + Limits.getHeapSize() + ',CPU limit = ' + Limits.getCPUTime() + ', SOQL limit = ' + Limits.getQueries() + ', Rows limit = ' + Limits.getQueryRows());
}
}
2. Create a trigger for the Delete event for custom objects. For example, for a custom object that has the API name Custom_Object__c:
trigger SB_Custom_Object_Delete_Trigger on Custom_Object__c (after delete) {
Map<string,String> mapSettings = new Map<String,String>();
mapSettings = (new SVMXC.COMM_Utils_ManageSettings()).SVMX_getGlobalSettingList(new List<String>{'DCON007_SET001', 'DCON007_SET003'});
system.debug(loggingLevel.WARN, 'mapSettings DCON007 = ' + mapSettings);
Boolean isServiceBoardEnabled = false;
if(mapSettings.containsKey('DCON007_SET001') && mapSettings.get('DCON007_SET001') != null) {
isServiceBoardEnabled = Boolean.valueOf(mapSettings.get('DCON007_SET001'));
}
if(isServiceBoardEnabled){
SB_Custom_IntegrationHelper peHelper = new SB_Custom_IntegrationHelper();
peHelper.preparePlatformEvent('delete', 'Custom_Object__c', trigger.oldMap);
}
}
For more information: