Skip Saleforce Trigger

  1. create this class
public with sharing virtual class TRH_BaseTrigger {

    private static Map<String, LoopCount> loopCountMap;
    private static Set<String> bypassedHandlers;
    private static User currentUser; 
    @TestVisible  
    private static Boolean skipAll = false;
        
    
    // the current context of the trigger, overridable in tests
    @TestVisible
    private TriggerContext context;

    @TestVisible
    private static Set<String> bypassedByUserConfiguration;

    // 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>();
        bypassedByUserConfiguration = new Set<String>();
    }
    
    // constructor
    public TRH_BaseTrigger() {
        this.setTriggerContext();
        this.setBypassedHandlersByUserConfiguration();
    }

    /***************************************
     * public instance methods
     ***************************************/

    // main method that will be called during execution
    public void run() {

        if(skipAll || !validateRun()) {
            return;
        }

        addToLoopCount();

        
        // 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 static void skipAll (Boolean skip) {
        skipAll = skip;
    }

    public void setMaxLoopCount(Integer max) {
        String handlerName = getHandlerName();
        if( !TRH_BaseTrigger.loopCountMap.containsKey(handlerName) ) {
            TRH_BaseTrigger.loopCountMap.put(handlerName, new LoopCount(max));
        }
        else {
            TRH_BaseTrigger.loopCountMap.get(handlerName).setMax(max);
        }
    }
    
    public integer getLoopCount(){
    	LoopCount lc = TRH_BaseTrigger.loopCountMap.get(getHandlerName());
    	if(null != lc){
            return lc.count;
        } 
    	else{
            return null;
        } 
    }

    public void clearMaxLoopCount() {
        this.setMaxLoopCount(-1);
    }

    /***************************************
     * public static methods
     ***************************************/

    public static void bypass(String handlerName) {
        System.debug('*** TRH_BaseTrigger.bypass handlerName: '+handlerName);
        TRH_BaseTrigger.bypassedHandlers.add(handlerName);
        System.debug('*** TRH_BaseTrigger.bypassedHandlers: '+TRH_BaseTrigger.bypassedHandlers);
    }

    public static void clearBypass(String handlerName) {
        TRH_BaseTrigger.bypassedHandlers.remove(handlerName);
    }

    public static Boolean isBypassed(String handlerName) {
        return TRH_BaseTrigger.bypassedHandlers.contains(handlerName);
    }

    public static void clearAllBypasses() {
        TRH_BaseTrigger.bypassedHandlers.clear();
    }
    
    /***************************************
     * private instancemethods
     ***************************************/

    @TestVisible
    private void setTriggerContext() {
        this.setTriggerContext(null, false);
    }
    private static void setBypassedHandlersByUserConfigurationStatic() {
        System.debug('** TRH_BaseTrigger::setBypassedHandlersByUserConfiguration::bypassedByUserConfiguration='+bypassedByUserConfiguration);
        System.debug('*** TRH_BaseTrigger::setBypassedHandlersByUserConfiguration::bypassedHandlers='+bypassedHandlers);
        if(bypassedByUserConfiguration.isEmpty()) {
            Set<String> bypassedHandlers = new Set<String>();
    
            List<CustomPermission> customPermissions = UTL_Permissions.getCustomPermissionsByUserId(UserInfo.getUserId());
            System.debug('TRH_BaseTrigger::setBypassedHandlersByUserConfiguration::customPermissions='+JSON.serializePretty(customPermissions));
            if(!customPermissions.isEmpty()){
                for(CustomPermission customPerm : customPermissions){
                    bypassedHandlers.add(customPerm.DeveloperName);
                }
            }

            bypassedByUserConfiguration = bypassedHandlers;
        }
        System.debug('** POST IF TRH_BaseTrigger::setBypassedHandlersByUserConfiguration::bypassedByUserConfiguration='+bypassedByUserConfiguration);
    }
    

    private void setBypassedHandlersByUserConfiguration(){
        setBypassedHandlersByUserConfigurationStatic();
    }

    @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) || 'before insert'.equalsIgnoreCase(ctx))     {
            this.context = TriggerContext.BEFORE_INSERT;
        } 
        else if((Trigger.isExecuting && Trigger.isBefore && Trigger.isUpdate) || 'before update'.equalsIgnoreCase(ctx)) {
            this.context = TriggerContext.BEFORE_UPDATE;   
        }
        else if((Trigger.isExecuting && Trigger.isBefore && Trigger.isDelete) || 'before delete'.equalsIgnoreCase(ctx)) {
            this.context = TriggerContext.BEFORE_DELETE;
        }
        else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isInsert) || 'after insert'.equalsIgnoreCase(ctx)) {
            this.context = TriggerContext.AFTER_INSERT;
        }
        else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isUpdate) || 'after update'.equalsIgnoreCase(ctx)) {
            this.context = TriggerContext.AFTER_UPDATE;
        }
        else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isDelete) || 'after delete'.equalsIgnoreCase(ctx)) {
            this.context = TriggerContext.AFTER_DELETE;
        } 
        else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isUndelete) || 'after undelete'.equalsIgnoreCase(ctx)) {
            this.context = TriggerContext.AFTER_UNDELETE;
        }
    }

    // increment the loop count
    @TestVisible
    private void addToLoopCount() {
        String handlerName = getHandlerName();

        if(TRH_BaseTrigger.loopCountMap.containsKey(handlerName)) {
            Boolean exceeded = TRH_BaseTrigger.loopCountMap.get(handlerName).increment();

            if(exceeded) {
                Integer max = TRH_BaseTrigger.loopCountMap.get(handlerName).max;
                throw new TriggerHandlerException ('Maximum loop count of ' + String.valueOf(max) 
                                                                         + ' reached in ' + handlerName);
            }
        }
    }

    // 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(TRH_BaseTrigger.bypassedHandlers.contains(getHandlerName())) {
            return false;
        }
      
        return true;
    }

    @TestVisible
    private String getHandlerName() {
        return String.valueOf(this).substring(0,String.valueOf(this).indexOf(':'));
    }
    
    public static User getCurrentUser(){	
        Id currentUserId = UserInfo.getUserId();

    	if(currentUser==null ){
            List<User> userList = new List<User>();
            if(User.SObjectType.getDescribe().isAccessible()){
    		    userList = [SELECT Id FROM User WHERE Id =:currentUserId];
            } 
            
            if(!userList.isEmpty()){
                currentUser = userList.get(0);
            }
    	}
        
        return currentUser;
    } 

    public Boolean canRun(String contextName){
        System.debug('*** canRun bypassedHandlers.contains(contextName): '+bypassedHandlers+'*** contextName: '+contextName);
        return !bypassedByUserConfiguration.contains(contextName) && !bypassedHandlers.contains(contextName);
    }
   
    @TestVisible
    protected virtual void beforeInsert(){
        System.debug('Running beforeInsert in Base Trigger');
    }

    @TestVisible
    protected virtual void beforeUpdate(){
        System.debug('Running beforeUpdate in Base Trigger');
    }

    @TestVisible
    protected virtual void beforeDelete(){
        System.debug('Running beforeDelete in Base Trigger');
    }

    @TestVisible
    protected virtual void afterInsert(){
        System.debug('Running afterInsert in Base Trigger');
    }

    @TestVisible
    protected virtual void afterUpdate(){
        System.debug('Running afterUpdate in Base Trigger');
    }

    @TestVisible
    protected virtual void afterDelete(){
        System.debug('Running afterDelete in Base Trigger');
    }

    @TestVisible
    protected virtual void afterUndelete(){
        System.debug('Running afterUndelete in Base Trigger');
    }

    /***************************************
     * 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 without sharing class TriggerHandlerException extends Exception {}
	
}

2. Create Trigger
trigger Account on Account (before insert, before update, before delete, after insert, after update) {
	
	if(Trigger.isBefore){ 
        if(Trigger.isInsert){ 
            
        } 
            
        if(Trigger.isUpdate){
            // TRH_Account accountTriggerRunner = new TRH_Account();
            // accountTriggerRunner.run(); 
        }          
        
        if(Trigger.isDelete){            
			TRH_Account accountTriggerRunner = new TRH_Account();			
        }         
    }
    
    else if(Trigger.isAfter) {        
        if(Trigger.isUpdate) {                        
            TRH_Account accountTriggerRunner = new TRH_Account();
            accountTriggerRunner.run(); 
        }
    }	
}

public with sharing virtual class TRH_BaseTrigger {

    private static Map<String, LoopCount> loopCountMap;
    private static Set<String> bypassedHandlers;
    private static User currentUser; 
    @TestVisible  
    private static Boolean skipAll = false;
        
    
    // the current context of the trigger, overridable in tests
    @TestVisible
    private TriggerContext context;

    @TestVisible
    private static Set<String> bypassedByUserConfiguration;

    // 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>();
        bypassedByUserConfiguration = new Set<String>();
    }
    
    // constructor
    public TRH_BaseTrigger() {
        this.setTriggerContext();
        this.setBypassedHandlersByUserConfiguration();
    }

    /***************************************
     * public instance methods
     ***************************************/

    // main method that will be called during execution
    public void run() {

        if(skipAll || !validateRun()) {
            return;
        }

        addToLoopCount();

        
        // 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 static void skipAll (Boolean skip) {
        skipAll = skip;
    }

    public void setMaxLoopCount(Integer max) {
        String handlerName = getHandlerName();
        if( !TRH_BaseTrigger.loopCountMap.containsKey(handlerName) ) {
            TRH_BaseTrigger.loopCountMap.put(handlerName, new LoopCount(max));
        }
        else {
            TRH_BaseTrigger.loopCountMap.get(handlerName).setMax(max);
        }
    }
    
    public integer getLoopCount(){
    	LoopCount lc = TRH_BaseTrigger.loopCountMap.get(getHandlerName());
    	if(null != lc){
            return lc.count;
        } 
    	else{
            return null;
        } 
    }

    public void clearMaxLoopCount() {
        this.setMaxLoopCount(-1);
    }

    /***************************************
     * public static methods
     ***************************************/

    public static void bypass(String handlerName) {
        System.debug('*** TRH_BaseTrigger.bypass handlerName: '+handlerName);
        TRH_BaseTrigger.bypassedHandlers.add(handlerName);
        System.debug('*** TRH_BaseTrigger.bypassedHandlers: '+TRH_BaseTrigger.bypassedHandlers);
    }

    public static void clearBypass(String handlerName) {
        TRH_BaseTrigger.bypassedHandlers.remove(handlerName);
    }

    public static Boolean isBypassed(String handlerName) {
        return TRH_BaseTrigger.bypassedHandlers.contains(handlerName);
    }

    public static void clearAllBypasses() {
        TRH_BaseTrigger.bypassedHandlers.clear();
    }
    
    /***************************************
     * private instancemethods
     ***************************************/

    @TestVisible
    private void setTriggerContext() {
        this.setTriggerContext(null, false);
    }
    private static void setBypassedHandlersByUserConfigurationStatic() {
        System.debug('** TRH_BaseTrigger::setBypassedHandlersByUserConfiguration::bypassedByUserConfiguration='+bypassedByUserConfiguration);
        System.debug('*** TRH_BaseTrigger::setBypassedHandlersByUserConfiguration::bypassedHandlers='+bypassedHandlers);
        if(bypassedByUserConfiguration.isEmpty()) {
            Set<String> bypassedHandlers = new Set<String>();
    
            List<CustomPermission> customPermissions = UTL_Permissions.getCustomPermissionsByUserId(UserInfo.getUserId());
            System.debug('TRH_BaseTrigger::setBypassedHandlersByUserConfiguration::customPermissions='+JSON.serializePretty(customPermissions));
            if(!customPermissions.isEmpty()){
                for(CustomPermission customPerm : customPermissions){
                    bypassedHandlers.add(customPerm.DeveloperName);
                }
            }

            bypassedByUserConfiguration = bypassedHandlers;
        }
        System.debug('** POST IF TRH_BaseTrigger::setBypassedHandlersByUserConfiguration::bypassedByUserConfiguration='+bypassedByUserConfiguration);
    }
    

    private void setBypassedHandlersByUserConfiguration(){
        setBypassedHandlersByUserConfigurationStatic();
    }

    @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) || 'before insert'.equalsIgnoreCase(ctx))     {
            this.context = TriggerContext.BEFORE_INSERT;
        } 
        else if((Trigger.isExecuting && Trigger.isBefore && Trigger.isUpdate) || 'before update'.equalsIgnoreCase(ctx)) {
            this.context = TriggerContext.BEFORE_UPDATE;   
        }
        else if((Trigger.isExecuting && Trigger.isBefore && Trigger.isDelete) || 'before delete'.equalsIgnoreCase(ctx)) {
            this.context = TriggerContext.BEFORE_DELETE;
        }
        else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isInsert) || 'after insert'.equalsIgnoreCase(ctx)) {
            this.context = TriggerContext.AFTER_INSERT;
        }
        else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isUpdate) || 'after update'.equalsIgnoreCase(ctx)) {
            this.context = TriggerContext.AFTER_UPDATE;
        }
        else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isDelete) || 'after delete'.equalsIgnoreCase(ctx)) {
            this.context = TriggerContext.AFTER_DELETE;
        } 
        else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isUndelete) || 'after undelete'.equalsIgnoreCase(ctx)) {
            this.context = TriggerContext.AFTER_UNDELETE;
        }
    }

    // increment the loop count
    @TestVisible
    private void addToLoopCount() {
        String handlerName = getHandlerName();

        if(TRH_BaseTrigger.loopCountMap.containsKey(handlerName)) {
            Boolean exceeded = TRH_BaseTrigger.loopCountMap.get(handlerName).increment();

            if(exceeded) {
                Integer max = TRH_BaseTrigger.loopCountMap.get(handlerName).max;
                throw new TriggerHandlerException ('Maximum loop count of ' + String.valueOf(max) 
                                                                         + ' reached in ' + handlerName);
            }
        }
    }

    // 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(TRH_BaseTrigger.bypassedHandlers.contains(getHandlerName())) {
            return false;
        }
      
        return true;
    }

    @TestVisible
    private String getHandlerName() {
        return String.valueOf(this).substring(0,String.valueOf(this).indexOf(':'));
    }
    
    public static User getCurrentUser(){	
        Id currentUserId = UserInfo.getUserId();

    	if(currentUser==null ){
            List<User> userList = new List<User>();
            if(User.SObjectType.getDescribe().isAccessible()){
    		    userList = [SELECT Id FROM User WHERE Id =:currentUserId];
            } 
            
            if(!userList.isEmpty()){
                currentUser = userList.get(0);
            }
    	}
        
        return currentUser;
    } 

    public Boolean canRun(String contextName){
        System.debug('*** canRun bypassedHandlers.contains(contextName): '+bypassedHandlers+'*** contextName: '+contextName);
        return !bypassedByUserConfiguration.contains(contextName) && !bypassedHandlers.contains(contextName);
    }
   
    @TestVisible
    protected virtual void beforeInsert(){
        System.debug('Running beforeInsert in Base Trigger');
    }

    @TestVisible
    protected virtual void beforeUpdate(){
        System.debug('Running beforeUpdate in Base Trigger');
    }

    @TestVisible
    protected virtual void beforeDelete(){
        System.debug('Running beforeDelete in Base Trigger');
    }

    @TestVisible
    protected virtual void afterInsert(){
        System.debug('Running afterInsert in Base Trigger');
    }

    @TestVisible
    protected virtual void afterUpdate(){
        System.debug('Running afterUpdate in Base Trigger');
    }

    @TestVisible
    protected virtual void afterDelete(){
        System.debug('Running afterDelete in Base Trigger');
    }

    @TestVisible
    protected virtual void afterUndelete(){
        System.debug('Running afterUndelete in Base Trigger');
    }

    /***************************************
     * 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 without sharing class TriggerHandlerException extends Exception {}
	
}

2. Create Trigger Handler (Extend First Class on this post)
global without sharing class TRH_Account extends TRH_BaseTrigger { 

    global override void beforeDelete(){            
        System.debug('TRH_Account-beforeDelete ' + JSON.serializePretty((Map<Id,Account>)Trigger.oldMap));  
        // TODO
    }
    
    global override void afterUpdate(){     
        List<Account> mySObjectList = Trigger.new;
        
        if([condition for bypass trigger]){
            System.debug('TRH_Account-afterUpdate BypassAccountTrigger');  
            TRH_Account.bypass('BypassAccountTrigger');
            TRH_Account accountTriggerRunner = new TRH_Account(); 
            // TODO
            
        }else {
            // NO BYPASS
        }         
    }

}