6

Assume this simple class in a managed package namespaced PKG:

global class Semaphore
{
    private static Boolean m_isLocked = false;

    global static Boolean lock() { m_isLocked = true; }
    global static Boolean unlock() { m_isLocked = false; }
    global static Boolean isLocked() { return m_isLocked; }
}

How can a client/subscriber org access the above global methods in Apex dynamically? This is because the above package may or may not be installed in the client org, but the code still needs to see if the semaphore has been set.

Maybe something like this:

// pseudo code
Boolean isLocked = Type.forName('PKG.Semaphore.isLocked()');

Btw I don't think my question is limited to packaged methods, but I wanted to be very specific.

2

With the Callable interface that was introduced in Winter '19 you can now build a light weight interface for the methods you want to dynamically call from the class. It provides that loose coupling and fall back mechanisms to cover the packaging scenario.

The example below is from the docs (tweaked to show dynamic method naming):

Example class you want to dynamically call

public class Extension implements Callable {

   // Actual method
   String concatStrings(String stringValue) {
     return stringValue + stringValue;
   }

   // Actual method
   Decimal multiplyNumbers(Decimal decimalValue) {
     return decimalValue * decimalValue;
   }

   // Dispatch actual methods
   public Object call(String action, Map<String, Object> args) {
     switch on action {
       when 'concatStrings' {
         return this.concatStrings((String)args.get('stringValue'));
       }
       when 'multiplyNumbers' {
         return this.multiplyNumbers((Decimal)args.get('decimalValue'));
       }
       when else {
        throw new ExtensionMalformedCallException('Method not implemented');
       }
     }
   }

   public class ExtensionMalformedCallException extends Exception {}
}

Unit test demonstrating the dynamic calling

@IsTest
private with sharing class ExtensionCaller {

   @IsTest
   private static void givenConfiguredExtensionWhenCalledThenValidResult() {

      // Given
      String className = 'Extension'; // Variable to demonstrate setting class name
      String methodName = 'multiplyNumbers'; // Variable to demonstrate setting method name
      Decimal decimalTestValue = 10;

      // When
      Callable extension = (Callable) Type.forName(className).newInstance();
      Decimal result = (Decimal) extension.call(methodName, new Map<String, Object> { 'decimalValue' => decimalTestValue });

      // Then
      System.assertEquals(100, result);
   }
}
  • Forgot that I had this question here! Best Answer updated. – Mossi Jun 25 at 22:12
3

The heart of the question is how to avoid having a dependency on the managed package - needing it to always be installed - yet still be able to invoke methods when it is installed. Unfortunately Apex lacks the language features (for example Java's reflection) that allow that.

While the Type class allows instances of a class to be created using just the string name of the class, to invoke any methods a cast to an explicit (compile-time know) interface or class is needed which gets you right back into having the dependency. (Using Type allows the extension of managed package code but does not break the dependency on that managed package code.)

If the subscriber org is not managed code then you will have to add/remove classes that depend on the managed package depending on whether it is present or not. You can insulate most of your code from that by using e.g. an interface and substituting the implementation: either have one that depends on the managed package or have one that defaults the behaviour or fails if called.

If the subscribing code is also a managed package then things get harder and introducing a third piece of code is necessary. See this Breaking managed package dependencies blog post for more explanation of that. It is so painful that it would only be worth doing for a small number of major features (unless tools were created to generate the code).

PS

Calling the tooling API from Apex is possible. But it is a web service callout, a separate transaction, requires parameters to be passed in the Apex code string, doesn't allow results to be directly returned and is presumably slow. So not the semantics of a typical method call.

  • This was a comprehensive answer. I am convinced that the only way to do it is by introducing some sort of a middleman -which is an overkill in my case. Thanks! – Mossi Mar 16 '16 at 18:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.