I have a trigger framework based on the work by Hari Krishnan: An architecture framework to handle triggers on the Force.com platform, however I have functionality on After Update in wich a trigger dispatcher on Accounts creates an instance of an After Update handler for Accounts where a Helper class is called to process the newMap to be processed. Basically the logic here is:
IF NOT a batch process the following:
- IF Account.PersonEmail is NOT NULL and Account.Phoenix_People_Id__pc is NULL THEN call an Async Batch Process to make a callout to an endpoint and populate the Account.Phoenix_People_Id__pc on the specified account records.
However this results in the batch process being executed twice! My thinking here is that the first time the update occurs and the condition is met to call the batch process, then the After Update for the Batch shouldnt make another call (I hope this makes sense).
Below is the classes involved in the process
Account Trigger
trigger AccountTrigger on Account (after insert, after update) {
TriggerFactory.createTriggerDispatcher(Account.sObjectType);
}
TriggerFactory
/**
* 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.
*
* @author Nelson Chisoko
* @created 2019/07/30
*/
public with sharing class TriggerFactory {
/**
* 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) {
ITriggerDispatcher dispatcher = getTriggerDispatcher(soType);
if (dispatcher == null)
throw new TriggerException('No Trigger dispatcher registered for Object Type: ' + soType);
execute(dispatcher);
}
/**
* Dispatches to the dispatcher's event handlers.
*
* @param ITriggerDispatcher A Trigger dispatcher that dispatches to the appropriate handlers
*/
private static void execute(ITriggerDispatcher dispatcher) {
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) {
dispatcher.bulkBefore();
} else { // Handle after trigger events
dispatcher.bulkAfter();
if (Trigger.isInsert)
dispatcher.afterInsert(tp);
else if (Trigger.isUpdate)
dispatcher.afterUpdate(tp);
}
dispatcher.andFinally();
}
/**
* 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 Feedback__c object, the dispatcher has to be named
* as FeedbackTriggerDispatcher.
*
* @param Schema.sObjectType Object type to create the dispatcher
*
* @return ITriggerDispatcher A trigger dispatcher if one exists or 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) + 'TriggerDispatcher';
} else dispatcherTypeName = originalTypeName + 'TriggerDispatcher';
Type obType = Type.forName(dispatcherTypeName);
ITriggerDispatcher dispatcher = (obType == null) ? null : (ITriggerDispatcher)obType.newInstance();
return dispatcher;
}
}
AccountTriggerDispatcher
/**
* 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).
*
* @author Nelson Chisoko
* @created 2019/07/31
*/
public class AccountTriggerDispatcher extends TriggerDispatcherBase {
private static Boolean isAfterInsertProcessing = false;
private static Boolean isAfterUpdateProcessing = false;
/**
* Called by the trigger framework to carry out the actions after the record(s) are created. 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 which includes the record(s) that got created.
*/
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);
}
/**
* 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 which includes the record(s) that got updated.
*/
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);
}
}
TriggerDispatcherBase
/**
* This class implements the ITriggerDispatcher and acts as an adapter to avoid implementing all the
* ITriggerDispatcher methods.
*
* @author Nelson Chisoko
* @created 2019/07/30
*/
public virtual class TriggerDispatcherBase implements ITriggerDispatcher {
private static ITriggerHandler afterInserthandler;
private static ITriggerHandler afterUpdatehandler;
/**
* 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() {}
/**
* This method is called prior to execution of an after trigger event.
*/
public virtual void bulkAfter() {}
/**
* 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.
*/
public virtual void afterInsert(TriggerParameters tp) {}
/**
* This method is called iteratively for each record updated during an AFTER trigger.
*/
public virtual void afterUpdate(TriggerParameters tp) {}
public virtual void andFinally() {}
/**
* 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.afterInsert)
afterInsertHandler = handlerInstance;
if (tEvent == TriggerParameters.TriggerEvent.afterUpdate)
afterUpdateHandler = handlerInstance;
handlerInstance.mainEntry(tp);
}
}
}
ITriggerDispatcher
/**
* The interface for the trigger dispatching architecture.
*
* @author Nelson Chisoko
* @created 2019/07/30
*/
public interface ITriggerDispatcher {
/**
* Called by the trigger framework to carry out the actions before the bulk operations.
*/
void bulkBefore();
/**
* Called by the trigger framework to carry out the actions after the bulk operations.
*/
void bulkAfter();
/**
* Called by the trigger framework to carry out the actions after completing the bulk operations.
*/
void andFinally();
/**
* 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);
/**
* 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);
}
AccountAfterUpdateTriggerHandler
/**
* After Update Trigger handler class for Accounts
*
* @author Nelson Chisoko
* @created 2019/07/31
*/
public class AccountAfterUpdateTriggerHandler extends TriggerHandlerBase {
public override void mainEntry(TriggerParameters tp) {
AccountHelper.updateRetrievePhoenixPersonDetails((Map<Id, Account>)tp.newMap);
}
}
AccountHelper
/**
* Helper class for Account trigger handlers
*
* @author Nelson Chisoko
* @created 2019/07/31
*/
public class AccountHelper {
public static Id updateRetrievePhoenixPersonDetails(Map<Id, Account> accountsMap) {
Set<Id> idsToProcess = new Set<Id>();
Id batchProcessId;
if (!System.isBatch()) {
for (Account a : accountsMap.values()) {
if (a.PersonEmail != null && a.Phoenix_People_Id__pc == null) {
idsToProcess.add(a.Id);
}
}
if (!idsToProcess.isEmpty()) {
batchProcessId = AsyncApexFramework.submitBatch(new GetPhoenixPersonDetailsBatch(idsToProcess), 100, null, false);
AsyncApexFramework.flush();
}
}
return batchProcessId;
}
}
I know its a lot of code, but I could use some assistance here