- 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
}
}
}