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
Account_TriggerHandler
might get you some suggestions - all depends what work you are doing in there as all the factory is doing is delegating to that class.Account
is where I'd start your search. The dev console's analysis perspective is also useful