Tag Archives: Apex

Salesforce Apex Trigger Framework

Benefits of Apex Trigger Framework:

  • A single trigger per object gives complete control over the order of execution.
  • Implementing trigger logic in handler class, makes unit testing and maintenance much easier.
  • Implementation of best practices.
  • It enforces trigger to work in a consistent way.
  • It allows to prevent trigger recursion without adding separate logic.
  • It is easier to work on a single trigger for multiple developers and reduce the development lifecycle.
  • It allows to make decisions to active/inactive the trigger from transaction and UI as well.

Each trigger must be implemented in a custom setting that allows the trigger to be active/inactive from UI.
Custom Settings: (Trigger Setting)

Trigger Setting Data: (Create record for each trigger)

Trigger Interface: (ITriggerHandler)

/*
@Author : Biswajeet Samal
@CreatedDate : 20th Oct 2019
@Description : Trigger Handler Interface
*/
public interface ITriggerHandler{
    
    /*
@Author : Biswajeet Samal
@CreatedDate : 20th Oct 2019
@Description : Called by the trigger framework before insert of the records
@Parameters : List<sObject> newList , Map<Id, sObject> newMap
*/
    void beforeInsert(List<sObject> newList);
    
    /*
@Author : Biswajeet Samal
@CreatedDate : 20th Oct 2019
@Description : Called by the trigger framework after insert of the records
@Parameters : List<sObject> newList
*/
    void afterInsert(List<sObject> newList, Map<Id, sObject> newMap);
    
    /*
@Author : Biswajeet Samal
@CreatedDate : 20th Oct 2019
@Description : Called by the trigger framework before update of the records
@Parameters : List<sObject> newList, Map<Id, sObject> newMap,  List<sObject> oldList, Map<Id, sObject> oldMap
*/
    void beforeUpdate(List<sObject> newList, Map<Id, sObject> newMap,  List<sObject> oldList, Map<Id, sObject> oldMap);
    
    /*
@Author : Biswajeet Samal
@CreatedDate : 20th Oct 2019
@Description : Called by the trigger framework after update of the records
@Parameters : List<sObject> newList, Map<Id, sObject> newMap,  List<sObject> oldList, Map<Id, sObject> oldMap
*/
    void afterUpdate(List<sObject> newList, Map<Id, sObject> newMap,  List<sObject> oldList, Map<Id, sObject> oldMap);
    
    /*
@Author : Biswajeet Samal
@CreatedDate : 20th Oct 2019
@Description : Called by the trigger framework before delete of the records
@Parameters : List<sObject> oldList , Map<Id, sObject> oldMap
*/            
    void beforeDelete(List<sObject> oldList , Map<Id, sObject> oldMap);
    
    /*
@Author : Biswajeet Samal
@CreatedDate : 20th Oct 2019
@Description : Called by the trigger framework after delete of the records
@Parameters : Map<Id, sObject> oldMap
*/
    void afterDelete(List<sObject> oldList , Map<Id, sObject> oldMap);
    
    /*
@Author : Biswajeet Samal
@CreatedDate : 20th Oct 2019
@Description : Called by the trigger framework after undelete of the records
@Parameters : List<sObject> newList, Map<Id, sObject> newMap
*/
    void afterUnDelete(List<sObject> newList, Map<Id, sObject> newMap);
    
    /*
@Author : Biswajeet Samal
@CreatedDate : 20th Oct 2019
@Description : Called by the trigger framework to check the trigger for the object is enabled or disabled
@Parameters :
*/
    Boolean isDisabled();
}

Trigger Dispatcher Class:(TriggerDispatcher)

/*
@Author : Biswajeet Samal
@CreatedDate : 20th Oct 2019
@Description : Trigger Dispatcher.
*/
public class TriggerDispatcher {
    
    /*
@Author : Biswajeet Samal
@CreatedDate : 20th Oct 2019
@Description : It will invoke the appropriate methods on the handler depending on the trigger context.
@Parameters : ITriggerHandler handler
*/
    public static void run(ITriggerHandler handler, string triggerName){
        
        //Check if the trigger is disabled
        if (handler.IsDisabled()){
            return;
        }
        
        //Get the trigger active information from custom settings by trigger name
        Boolean isActive = TriggerSetting__c.getValues(triggerName).isActive__c;
        
        if(isActive){
            //Check trigger context from trigger operation type
            switch on Trigger.operationType {
                
                when BEFORE_INSERT {
                    //Invoke before insert trigger handler
                    handler.beforeInsert(trigger.new);
                }
                when AFTER_INSERT {
                    //Invoke after insert trigger handler
                    handler.afterInsert(trigger.new, trigger.newMap);
                }
                when BEFORE_UPDATE {
                    //Invoke before update trigger handler
                    handler.beforeUpdate(trigger.new, trigger.newMap, trigger.old, trigger.oldMap);
                }
                when AFTER_UPDATE {
                    //Invoke after update trigger handler
                    handler.afterUpdate(trigger.new, trigger.newMap, trigger.old, trigger.oldMap);
                }
                when BEFORE_DELETE {
                    //Invoke before delete trigger handler
                    handler.beforeDelete(trigger.old, trigger.oldMap);
                }
                when AFTER_DELETE {
                    //Invoke after delete trigger handler
                    handler.afterDelete(trigger.old, trigger.oldMap);
                }
                when AFTER_UNDELETE {
                    //Invoke after undelete trigger handler
                    handler.afterUnDelete(trigger.new, trigger.newMap);
                }
            }
        }
    }
}

Account Object Trigger: (AccountTrigger)

/*
@Author : Biswajeet Samal
@CreatedDate : 20th Oct 2019
@Description : Account Object Trigger.
*/
trigger AccountTrigger on Account(before insert, after insert, before update, after update, before delete, after delete, after unDelete) {
    TriggerDispatcher.run(new AccountTriggerHandler(), 'AccountTrigger');
}

Account Object Trigger Handler: (AccountTriggerHandler)

/*
@Author : Biswajeet Samal
@CreatedDate : 20th Oct 2019
@Description : Account Object Trigger Handler.
*/
public class AccountTriggerHandler implements ITriggerHandler{
    
    //Use this variable to disable this trigger from transaction
    public static Boolean TriggerDisabled = false;
    
    //check if the trigger is disabled from transaction
    public Boolean isDisabled(){
        return TriggerDisabled;
    }
    
    public void beforeInsert(List<sObject> newList) {
        
    }
    
    public void afterInsert(List<sObject> newList , Map<Id, sObject> newMap) {
        
    }
    
    public void beforeUpdate(List<sObject> newList, Map<Id, sObject> newMap, List<sObject> oldList, Map<Id, sObject> oldMap) {
        
    }
    
    public void afterUpdate(List<sObject> newList, Map<Id, sObject> newMap,  List<sObject> oldList, Map<Id, sObject> oldMap) {
        
    }
    
    public void beforeDelete(List<sObject> oldList , Map<Id, sObject> oldMap) {
        
    }
    
    public void afterDelete(List<sObject> oldList , Map<Id, sObject> oldMap) {
        
    }
    
    public void afterUnDelete(List<sObject> newList, Map<Id, sObject> newMap) {
        
    }
}

Get Salesforce Org Limits Using Apex

Sample Code:

Map<String, System.OrgLimit> orgLimitMap = OrgLimits.getMap();
System.debug('OrgLimitMap-' + orgLimitMap);

for(String olName :orgLimitMap.keySet()) {
    System.OrgLimit orgLimit = orgLimitMap.get(olName);
    System.debug('Org Limit Name: ' + olName);
    System.debug('Org Limit Used:' + orgLimit.getValue());
    System.debug('Org Limit Available:' + orgLimit.getLimit());
}

Salesforce Apex TriggerOperation Enum

The System.TriggerOperation enum has the following values:

  • BEFORE_INSERE
  • AFTER_INSERT
  • BEFORE_UPDATE
  • AFTER_UPDATE
  • BEFORE_DELETE
  • AFTER_DELETE
  • AFTER_UNDELETE

The new Trigger context variable Trigger.operationType will return System.TriggerOperation enum during trigger context.

If you combine this new context variable and the new Apex switch feature, trigger code becomes much easy to implement and understand.

Sample Switch and enum in Triggers:

trigger AccountTrigger on Account(before insert, after insert, before update, after update, before delete, after delete, after undelete) {
    
    switch on Trigger.operationType {
        
        when BEFORE_INSERT {
            //Invoke before insert trigger handler
            system.debug('Before Insert');
        }
        when AFTER_INSERT {
            //Invoke after insert trigger handler
            system.debug('After Insert');
        }
        when BEFORE_UPDATE {
            //Invoke before update trigger handler
            system.debug('Before Update');
        }
        when AFTER_UPDATE {
            //Invoke after update trigger handler
            system.debug('After Update');
        }
        when BEFORE_DELETE {
            //Invoke before delete trigger handler
            system.debug('Before Delete');
        }
        when AFTER_DELETE {
            //Invoke after delete trigger handler
            system.debug('After Delete');
        }
        when AFTER_UNDELETE {
            //Invoke after undelete trigger handler
            system.debug('After Undelete');
        }
    }
}

Salesforce User Logout Event Trigger

  • Salesforce has introduced LogoutEventStream in Summer’18 release, to get the User logout events.
  • It is a beta feature and to make it available in your org, you need to contact Salesforce Support.
  • After it will available in your Org, you can enable LogoutEventStream.
  • After enabled LogoutEventStream, Salesforce publishes logout events when users log out from the UI.
  • You can add an Apex trigger to subscribe to those events, where you can implement our custom logic during logout.

Enable Logout Events Stream :
Go to Setup | Event Manager (Enter Event Manager in Quick Find) | Click on Logout Event link and click on Update Event on right coroner button and select Enable Streaming.

After enable Logout Events Stream, I have created an Apex trigger on LogoutEventStream object, where I’m saving the User Logout Event information in a Custom Object.

Custom Object:

Apex Trigger:

trigger LogoutEventTrigger on LogoutEventStream (after insert) {
    
    List<LogoutEventInfo__c> leInfoList = new List<LogoutEventInfo__c>();
    
    for(LogoutEventStream les : Trigger.new){
        LogoutEventInfo__c leInfo = new LogoutEventInfo__c();
        leInfo.EventIdentifier__c = les.EventIdentifier;
        leInfo.UserId__c = les.UserId;
        leInfo.Username__c = les.Username;
        leInfo.EventDate__c = les.EventDate;
        leInfo.RelatedEventIdentifier__c = les.RelatedEventIdentifier;
        leInfo.SessionKey__c = les.SessionKey;
        leInfo.LoginKey__c = les.LoginKey;
        leInfo.ReplayId__c = les.ReplayId;
        leInfo.SessionLevel__c = les.SessionLevel;
        leInfo.SourceIp__c = les.SourceIp;
        leInfoList.add(leInfo);
    }
    Insert leInfoList;
}

Now, you can Logout and Login again to check the Logout Event information in Custom Object.