Dynamically Determine Calling Context?

Just to play devil's advocate, I think you may be somewhat missing the point of a generic utility method. Generic is powerful. Now you seek to pollute that. To some extent, context specific logic belongs in the calling context.

I understand that sometimes, reality doesn't play nice, and you may need to do source dependent processing. But a lot of times, those calling contexts leave artifacts. For example, what are you setting as the value of the LeadSource field? Does it vary depending on these contexts? Are there other field indicators that can lead you to conclude which context a record was created in? My preference is to put that sort of logic in a trigger if you can conclusively determine calling context another way.


Here is the extent to which I know you can determine calling context:

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

As far as I know you can't tell if you are running Execute Anonymous, WebService, or Visualforce. The following might be somewhat helpful:

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

You can also get a some information by querying the AuthSession object. Not sure how reliable this approach would be but worth mention.

SELECT LoginType, SessionType FROM AuthSession
WHERE UsersId = :UserInfo.getUserId()
AND IsCurrent = true
ORDER BY CreatedDate DESC

You could also try this strategy but it's more dubious:

try
{
    throw new DmlException();
}
catch (DmlException e)
{
    String stackTrace = e.getStackTraceString();
    if (stackTrace.contains('AnonymousBlock'))
        system.debug('Execute Anonymous');
    // other parsing...
}

If you really must determine the context, I was going to recommend something similar to what @Boris wrote up. However, in addition to setting a static variable, you could consider method overloads.

public class Utility
{
    public static void method(List<Lead> input)
    {
        // existing logic
    }
    public static void method(List<Lead> input, /*String or Enum*/ context)
    {
        method(input);
        // further processing
    }
}

Or you can at least move the context specific logic to another method to minimize the "pollution" factor:

public class Utility
{
    public static void method(List<Lead> input)
    {
        // existing logic
        contextLogic(input);
    }
    static void contextLogic(List<Lead> input)
    {
        if (System.isBatch()) { batchLogic(input); }
        if (System.isFuture()) { futureLogic(input); }
        if (System.isQueueable()) { queueableLogic(input); }
        if (System.isScheduled()) { schedulableLogic(input); }
        if (Trigger.isExecuting) { triggerLogic(input); }

        if (ApexPages.currentPage() != null) { visualforceLogic(input); }
        if (RestContext.request != null) { apexRestLogic(input); }

        try
        {
            throw new DmlException(); // down the rabbit hole...
        }
        catch (DmlException e)
        {
            // have fun
        }
    }
}

There are different ways how to handle that. In the same fashion as you mentioned Trigger.isExecuting (it's boolean and can only have 2 values), you can create a static variable (of type String or Integer or Enum, whatever type that makes sense) inside your utility class, to which you can assign a different value from each of the callers. For an example if the utility method is called from a VF controller you can set the value to indicate that the context is VF controller. If you're calling from an invocable method, set a different value etc.

Let's assume this is your utility class:

public with sharing class Util
{
    // Variable to hold the current transaction context
    public static String context;

    // Assuming this method can be called from multiple places
    public static Boolean checkAvailability(Date particularDate)
    {
        // If it's called from a trigger, do one thing
        if (context == 'TRIGGER')
        {
            // do something
            return true;
        }
        // If it's called from an invocable method, do something else
        else if (context == 'INVOCABLE')
        {
            // do something else
            return true;
        }
        else
        {
            // Default
            return false;
        }
    }
}

Now inside your trigger for an example you can set the context before calling the checkAvailability method:

trigger Account on Account (before insert)
{
    Util.context = 'TRIGGER';
    Boolean available = Util.checkAvailability(System.today());
    // do something
}

Use the same principle when calling from elsewhere.