Created
February 13, 2020 15:43
-
-
Save fbouzeraa/29f6b84e7a93c3c39b20631b89a97f8d to your computer and use it in GitHub Desktop.
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
public virtual class LXO_TriggerHandler { | |
// static map of handlername, times run() was invoked | |
private static Map<String, LoopCount> loopCountMap; | |
private static Set<String> bypassedHandlers; | |
// the current context of the trigger, overridable in tests | |
@TestVisible | |
private TriggerContext context; | |
// the current context of the trigger, overridable in tests | |
@TestVisible | |
private Boolean isTriggerExecuting; | |
// static initialization | |
static { | |
loopCountMap = new Map<String, LoopCount>(); | |
bypassedHandlers = new Set<String>(); | |
} | |
// constructor | |
public LXO_TriggerHandler() { | |
this.setTriggerContext(); | |
} | |
/*************************************** | |
* public instance methods | |
***************************************/ | |
// main method that will be called during execution | |
public void run() { | |
if(!validateRun()) { | |
return; | |
} | |
// FBO:we should not throw exception if the max loop is reached but just exit trigger execution. | |
if(!addToLoopCount()) { | |
return; | |
} | |
// dispatch to the correct handler method | |
if(this.context == TriggerContext.BEFORE_INSERT) { | |
this.beforeInsert(); | |
} else if(this.context == TriggerContext.BEFORE_UPDATE) { | |
this.beforeUpdate(); | |
} else if(this.context == TriggerContext.BEFORE_DELETE) { | |
this.beforeDelete(); | |
} else if(this.context == TriggerContext.AFTER_INSERT) { | |
this.afterInsert(); | |
} else if(this.context == TriggerContext.AFTER_UPDATE) { | |
this.afterUpdate(); | |
} else if(this.context == TriggerContext.AFTER_DELETE) { | |
this.afterDelete(); | |
} else if(this.context == TriggerContext.AFTER_UNDELETE) { | |
this.afterUndelete(); | |
} | |
} | |
public void setMaxLoopCount(Integer max) { | |
// FBO to realy manage loops, we need to check wich event is going to be restart not the Handlername (this last is for all this trigger's events ) | |
String handle = getEventHandlerName(); | |
if(!LXO_TriggerHandler.loopCountMap.containsKey(handle)) { | |
LXO_TriggerHandler.loopCountMap.put(handle, new LoopCount(max)); | |
} else { | |
LXO_TriggerHandler.loopCountMap.get(handle).setMax(max); | |
} | |
} | |
public void clearMaxLoopCount() { | |
this.setMaxLoopCount(-1); | |
} | |
/*************************************** | |
* public static methods | |
***************************************/ | |
public static void bypass(String handlerName) { | |
LXO_TriggerHandler.bypassedHandlers.add(handlerName); | |
} | |
public static void clearBypass(String handlerName) { | |
LXO_TriggerHandler.bypassedHandlers.remove(handlerName); | |
} | |
public static Boolean isBypassed(String handlerName) { | |
return LXO_TriggerHandler.bypassedHandlers.contains(handlerName); | |
} | |
public static void clearAllBypasses() { | |
LXO_TriggerHandler.bypassedHandlers.clear(); | |
} | |
/*************************************** | |
* private instancemethods | |
***************************************/ | |
@TestVisible | |
private void setTriggerContext() { | |
this.setTriggerContext(null, false); | |
} | |
@TestVisible | |
private void setTriggerContext(String ctx, Boolean testMode) { | |
if(!Trigger.isExecuting && !testMode) { | |
this.isTriggerExecuting = false; | |
return; | |
} else { | |
this.isTriggerExecuting = true; | |
} | |
if((Trigger.isExecuting && Trigger.isBefore && Trigger.isInsert) || | |
(ctx != null && ctx == 'before insert')) { | |
this.context = TriggerContext.BEFORE_INSERT; | |
} else if((Trigger.isExecuting && Trigger.isBefore && Trigger.isUpdate) || | |
(ctx != null && ctx == 'before update')){ | |
this.context = TriggerContext.BEFORE_UPDATE; | |
} else if((Trigger.isExecuting && Trigger.isBefore && Trigger.isDelete) || | |
(ctx != null && ctx == 'before delete')) { | |
this.context = TriggerContext.BEFORE_DELETE; | |
} else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isInsert) || | |
(ctx != null && ctx == 'after insert')) { | |
this.context = TriggerContext.AFTER_INSERT; | |
} else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isUpdate) || | |
(ctx != null && ctx == 'after update')) { | |
this.context = TriggerContext.AFTER_UPDATE; | |
} else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isDelete) || | |
(ctx != null && ctx == 'after delete')) { | |
this.context = TriggerContext.AFTER_DELETE; | |
} else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isUndelete) || | |
(ctx != null && ctx == 'after undelete')) { | |
this.context = TriggerContext.AFTER_UNDELETE; | |
} | |
} | |
// increment the loop count | |
@TestVisible | |
// FBO: we should not throw exception if the max loop is reached but just exit trigger execution. | |
// So I changed to return Boolean Value. It'll be checked in Run method. | |
private boolean addToLoopCount() { | |
String handle = getEventHandlerName(); | |
if(LXO_TriggerHandler.loopCountMap.containsKey(handle)) { | |
Boolean exceeded = LXO_TriggerHandler.loopCountMap.get(handle).increment(); | |
if(exceeded) { | |
/*Integer max = LXO_TriggerHandler.loopCountMap.get(handle).max; | |
throw new TriggerHandlerException('Maximum loop count of ' + String.valueOf(max) + ' reached in ' + handle);*/ | |
return false; | |
} | |
return true; | |
} | |
return true; | |
} | |
// make sure this trigger should continue to run | |
@TestVisible | |
private Boolean validateRun() { | |
if(!this.isTriggerExecuting || this.context == null) { | |
throw new TriggerHandlerException('Trigger handler called outside of Trigger execution'); | |
} | |
if(LXO_TriggerHandler.bypassedHandlers.contains(getHandlerName())) { | |
return false; | |
} | |
return true; | |
} | |
@TestVisible | |
private String getHandlerName() { | |
return String.valueOf(this).substring(0,String.valueOf(this).indexOf(':')); | |
} | |
// FBO to realy manage loops, we need to check wich event is going to be restart not the Handlername | |
// (this last is for all this trigger's events ) | |
@TestVisible | |
private String getEventHandlerName() { | |
return (getHandlerName() + this.context) ; | |
} | |
/*************************************** | |
* context methods | |
***************************************/ | |
// context-specific methods for override | |
@TestVisible | |
protected virtual void beforeInsert(){} | |
@TestVisible | |
protected virtual void beforeUpdate(){} | |
@TestVisible | |
protected virtual void beforeDelete(){} | |
@TestVisible | |
protected virtual void afterInsert(){} | |
@TestVisible | |
protected virtual void afterUpdate(){} | |
@TestVisible | |
protected virtual void afterDelete(){} | |
@TestVisible | |
protected virtual void afterUndelete(){} | |
/*************************************** | |
* inner classes | |
***************************************/ | |
// inner class for managing the loop count per handler | |
@TestVisible | |
private class LoopCount { | |
private Integer max; | |
private Integer count; | |
public LoopCount() { | |
this.max = 5; | |
this.count = 0; | |
} | |
public LoopCount(Integer max) { | |
this.max = max; | |
this.count = 0; | |
} | |
public Boolean increment() { | |
this.count++; | |
return this.exceeded(); | |
} | |
public Boolean exceeded() { | |
if(this.max < 0) return false; | |
if(this.count > this.max) { | |
return true; | |
} | |
return false; | |
} | |
public Integer getMax() { | |
return this.max; | |
} | |
public Integer getCount() { | |
return this.count; | |
} | |
public void setMax(Integer max) { | |
this.max = max; | |
} | |
} | |
// possible trigger contexts | |
@TestVisible | |
private enum TriggerContext { | |
BEFORE_INSERT, BEFORE_UPDATE, BEFORE_DELETE, | |
AFTER_INSERT, AFTER_UPDATE, AFTER_DELETE, | |
AFTER_UNDELETE | |
} | |
// exception class | |
public class TriggerHandlerException extends Exception {} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment