Tuesday, December 14, 2021

How to dynamically determine Calling Context using Apex in Salesforce?

 

Batch - System.isBatch()

@future - System.isFuture()
Queueable - System.isQueueable()
Schedulable - System.isScheduled()
Trigger - Trigger.isExecuting


Visualforce - ApexPages.currentPage() != null
Apex REST - RestContext.request != null


Batch - System.isBatch() : 

How to prevent a trigger firing during a batch job?

I have a batch job that updates accounts. There is also an after trigger (just to make life more complicated). I want that the trigger will not fire during the batch job.


Sol:- You can use !system.isBatch() in your trigger to check if it is fired from within a batch or not.


USE OF TRIGGER.ISEXECUTING


 Trigger.isexecuting is used to identify that the current context for the Apex code is a trigger and the apex code is not getting called from any other sources like webservice, visualforce page, etc.

In simple words when we want our apex code to execute only when it is getting called from trigger we make use of trigger.isexecuting.

To understand this with an example we will be going to consider the below scenario.

SCENARIO: We are having the requirement to check if the phone number on the account record is getting updated with a new value and if it is getting updated we want the checkbox field(Phone Number Changed) on account record to be marked as true. Also, we want this piece of code in apex class to be fire only when the apex code is getting called from Trigger.

APEX TRIGGER:

trigger AccountMainTrigger on Account (before update) {
    createContactClass obj=new createContactClass();
    if(trigger.isbefore && trigger.isupdate)
    {
     obj.method1(trigger.new,trigger.old);
    }
}

APEX CLASS:

public class createContactClass {
       public void method1(List<Account> newList,List<Account> oldList){
             system.debug('Is trigger executing'+trigger.isexecuting);
                                            if(trigger.isexecuting){ // Checking If the current context for the apex code is a trigger.
                 for(Account newAcc:newList){
                     for(Account oldAcc:oldList){
                         if(newAcc.id==oldAcc.id){
                             if(newAcc.phone!=oldAcc.phone){
                                 newAcc.Phone_Number_Changed__c=True;
                             }
                         }
                     }
                 }
             }
       }
}



Trigger.isExecuting is to determine that your current context for the Apex code is a trigger, not a Visualforce page, a Web service, or an executeanonymous() API call.
Here is an example of Apex Class, this class can identify current context for the Apex code is a trigger using Trigger.isExecuting.

Apex Class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class AccountHandler
{
    public Boolean handleAccount(List<Account> accList){
         
        System.debug('Trigger is executing : ' + Trigger.isExecuting);
         
        if(Trigger.isExecuting){
            //Do what ever you want to do as part of the trigger invocation
        }
        else{
            //Do what ever you want to do if the call originated from different context, such as from controller.
        }
        return Trigger.isExecuting;
    }
}

Apex Trigger:

1
2
3
4
trigger AccountTrigger on Account (before insert, before update){
    AccountHandler handler = new AccountHandler();
    handler.handleAccount(Trigger.New);
}


Considerations for invoking a future method from trigger


While developing triggers, there might be a need to invoke a future method from the trigger to do certain things asynchronously (for eg., to make a callout, avoid MIXED_DML_OPERATION exception etc.,). In such instances, developers should make sure this invocation is not made if the current context is already asynchronous (ie., either future or batch).

Consider the following scenario. There is a trigger on Account object that invokes a future method as follows,

     trigger AccountTrigger on Account (after insert, after update) {                                                                            AccountHandler.makeCalloutFutureMethod(Trigger.newMap.keySet());
            }
* makeCalloutFutureMethod is a future annotated method
Now, if an Account record is inserted through a batch process or through another future method, the above trigger would throw an exception saying "CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, AccountTrigger: execution of AfterInsert caused by: System.AsyncException: Future method cannot be called from a future or batch method". As the exception message indicates, a future method cannot be invoked from future or batch method execution context.

There are two possible workarounds for this problem:

Update the trigger logic to leverage the System.isFuture() and System.isBatch() calls so that the future method invocation is not made if the current execution context is future or batch. Records created through such cases should be handled separately using a scheduled batch job or something similar. The above example can be updated as follows,
        trigger AccountTrigger on Account (after insert, after update) {
            if(!System.isFuture() && !System.isBatch())                                                                                            AccountHandler.makeCalloutFutureMethod(Trigger.newMap.keySet());
                    }

 2. Replace the future method with Queueable Apex. Queueable Apex was introduced in Winter'15 and it is more powerful than future methods.

Please read the following articles to find more information about Queueable Apex: