This code represents a centralized trigger framework based on concepts from Dan Appleman's 'Advanced Apex Programming' book and also leverages code written in Hari Krishnan's blog post on the topic (https://krishhari.wordpress.com/2013/07/22/an-architecture-framework-to-handle-triggers-in-the-force-com-platform/). This code is one piece of a lar…
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
This code represents a centralized trigger framework based on concepts from Dan Appleman's 'Advanced Apex Programming' book | |
and also leverages code written in Hari Krishnan's blog post on the topic (https://krishhari.wordpress.com/2013/07/22/an-architecture-framework-to-handle-triggers-in-the-force-com-platform/). | |
This code is one piece of a larger, open-sourced development framework available here: http://bit.ly/1mZE9b7 |
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
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Account Trigger Handler for After Update Trigger Context | |
* @see TriggerHandlerBase | |
*/ | |
public class AccountAfterUpdateTriggerHandler extends TriggerHandlerBase { | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Main Entry point for trigger handling | |
* @param TriggerParameters Contains the trigger parameters | |
*/ | |
public override void mainEntry(TriggerParameters tp) { | |
DiagnosticsInstrumentation.Push('AccountAfterUpdateTriggerHandler.mainEntry'); | |
process((List<Account>)tp.newList); | |
DiagnosticsInstrumentation.Pop(); | |
} | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Process Accounts | |
* @param List A list of updated accounts | |
*/ | |
private void process(List<Account> listNewAccounts) { | |
DiagnosticsInstrumentation.Push('AccountAfterUpdateTriggerHandler.mainEntry'); | |
sObjectsToUpdate.putAll(AccountHelper.updateWebsite(listNewAccounts)); | |
DiagnosticsInstrumentation.Pop(); | |
} | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description In Progress Entry point for trigger handling | |
* @param TriggerParameters Contains the trigger parameters | |
*/ | |
public override void inProgressEntry(TriggerParameters tp) { | |
DiagnosticsInstrumentation.Push('AccountAfterUpdateTriggerHandler.inProgressEntry'); | |
System.debug('This is an example for reentrant code...'); | |
DiagnosticsInstrumentation.Pop(); | |
} | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Batch Apex Entry point for trigger handling | |
* @param TriggerParameters Contains the trigger parameters | |
*/ | |
/* | |
public override void batchEntry(TriggerParameters tp) { | |
DiagnosticsInstrumentation.Push('AccountAfterUpdateTriggerHandler.batchEntry'); | |
System.debug('This is an example for batch entry code...'); | |
DiagnosticsInstrumentation.Pop(); | |
} | |
*/ | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Future Entry point for trigger handling | |
* @param TriggerParameters Contains the trigger parameters | |
*/ | |
/* | |
public override void futureEntry(TriggerParameters tp) { | |
DiagnosticsInstrumentation.Push('AccountAfterUpdateTriggerHandler.futureEntry'); | |
System.debug('This is an example for future entry code...'); | |
DiagnosticsInstrumentation.Pop(); | |
} | |
*/ | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Scheduled Apex Entry point for trigger handling | |
* @param TriggerParameters Contains the trigger parameters | |
*/ | |
/* | |
public override void scheduledEntry(TriggerParameters tp) { | |
DiagnosticsInstrumentation.Push('AccountAfterUpdateTriggerHandler.scheduledEntry'); | |
System.debug('This is an example for scheduled entry code...'); | |
DiagnosticsInstrumentation.Pop(); | |
} | |
*/ | |
} |
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
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Helper Methods for Account Trigger Handling | |
*/ | |
public with sharing class AccountHelper { | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Update website field of accounts to the Salesforce homepage | |
* @param List A list of accounts that should have their website field updated | |
* @param Map A map of accounts with their website field set to the Salesforce homepage | |
*/ | |
public static map<Id,SObject> updateWebsite(list<Account> listNewAccounts){ | |
map<Id,SObject> resultMap = new map<Id,SObject>(); | |
for(Account acct : listNewAccounts) { | |
Account newAccount = new Account(); | |
newAccount.Id = acct.Id; | |
newAccount.Website = 'www.salesforce.com'; | |
resultMap.put(newAccount.Id, newAccount); | |
} | |
return resultMap; | |
} | |
} |
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
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description This class extends the TriggerDispatcherBase to provide the dispatching functionality for the trigger actions | |
* on the Account object. The event handlers support allowing and preventing actions for reentrant scenarios. | |
* This is controlled by the flag isBeforeXxxxx and isAfterXxxxx member variables. These variables need to be set | |
* to true before invoking the handlers and set to false after the invocation of the handlers. Resetting is MUST | |
* as otherwise unit tests MAY fail. The actual actions should be placed in the handlers (in a separate class). | |
*/ | |
public class AccountTriggerDispatcher extends TriggerDispatcherBase { | |
/** Stores if before insert handler is processing */ | |
private static Boolean isBeforeInsertProcessing = false; | |
/** Stores if before update handler is processing */ | |
private static Boolean isBeforeUpdateProcessing = false; | |
/** Stores if before delete handler is processing */ | |
private static Boolean isBeforeDeleteProcessing = false; | |
/** Stores if after insert handler is processing */ | |
private static Boolean isAfterInsertProcessing = false; | |
/** Stores if after update handler is processing */ | |
private static Boolean isAfterUpdateProcessing = false; | |
/** Stores if after delete handler is processing */ | |
private static Boolean isAfterDeleteProcessing = false; | |
/** Stores if after undelete handler is processing */ | |
private static Boolean isAfterUnDeleteProcessing = false; | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called by the trigger framework to carry out the actions before the records are inserted. If there is an | |
* existing call running on the same context, the rentrant call will utilize the handler that was created | |
* in the original call. | |
* @param TriggerParameters Contains the trigger parameters | |
*/ | |
public virtual override void beforeInsert(TriggerParameters tp) { | |
if(!isBeforeInsertProcessing) { | |
isBeforeInsertProcessing = true; | |
execute(new AccountBeforeInsertTriggerHandler(), tp, TriggerParameters.TriggerEvent.beforeInsert); | |
isBeforeInsertProcessing = false; | |
} | |
else execute(null, tp, TriggerParameters.TriggerEvent.beforeInsert); | |
} | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called by the trigger framework to carry out the actions before the records are updated. If there is an | |
* existing call running on the same context, the rentrant call will utilize the handler that was created | |
* in the original call. | |
* @param TriggerParameters Contains the trigger parameters | |
*/ | |
public virtual override void beforeUpdate(TriggerParameters tp) { | |
if(!isBeforeUpdateProcessing) { | |
isBeforeUpdateProcessing = true; | |
execute(new AccountBeforeUpdateTriggerHandler(), tp, TriggerParameters.TriggerEvent.beforeUpdate); | |
isBeforeUpdateProcessing = false; | |
} | |
else execute(null, tp, TriggerParameters.TriggerEvent.beforeUpdate); | |
} | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called by the trigger framework to carry out the actions before the records are deleted. If there is an | |
* existing call running on the same context, the rentrant call will utilize the handler that was created | |
* in the original call. | |
* @param TriggerParameters Contains the trigger parameters | |
*/ | |
public virtual override void beforeDelete(TriggerParameters tp) { | |
if(!isBeforeDeleteProcessing) { | |
isBeforeDeleteProcessing = true; | |
execute(new AccountBeforeDeleteTriggerHandler(), tp, TriggerParameters.TriggerEvent.beforeDelete); | |
isBeforeDeleteProcessing = false; | |
} | |
else execute(null, tp, TriggerParameters.TriggerEvent.beforeDelete); | |
} | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called by the trigger framework to carry out the actions after the records are inserted. If there is an | |
* existing call running on the same context, the rentrant call will utilize the handler that was created | |
* in the original call. | |
* @param TriggerParameters Contains the trigger parameters | |
*/ | |
public virtual override void afterInsert(TriggerParameters tp) { | |
if(!isAfterInsertProcessing) { | |
isAfterInsertProcessing = true; | |
execute(new AccountAfterInsertTriggerHandler(), tp, TriggerParameters.TriggerEvent.afterInsert); | |
isAfterInsertProcessing = false; | |
} | |
else execute(null, tp, TriggerParameters.TriggerEvent.afterInsert); | |
} | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called by the trigger framework to carry out the actions after the records are updated. If there is an | |
* existing call running on the same context, the rentrant call will utilize the handler that was created | |
* in the original call. | |
* @param TriggerParameters Contains the trigger parameters | |
*/ | |
public virtual override void afterUpdate(TriggerParameters tp) { | |
if(!isAfterUpdateProcessing) { | |
isAfterUpdateProcessing = true; | |
execute(new AccountAfterUpdateTriggerHandler(), tp, TriggerParameters.TriggerEvent.afterUpdate); | |
isAfterUpdateProcessing = false; | |
} | |
else execute(null, tp, TriggerParameters.TriggerEvent.afterUpdate); | |
} | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called by the trigger framework to carry out the actions after the records are deleted. If there is an | |
* existing call running on the same context, the rentrant call will utilize the handler that was created | |
* in the original call. | |
* @param TriggerParameters Contains the trigger parameters | |
*/ | |
public virtual override void afterDelete(TriggerParameters tp) { | |
if(!isAfterDeleteProcessing) { | |
isAfterDeleteProcessing = true; | |
execute(new AccountAfterDeleteTriggerHandler(), tp, TriggerParameters.TriggerEvent.afterDelete); | |
isAfterDeleteProcessing = false; | |
} | |
else execute(null, tp, TriggerParameters.TriggerEvent.afterDelete); | |
} | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called by the trigger framework to carry out the actions after the records are undeleted. If there is an | |
* existing call running on the same context, the rentrant call will utilize the handler that was created | |
* in the original call. | |
* @param TriggerParameters Contains the trigger parameters | |
*/ | |
public virtual override void afterUnDelete(TriggerParameters tp) { | |
if(!isAfterUnDeleteProcessing) { | |
isAfterUnDeleteProcessing = true; | |
execute(new AccountAfterUnDeleteTriggerHandler(), tp, TriggerParameters.TriggerEvent.afterUndelete); | |
isAfterUnDeleteProcessing = false; | |
} | |
else execute(null, tp, TriggerParameters.TriggerEvent.afterUndelete); | |
} | |
} |
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
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Defines the interface for the trigger dispatching architecture. | |
*/ | |
public interface ITriggerDispatcher { | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called by the trigger framework to carry out the actions before the bulk operations. | |
*/ | |
void bulkBefore(); | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called by the trigger framework to carry out the actions after the bulk operations. | |
*/ | |
void bulkAfter(); | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called by the trigger framework to carry out the actions after completing the bulk operations. | |
*/ | |
void andFinally(); | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called by the trigger framework to carry out the actions before the records are inserted. | |
* @param TriggerParameters Contains the trigger parameters which includes the records that is getting inserted. | |
*/ | |
void beforeInsert(TriggerParameters tp); | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called by the trigger framework to carry out the actions before the records are updated. | |
* @param TriggerParameters Contains the trigger parameters which includes the records that is getting updated. | |
*/ | |
void beforeUpdate(TriggerParameters tp); | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called by the trigger framework to carry out the actions before the records are deleted. | |
* @param TriggerParameters Contains the trigger parameters which includes the records that is getting deleted. | |
*/ | |
void beforeDelete(TriggerParameters tp); | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called by the trigger framework to carry out the actions after the records are inserted. | |
* @param TriggerParameters Contains the trigger parameters which includes the records that got inserted. | |
*/ | |
void afterInsert(TriggerParameters tp); | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called by the trigger framework to carry out the actions before the records are updated. | |
* @param TriggerParameters Contains the trigger parameters which includes the records that got updated. | |
*/ | |
void afterUpdate(TriggerParameters tp); | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called by the trigger framework to carry out the actions after the records got deleted. | |
* @param TriggerParameters Contains the trigger parameters which includes the records that got deleted. | |
*/ | |
void afterDelete(TriggerParameters tp); | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called by the trigger framework to carry out the actions after the records are undeleted. | |
* @param TriggerParameters Contains the trigger parameters which includes the records that got undeleted. | |
*/ | |
void afterUnDelete(TriggerParameters tp); | |
} |
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
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Defines the interface for trigger handlers. Logic for the first time events are placed under the mainEntry | |
* method and the logic for the subsequent events raised on the same transaction (reentrant) are placed under | |
* the inProgressEntry method. | |
*/ | |
public interface ITriggerHandler { | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called for the first time in the execution context. | |
* @param TriggerParameters The trigger parameters such as the list of records before and after the update. | |
*/ | |
void mainEntry(TriggerParameters tp); | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called for the subsequent times in the same execution context. | |
* @param TriggerParameters The trigger parameters such as the list of records before and after the update. | |
*/ | |
void inProgressEntry(TriggerParameters tp); | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called by batch apex | |
* @param TriggerParameters The trigger parameters such as the list of records before and after the update. | |
*/ | |
void batchEntry(TriggerParameters tp); | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called by asynchronous function | |
* @param TriggerParameters The trigger parameters such as the list of records before and after the update. | |
*/ | |
void futureEntry(TriggerParameters tp); | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called by scheduled apex | |
* @param TriggerParameters The trigger parameters such as the list of records before and after the update. | |
*/ | |
void scheduledEntry(TriggerParameters tp); | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Updates the objects, if any. | |
*/ | |
void updateObjects(); | |
} |
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
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description This class implements the ITriggerDispatcher and acts as an adapter to avoid implementing all the | |
* ITriggerDispatcher methods. | |
*/ | |
public virtual class TriggerDispatcherBase implements ITriggerDispatcher { | |
/** Stores before insert handler for trigger event */ | |
private static ITriggerHandler beforeInserthandler; | |
/** Stores before update handler for trigger event */ | |
private static ITriggerHandler beforeUpdatehandler; | |
/** Stores before delete handler for trigger event */ | |
private static ITriggerHandler beforeDeleteHandler; | |
/** Stores after insert handler for trigger event */ | |
private static ITriggerHandler afterInserthandler; | |
/** Stores after update handler for trigger event */ | |
private static ITriggerHandler afterUpdatehandler; | |
/** Stores after delete handler for trigger event */ | |
private static ITriggerHandler afterDeleteHandler; | |
/** Stores after undelete handler for trigger event */ | |
private static ITriggerHandler afterUndeleteHandler; | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description This method is called prior to execution of a before trigger event. If you want | |
* to load any lookup data or cache the data, then this is the place that you need | |
* to put your code. | |
*/ | |
public virtual void bulkBefore() {} | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description This method is called prior to execution of an after trigger event. | |
*/ | |
public virtual void bulkAfter() {} | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description This method is called for records to be inserted during a BEFORE trigger. | |
* @param TriggerParameters Contains the trigger parameters | |
*/ | |
public virtual void beforeInsert(TriggerParameters tp) {} | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description This method is called for records to be updated during a BEFORE trigger. | |
* @param TriggerParameters Contains the trigger parameters | |
*/ | |
public virtual void beforeUpdate(TriggerParameters tp) {} | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description This method is called for records to be deleted during a BEFORE trigger. | |
* @param TriggerParameters Contains the trigger parameters | |
*/ | |
public virtual void beforeDelete(TriggerParameters tp) {} | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description This method is called for records inserted during an AFTER trigger. Always put field validation | |
* in the 'After' methods in case another trigger has modified any values. The record is 'read only' | |
* at this point. | |
* @param TriggerParameters Contains the trigger parameters | |
*/ | |
public virtual void afterInsert(TriggerParameters tp) {} | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description This method is called iteratively for each record updated during an AFTER trigger. | |
* @param TriggerParameters Contains the trigger parameters | |
*/ | |
public virtual void afterUpdate(TriggerParameters tp) {} | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description This method is called iteratively for each record deleted during an AFTER trigger. | |
* @param TriggerParameters Contains the trigger parameters | |
*/ | |
public virtual void afterDelete(TriggerParameters tp) {} | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description This method is called prior to execution of a AFTER UNDELETE trigger. | |
* @param TriggerParameters Contains the trigger parameters | |
*/ | |
public virtual void afterUnDelete(TriggerParameters tp) {} | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description This method is called at the end of a before trigger event | |
*/ | |
public virtual void andFinally() {} | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called by the event handlers. If this is the first call in the context, then this method will create a new | |
* instance of the appropriate handler and execute the mainEntry method. If there is an existing call runing | |
* on the same context, then this method will use the existing handler instance created by the original call | |
* and execute the inProgressEntry method. | |
* @param ITriggerHandler The trigger handler instance. The dispatcher need to pass an instance of the trigger handler, such | |
* as AccountAfterInsertTriggerHandler if this is the first call in a given context. If it is retry, | |
* then the dispatcher will need to pass null. | |
* @param TriggerParameters The trigger parameters passed by the framework. | |
* @param TriggerParameters.TriggerEvent The trigger event. | |
*/ | |
protected void execute(ITriggerHandler handlerInstance, TriggerParameters tp, TriggerParameters.TriggerEvent tEvent) { | |
if(handlerInstance != null) { | |
if(tEvent == TriggerParameters.TriggerEvent.beforeInsert) | |
beforeInsertHandler = handlerInstance; | |
if(tEvent == TriggerParameters.TriggerEvent.beforeUpdate) | |
beforeUpdateHandler = handlerInstance; | |
if(tEvent == TriggerParameters.TriggerEvent.beforeDelete) | |
beforeDeleteHandler = handlerInstance; | |
if(tEvent == TriggerParameters.TriggerEvent.afterInsert) | |
afterInsertHandler = handlerInstance; | |
if(tEvent == TriggerParameters.TriggerEvent.afterUpdate) | |
afterUpdateHandler = handlerInstance; | |
if(tEvent == TriggerParameters.TriggerEvent.afterDelete) | |
afterDeleteHandler = handlerInstance; | |
if(tEvent == TriggerParameters.TriggerEvent.afterUnDelete) | |
afterUndeleteHandler = handlerInstance; | |
if (System.isBatch()){ | |
handlerInstance.batchEntry(tp); | |
} | |
else if (System.isFuture()){ | |
handlerInstance.futureEntry(tp); | |
} | |
else if (System.isScheduled()){ | |
handlerInstance.scheduledEntry(tp); | |
} | |
else { | |
handlerInstance.mainEntry(tp); | |
} | |
handlerInstance.updateObjects(); | |
} | |
else { | |
if(tEvent == TriggerParameters.TriggerEvent.beforeInsert) | |
beforeInsertHandler.inProgressEntry(tp); | |
if(tEvent == TriggerParameters.TriggerEvent.beforeUpdate) | |
beforeUpdateHandler.inProgressEntry(tp); | |
if(tEvent == TriggerParameters.TriggerEvent.beforeDelete) | |
beforeDeleteHandler.inProgressEntry(tp); | |
if(tEvent == TriggerParameters.TriggerEvent.afterInsert) | |
afterInsertHandler.inProgressEntry(tp); | |
if(tEvent == TriggerParameters.TriggerEvent.afterUpdate) | |
afterUpdateHandler.inProgressEntry(tp); | |
if(tEvent == TriggerParameters.TriggerEvent.afterDelete) | |
afterDeleteHandler.inProgressEntry(tp); | |
if(tEvent == TriggerParameters.TriggerEvent.afterUnDelete) | |
afterUndeleteHandler.inProgressEntry(tp); | |
} | |
} | |
} |
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
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description This factory creates the correct dispatcher and dispatches the trigger event(s) to the appropriate | |
* event handler(s). The dispatchers are automatically created using the Type API, hence dispatcher | |
* registration is not required for each dispatchers. | |
*/ | |
public with sharing class TriggerFactory | |
{ | |
/** Determines if a custom exception should be forced for test purposes */ | |
@TestVisible private static Boolean FakeException = false; | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Creates the appropriate dispatcher and dispatches the trigger event to the dispatcher's event handler method. | |
* @param Schema.sObjectType Object type to process (SObject.sObjectType) | |
*/ | |
public static void createTriggerDispatcher(Schema.sObjectType soType) | |
{ | |
if (!ConfigService.KillSwitchEnabled && !ConfigService.BypassObjects.contains(soType)){ | |
// Initial Push to Diagnostic Log | |
String beforeOrAfter = 'Before '; | |
if (Trigger.isAfter) | |
beforeOrAfter = 'After '; | |
String dmlState = 'Insert'; | |
if (Trigger.isUpdate){ | |
dmlState = 'Update'; | |
} | |
else if (Trigger.isDelete){ | |
dmlState = 'Delete'; | |
} | |
else if (Trigger.isUndelete){ | |
dmlState = 'Undelete'; | |
} | |
String soTypeName = soType.getDescribe().getLocalName(); | |
DiagnosticsInstrumentation.Push(beforeOrAfter + dmlState + ' Trigger for ' + soTypeName + ' Object'); | |
try{ | |
ITriggerDispatcher dispatcher = getTriggerDispatcher(soType); | |
if (dispatcher == null || FakeException) | |
throw new CustomException('No Trigger dispatcher registered for Object Type: ' + soType); | |
execute(dispatcher,soTypeName); | |
// Final Pop from Diagnostic Log | |
DiagnosticsInstrumentation.Pop(); | |
} | |
catch(Exception ex){ | |
DiagnosticsInstrumentation.DebugException(ex); | |
} | |
} | |
} | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Dispatches to the dispatcher's event handlers. | |
* @param ITriggerDispatcher A Trigger dispatcher that dispatches to the appropriate handlers | |
*/ | |
private static void execute(ITriggerDispatcher dispatcher, String soTypeName) | |
{ | |
TriggerParameters tp = new TriggerParameters(Trigger.old, Trigger.new, Trigger.oldMap, Trigger.newMap, | |
Trigger.isBefore, Trigger.isAfter, Trigger.isDelete, | |
Trigger.isInsert, Trigger.isUpdate, Trigger.isUnDelete, Trigger.isExecuting); | |
// Handle before trigger events | |
if (Trigger.isBefore) { | |
DiagnosticsInstrumentation.Push(soTypeName + 'TriggerDispatcher.bulkBefore'); | |
dispatcher.bulkBefore(); | |
DiagnosticsInstrumentation.Pop(); | |
if (Trigger.isDelete){ | |
DiagnosticsInstrumentation.Push(soTypeName + 'TriggerDispatcher.beforeDelete'); | |
dispatcher.beforeDelete(tp); | |
DiagnosticsInstrumentation.Pop(); | |
} | |
else if (Trigger.isInsert){ | |
DiagnosticsInstrumentation.Push(soTypeName + 'TriggerDispatcher.beforeInsert'); | |
dispatcher.beforeInsert(tp); | |
DiagnosticsInstrumentation.Pop(); | |
} | |
else if (Trigger.isUpdate){ | |
DiagnosticsInstrumentation.Push(soTypeName + 'TriggerDispatcher.beforeUpdate'); | |
dispatcher.beforeUpdate(tp); | |
DiagnosticsInstrumentation.Pop(); | |
} | |
} | |
else // Handle after trigger events | |
{ | |
DiagnosticsInstrumentation.Push(soTypeName + 'TriggerDispatcher.bulkAfter'); | |
dispatcher.bulkAfter(); | |
DiagnosticsInstrumentation.Pop(); | |
if (Trigger.isDelete){ | |
DiagnosticsInstrumentation.Push(soTypeName + 'TriggerDispatcher.afterDelete'); | |
dispatcher.afterDelete(tp); | |
DiagnosticsInstrumentation.Pop(); | |
} | |
else if (Trigger.isInsert){ | |
DiagnosticsInstrumentation.Push(soTypeName + 'TriggerDispatcher.afterInsert'); | |
dispatcher.afterInsert(tp); | |
DiagnosticsInstrumentation.Pop(); | |
} | |
else if (Trigger.isUpdate){ | |
DiagnosticsInstrumentation.Push(soTypeName + 'TriggerDispatcher.afterUpdate'); | |
dispatcher.afterUpdate(tp); | |
DiagnosticsInstrumentation.Pop(); | |
} | |
else if (Trigger.isUnDelete){ | |
DiagnosticsInstrumentation.Push(soTypeName + 'TriggerDispatcher.afterUnDelete'); | |
dispatcher.afterUnDelete(tp); | |
DiagnosticsInstrumentation.Pop(); | |
} | |
} | |
DiagnosticsInstrumentation.Push(soTypeName + 'TriggerDispatcher.andFinally'); | |
dispatcher.andFinally(); | |
DiagnosticsInstrumentation.Pop(); | |
} | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Gets the appropriate dispatcher based on the SObject. It constructs the instance of the dispatcher | |
* dynamically using the Type API. The name of the dispatcher has to follow this format: | |
* <ObjectName>TriggerDispatcher. For e.g. for the Account object, the dispatcher has to be named | |
* as AccountTriggerDispatcher. For the custom object Custom__c, the name should be CustomTriggerDispatcher. | |
* @param Schema.sObjectType Object type to create the dispatcher | |
* @return ITriggerDispatcher A trigger dispatcher if one exists, otherwise null. | |
*/ | |
private static ITriggerDispatcher getTriggerDispatcher(Schema.sObjectType soType) | |
{ | |
String originalTypeName = soType.getDescribe().getName(); | |
String dispatcherTypeName = null; | |
if (originalTypeName.toLowerCase().endsWith('__c')) { | |
Integer index = originalTypeName.toLowerCase().indexOf('__c'); | |
dispatcherTypeName = originalTypeName.substring(0, index).replace('_','') + 'TriggerDispatcher'; | |
} | |
else | |
dispatcherTypeName = originalTypeName.replace('_','') + 'TriggerDispatcher'; | |
Type obType = Type.forName(dispatcherTypeName); | |
ITriggerDispatcher dispatcher = (obType == null) ? null : (ITriggerDispatcher)obType.newInstance(); | |
return dispatcher; | |
} | |
} |
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
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description This class implements the ITriggerHandler to provide abstract/virtual methods for the interface methods | |
* and so that the trigger handlers need to implement only the method that they have to. The only exception | |
* is the mainEntry, which is mandatory for the trigger handlers to implement. | |
*/ | |
public abstract class TriggerHandlerBase implements ITriggerHandler { | |
/** Map of sobjects to update at the end of the trigger event */ | |
protected Map<Id, SObject> sObjectsToUpdate = new Map<Id, SObject>(); | |
/** Determines if a concurrency error should be simulated for test purposes */ | |
@TestVisible private static List<Boolean> SimulateConcurrencyError; | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called for the first time in the execution context. The trigger handlers need to implement | |
* this method. | |
* @param TriggerParameters The trigger parameters such as the list of records before and after the update. | |
*/ | |
public abstract void mainEntry(TriggerParameters tp); | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called for the subsequent times in the same execution context. The trigger handlers can choose | |
* to ignore if they don't need the reentrant feature. | |
* @param TriggerParameters The trigger parameters such as the list of records before and after the update. | |
*/ | |
public virtual void inProgressEntry(TriggerParameters tp) { | |
} | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called by batch apex. The trigger handlers can choose to ignore this method if they | |
* want batch entries to behave no differently than main entries. | |
* @param TriggerParameters The trigger parameters such as the list of records before and after the update. | |
*/ | |
public virtual void batchEntry(TriggerParameters tp) { | |
mainEntry(tp); | |
} | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called by asynchronous function. The trigger handlers can choose to ignore this method if they | |
* want future entries to behave no differently than main entries. | |
* @param TriggerParameters The trigger parameters such as the list of records before and after the update. | |
*/ | |
public virtual void futureEntry(TriggerParameters tp) { | |
mainEntry(tp); | |
} | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Called by scheduled apex. The trigger handlers can choose to ignore this method if they | |
* want scheduled entries to behave no differently than main entries. | |
* @param TriggerParameters The trigger parameters such as the list of records before and after the update. | |
*/ | |
public virtual void scheduledEntry(TriggerParameters tp) { | |
mainEntry(tp); | |
} | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Updates the objects (if any). Concurrency errors will be silently logged while any other errors will throw an exception. | |
*/ | |
public virtual void updateObjects() { | |
if(sObjectsToUpdate.size() > 0){ | |
List<Database.Saveresult> dmlResults = Database.Update(sObjectsToUpdate.values(), false); | |
Map<Id, SObject> concurrencyFailures = new Map<Id, SObject>(); | |
List<Database.Error> updateFailures = new List<Database.Error>(); | |
for(Integer x = 0; x< sObjectsToUpdate.size(); x++) { | |
Database.Saveresult sr = dmlResults[x]; | |
if(!sr.isSuccess() || (SimulateConcurrencyError!=null && SimulateConcurrencyError[x])) | |
{ | |
for(Database.Error err : sr.getErrors()) | |
{ | |
if(err.getStatusCode() == StatusCode.UNABLE_TO_LOCK_ROW) | |
{ | |
concurrencyFailures.put(sObjectsToUpdate.values()[x].Id,sObjectsToUpdate.values()[x]); | |
} | |
else{ | |
updateFailures.add(err); | |
} | |
} | |
if(SimulateConcurrencyError!=null && SimulateConcurrencyError[x]) concurrencyFailures.put(sObjectsToUpdate.values()[x].Id,sObjectsToUpdate.values()[x]); | |
} | |
} | |
if(concurrencyFailures.size()>0) { | |
// Log Concurrency Error | |
String errorMessage = 'Concurrency failure on records ' + String.Join(new List<ID>(concurrencyFailures.keyset()),','); | |
DiagnosticsInstrumentation.Debug(errorMessage); | |
// Attempt Recovery | |
AsyncRequest__c ar = new AsyncRequest__c(AsyncType__c = RepeatingBatch.TYPE_DEFAULT,ScheduledTime__c = System.now(),Params__c=String.Join(new List<ID>(concurrencyFailures.keyset()),',')); | |
insert ar; | |
ScheduleHandler.StartScheduler(); | |
} | |
if(updateFailures.size()>0) { | |
throw new CustomException(updateFailures[0].getMessage()); | |
} | |
} | |
} | |
} |
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
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description This class holds the trigger parameters. | |
*/ | |
public class TriggerParameters { | |
/** An enum that represents the trigger event */ | |
public Enum TriggerEvent { beforeInsert, beforeUpdate, beforeDelete, afterInsert, afterUpdate, afterDelete, afterUndelete } | |
/** Current trigger event */ | |
public TriggerEvent tEvent; | |
/** Stores Trigger.old */ | |
public List<SObject> oldList { get; private set; } | |
/** Stores Trigger.new */ | |
public List<SObject> newList { get; private set; } | |
/** Stores Trigger.oldMap */ | |
public Map<Id, SObject> oldMap { get; private set; } | |
/** Stores Trigger.newMap */ | |
public Map<Id, SObject> newMap { get; private set; } | |
/** SObject for currently executing trigger */ | |
public String triggerObject { get; private set; } | |
/** Stores execution state of trigger */ | |
public Boolean isExecuting { get; private set; } | |
/** | |
* @author Scott Covert | |
* @date 11/9/2014 | |
* @description Constructs the TriggerParameter object. | |
* @param List A list of records with the state of 'before' event. | |
* @param List A list of records with the state of 'after' event. | |
* @param Map A map of records with the state of 'before' event. | |
* @param Map A map of records with the state of 'after' event. | |
* @param Boolean A flag to indicate 'isBefore' event. | |
* @param Boolean A flag to indicate 'isAfter' event. | |
* @param Boolean A flag to indicate 'isDelete' event. | |
* @param Boolean A flag to indicate 'isInsert' event. | |
* @param Boolean A flag to indicate 'isUpdate' event. | |
* @param Boolean A flag to indicate 'isUnDelete' event. | |
* @param Boolean A flag to indicate 'isExecuting'. | |
*/ | |
public TriggerParameters(List<SObject> olist, List<SObject> nlist, Map<Id, SObject> omap, Map<Id, SObject> nmap, | |
Boolean ib, Boolean ia, Boolean id, Boolean ii, Boolean iu, Boolean iud, Boolean ie) { | |
this.oldList = olist; | |
this.newList = nlist; | |
this.oldMap = omap; | |
this.newMap = nmap; | |
this.triggerObject = SObjectService.TypeName((this.oldList != null && this.oldList.size() > 0) ? this.oldList[0] : this.newList[0]); | |
if (ib & ii) tEvent = TriggerEvent.beforeInsert; | |
else if (ib && iu) tEvent = TriggerEvent.beforeUpdate; | |
else if (ib && id) tEvent = TriggerEvent.beforeDelete; | |
else if (ia && ii) tEvent = TriggerEvent.afterInsert; | |
else if (ia && iu) tEvent = TriggerEvent.afterUpdate; | |
else if (ia && id) tEvent = TriggerEvent.afterDelete; | |
else if (ia && iud) tEvent = TriggerEvent.afterUndelete; | |
isExecuting = ie; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment