I'm using Tony Scott's Tidy Trigger pattern for triggers. Suddenly, I'm facing CPU limit exception on trying to update 2k accounts. On checking the debug log I see that validation rules & duplicate rules are being called. But I'm not sure why a working pattern has stopped working all of a sudden. The debug log registers the following -
Class.TriggerFactory.execute: line 94, column 1 Class.TriggerFactory.createAndExecuteHandler: line 27, column 1 Trigger.AccountTrigger: line 3, column 1
Here is the Account TriggerHandler, I don't see that the any logic is written on the update events, beforeUpdate & afterUpdate are empty methods.
public with sharing class Account_TriggerHandler implements ITrigger{ private list<Account> accList = new list<Account>(); private Map<Id, Account> MapIdAcc = new Map<Id, Account>(); private string TriggerEventName; public void bulkBefore() { if(Trigger.isDelete) { MapIdAcc = (Map<Id, Account>)Trigger.OldMap; } if(Trigger.isInsert){ TriggerEventName = 'Insert'; } else{ TriggerEventName = 'Update'; } } public void bulkAfter() { if(Trigger.isInsert) { accList = Trigger.new; } } public void beforeInsert(SObject so) { } public void beforeUpdate(SObject oldSo, SObject so) { } public void beforeDelete(SObject so) { } public void afterInsert(SObject so) { } public void afterUpdate(SObject oldSo, SObject so) { } public void afterDelete(SObject so) { } public void andFinally() { if(Trigger.isAfter && Trigger.isInsert) { Account_TriggerHelper.sendEmailToSharedPeople(accList); Account_TriggerHelper.sendEmailOnBlockingCustomer(accList); } if(Trigger.isBefore && !Trigger.isDelete){ Account_TriggerHelper.populateApprovalGroup(accList, MapIdAcc, TriggerEventName); } } } Account_TriggerHelper -
public class Account_TriggerHelper { public static void sendEmailToSharedPeople(List<Account> accList) { Map<Id, Account> mapOfAccIdAndAcc = new Map<Id, Account> (); Map<Id, Set<Id>> mapOfAccAndSharedUsers = new Map<Id, Set<Id>> (); Map<Id, String> mapOfUserAndName = new Map<Id, String> (); Set<Id> setOfGroupIds = new Set<Id> (); Map<Id, Set<Id>> mapOfGroupIdAndMember = new Map<Id, Set<Id>> (); String userType = Schema.sObjectType.User.getKeyPrefix(); String groupType = Schema.sObjectType.Group.getKeyPrefix(); for(sObject accS : accList) { Account acc = (Account)accS; mapOfAccIdAndAcc.put(acc.Id, acc); } for(AccountShare accShare : [SELECT Id, AccountId, UserOrGroupId FROM AccountShare WHERE AccountId IN :accList]) { if(!mapOfAccAndSharedUsers.containsKey(accShare.AccountId)) { mapOfAccAndSharedUsers.put(accShare.AccountId, new Set<Id> ()); } if(String.valueOf(accShare.UserOrGroupId).startsWith(userType) && (!UserUtil.getUser(accShare.UserOrGroupId).Profile.Name.contains('Administrator'))) { mapOfAccAndSharedUsers.get(accShare.AccountId).add(accShare.UserOrGroupId); } if(String.valueOf(accShare.UserOrGroupId).startsWith(groupType)) { setOfGroupIds.add(accShare.UserOrGroupId); } } for(GroupMember gm : [SELECT Id, UserOrGroupId, GroupId FROM GroupMember WHERE GroupId IN :setOfGroupIds]) { if(String.valueOf(gm.UserOrGroupId).startsWith(userType)) { if(!mapOfGroupIdAndMember.containsKey(gm.GroupId)) { mapOfGroupIdAndMember.put(gm.GroupId, new Set<Id>()); } mapOfGroupIdAndMember.get(gm.GroupId).add(gm.UserOrGroupId); } } for(AccountShare accShare : [SELECT Id, AccountId, UserOrGroupId FROM AccountShare WHERE UserOrGroupId IN :setOfGroupIds AND AccountId IN :mapOfAccIdAndAcc.keySet()]) { if(!mapOfAccAndSharedUsers.containsKey(accShare.AccountId)) { mapOfAccAndSharedUsers.put(accShare.AccountId, new Set<Id> ()); } if(mapOfGroupIdAndMember.get(accShare.UserOrGroupId) != null) { mapOfAccAndSharedUsers.get(accShare.AccountId).addAll(mapOfGroupIdAndMember.get(accShare.UserOrGroupId)); } } Set<Id> setOfUserIds = new Set<Id> (); for(Set<Id> lstId : mapOfAccAndSharedUsers.values()) { for(Id idVal : lstId) { if(!setOfUserIds.contains(idVal)) { setOfUserIds.add(idVal); } } } for(User u : [SELECT Id, Name FROM User WHERE Id IN :setOfUserIds]) { mapOfUserAndName.put(u.Id, u.Name); } List<Messaging.SingleEmailMessage> mails = new List<Messaging.SingleEmailMessage>(); for(Id idOfAcc : mapOfAccAndSharedUsers.keySet()) { for(Id idOfUser : mapOfAccAndSharedUsers.get(idOfAcc)) { Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage(); mail.setTargetObjectId(idOfUser); mail.saveAsActivity = false; mail.setSubject('New Customer:' + mapOfAccIdAndAcc.get(idOfAcc).Name + ' has been shared with you.'); String body = 'Dear ' + mapOfUserAndName.get(idOfUser) + ',<br/>' + '<br/>'; body += 'Please be informed that customer ' + '<b>' + mapOfAccIdAndAcc.get(idOfAcc).Name +'</b>'+ ' has been shared with you.' + '<br/>'+ '<br/>'; body += 'Please find the customer link ' + '<a href=" ' + MapOfAccIdAndAcc.get(idOfAcc).ORG_URL__c + String.valueOf(mapOfAccIdAndAcc.get(idOfAcc).Id) + '">here.</a>' + '<br/>' + '<br/>'; body += 'Thanks & Regards,'+ '<br/>'+ '<br/>'; body += 'Sales Team'; mail.setHtmlBody(body); mails.add(mail); } } Messaging.sendEmail(mails); } public static void sendEmailOnBlockingCustomer(List<Account> accList) { Map<Id, String> mapOfAccIdAndName = new Map<Id, String> (); Map<Id, Set<Id>> mapOfAccAndSharedUsers = new Map<Id, Set<Id>> (); Map<Id, String> mapOfUserAndName = new Map<Id, String> (); Set<Id> setOfGroupIds = new Set<Id> (); String userType = Schema.sObjectType.User.getKeyPrefix(); Map<Id, Set<Id>> mapOfGroupIdAndMember = new Map<Id, Set<Id>> (); for(sObject accV : accList) { Account acc = (Account)accV; Account oldVal = new Account(); if(Trigger.oldMap != null && Trigger.oldMap.containsKey(acc.Id)) { oldVal = (Account)Trigger.oldMap.get(acc.Id); } if((Trigger.oldMap == null && (acc.Block_Customer__c == true || acc.Blocked_Customer__c == true)) || (acc.Block_Customer__c == true && oldVal.Blocked_Customer__c == false) || (acc.Blocked_Customer__c == true && oldVal.Blocked_Customer__c == false)) { mapOfAccIdAndName.put(acc.Id, acc.Name); } } for(AccountShare accShare : [SELECT Id, AccountId, UserOrGroupId FROM AccountShare WHERE AccountId IN :mapOfAccIdAndName.keySet()]) { if(String.valueOf(accShare.UserOrGroupId).startsWith(userType)) { if(!mapOfAccAndSharedUsers.containsKey(accShare.AccountId)) { mapOfAccAndSharedUsers.put(accShare.AccountId, new Set<Id> ()); } mapOfAccAndSharedUsers.get(accShare.AccountId).add(accShare.UserOrGroupId); } else { if(!setOfGroupIds.contains(accShare.UserOrGroupId)) { setOfGroupIds.add(accShare.UserOrGroupId); } } } for(GroupMember gm : [SELECT Id, UserOrGroupId,GroupId FROM GroupMember WHERE GroupId IN :setOfGroupIds]) { if(String.valueOf(gm.UserOrGroupId).startsWith(userType)) { if(!mapOfGroupIdAndMember.containsKey(gm.GroupId)) { mapOfGroupIdAndMember.put(gm.GroupId, new Set<Id>()); } mapOfGroupIdAndMember.get(gm.GroupId).add(gm.UserOrGroupId); } } for(AccountShare accShare : [SELECT Id, AccountId, UserOrGroupId FROM AccountShare WHERE UserOrGroupId IN :setOfGroupIds AND AccountId IN :mapOfAccIdAndName.keySet()]) { if(!mapOfAccAndSharedUsers.containsKey(accShare.AccountId)) { mapOfAccAndSharedUsers.put(accShare.AccountId, new Set<Id> ()); } if(mapOfGroupIdAndMember != null && mapOfGroupIdAndMember.containsKey(accShare.UserOrGroupId)) { for(Id IdVal : mapOfGroupIdAndMember.get(accShare.UserOrGroupId)) { mapOfAccAndSharedUsers.get(accShare.AccountId).add(idVal); } } } Set<Id> setOfUserIds = new Set<Id> (); for(Set<Id> lstId : mapOfAccAndSharedUsers.values()) { for(Id idVal : lstId) { if(!setOfUserIds.contains(idVal)) { setOfUserIds.add(idVal); } } } for(User u : [SELECT Id, Email , Name FROM User WHERE Id IN :setOfUserIds]) { mapOfUserAndName.put(u.Id, u.Name); } List<Messaging.SingleEmailMessage> mails = new List<Messaging.SingleEmailMessage>(); for(Id idOfAcc : mapOfAccAndSharedUsers.keySet()) { for(Id idOfUser : mapOfAccAndSharedUsers.get(idOfAcc)) { Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage(); mail.setTargetObjectId(idOfUser); mail.saveAsActivity = false; mail.setSubject('Customer: ' + mapOfAccIdAndName.get(idOfAcc) + ' has been blocked.'); String body = 'Dear ' + mapOfUserAndName.get(idOfUser) + ',<br/>' + '<br/>'; body += 'Please be informed that customer ' + '<b>' + mapOfAccIdAndName.get(idOfAcc) +'</b>'+ ' has been blocked.' + '<br/>'+ '<br/>'; body += 'Please find the customer link ' + '<a href=" ' + String.valueOf(mapOfAccIdAndName.get(idOfAcc)) +'">here.</a>' + '<br/>' + '<br/>'; body += 'Thanks & Regards,'; body += 'Sales Team'; mail.setHtmlBody(body); mails.add(mail); } } Messaging.sendEmail(mails); } public static void populateApprovalGroup(List<Account> lstAccounts, Map<id, Account> oldMapAccounts, String strEvent){ //Variable Declaration set<String> setSalesOfficeName = new Set<String>(); Set<String> setSalesOfficeIds = new Set<String>(); Map<String, List<Account>> mapSalesOfficeLstAccounts = new Map<String, List<Account>>(); Map<String, List<Account>> mapSalesOfficeIdLstAccounts = new Map<String, List<Account>>(); List<Account> lstAccountsToBeUpdated = new List<Account>(); Id idSAPcustomer_RT = Schema.SObjectType.Account.getRecordTypeInfosByName().get('SAP Customer').getRecordTypeId(); Boolean boolEligibleRec; //Iterate Over Accounts to Collect records for(Account iteratorAcc : lstAccounts){ boolEligibleRec = false; //Block for SAP Customer if(iteratorAcc.recordTypeID == idSAPcustomer_RT) { if(strEvent == 'Insert') { boolEligibleRec = true; } else if(strEvent == 'Update') { if(oldMapAccounts.get(iteratorAcc.id).Sales_office_via_interface__c != iteratorAcc.Sales_office_via_interface__c) { boolEligibleRec = true; } } if(boolEligibleRec) { setSalesOfficeIds.add(iteratorAcc.Sales_office_via_interface__c); if(mapSalesOfficeIdLstAccounts.containsKey(iteratorAcc.Sales_office_via_interface__c)) { mapSalesOfficeIdLstAccounts.get(iteratorAcc.Sales_office_via_interface__c).add(iteratorAcc); } else { mapSalesOfficeIdLstAccounts.put(iteratorAcc.Sales_office_via_interface__c, new List<Account>{iteratorAcc}); } } } } //Modify Block for SalesOffice Ids if(setSalesOfficeIds.size()>0) { List<Sales_Office__c> lstSalesOffice = [select Id, Name from Sales_Office__c WHERE Id =: setSalesOfficeIds]; for(Sales_Office__c iteratorSalesOffice: lstSalesOffice) { setSalesOfficeName.add(iteratorSalesOffice.Name); if(mapSalesOfficeLstAccounts.containsKey(iteratorSalesOffice.Name)) { mapSalesOfficeLstAccounts.get(iteratorSalesOffice.Name).addAll(mapSalesOfficeIdLstAccounts.get(iteratorSalesOffice.id)); } else { mapSalesOfficeLstAccounts.put(iteratorSalesOffice.Name, mapSalesOfficeIdLstAccounts.get(iteratorSalesOffice.id)); } } } List<Sales_Office_and_Group_Mapping__c> lstSalesAndGroupMapping = [select Name, Group__c from Sales_Office_and_Group_Mapping__c WHERE Name=: setSalesOfficeName]; for(Sales_Office_and_Group_Mapping__c iteratorSalesAndGroupMapping: lstSalesAndGroupMapping) { for(Account varIteratorAcc : mapSalesOfficeLstAccounts.get(iteratorSalesAndGroupMapping.Name)) { varIteratorAcc.Approval_Group__c = iteratorSalesAndGroupMapping.Group__c; lstAccountsToBeUpdated.add(varIteratorAcc); } } } } This is the trigger -
trigger AccountTrigger on Account (after delete, after insert, after update, before delete, before insert, before update) { TriggerFactory.createAndExecuteHandler(Account_TriggerHandler.class); } & following is the TriggerFactory -
public with sharing class TriggerFactory { /** * Public static method to create and execute a trigger handler * * Arguments: Type t - Type of handler to instatiate * * Throws a TriggerException if no handler has been found. */ public static void createAndExecuteHandler(Type t) { // Get a handler appropriate to the object being processed ITrigger handler = getHandler(t); // Make sure we have a handler registered, new handlers must be registered in the getHandler method. if (handler == null) { throw new TriggerException('No Trigger Handler found named: ' + t.getName()); } // Execute the handler to fulfil the trigger execute(handler); } /** * private static method to control the execution of the handler * * Arguments: ITrigger handler - A Trigger Handler to execute */ private static void execute(ITrigger handler) { // Before Trigger if (Trigger.isBefore) { // Call the bulk before to handle any caching of data and enable bulkification handler.bulkBefore(); // Iterate through the records to be deleted passing them to the handler. if (Trigger.isDelete) { for (SObject so : Trigger.old) { handler.beforeDelete(so); } } // Iterate through the records to be inserted passing them to the handler. else if (Trigger.isInsert) { for (SObject so : Trigger.new) { handler.beforeInsert(so); } } // Iterate through the records to be updated passing them to the handler. else if (Trigger.isUpdate) { for (SObject so : Trigger.old) { handler.beforeUpdate(so, Trigger.newMap.get(so.Id)); } } } else { // Call the bulk after to handle any caching of data and enable bulkification handler.bulkAfter(); // Iterate through the records deleted passing them to the handler. if (Trigger.isDelete) { for (SObject so : Trigger.old) { handler.afterDelete(so); } } // Iterate through the records inserted passing them to the handler. else if (Trigger.isInsert) { for (SObject so : Trigger.new) { handler.afterInsert(so); } } // Iterate through the records updated passing them to the handler. else if (Trigger.isUpdate) { for (SObject so : Trigger.old) { handler.afterUpdate(so, Trigger.newMap.get(so.Id)); } } } // Perform any post processing handler.andFinally(); } /** * private static method to get the named handler. * * Arguments: Type t - Class of handler to instatiate * * Returns: ITrigger - A trigger handler if one exists or null. */ private static ITrigger getHandler(Type t) { // Instantiate the type Object o = t.newInstance(); // if its not an instance of ITrigger return null if (!(o instanceOf ITrigger)) { return null; } return (ITrigger)o; } public class TriggerException extends Exception {} } Line 94 on the TriggerFactory is -
handler.afterUpdate(so, Trigger.newMap.get(so.Id)); Line 27 on the TriggerFactory is -
execute(handler); Line 3 on the Trigger is -
TriggerFactory.createAndExecuteHandler(Account_TriggerHandler.class); The debug log reveals too less about the classes & I see validation & duplicate rules being loggedLink to debug log