diff --git a/trigger-actions-framework/main/default/classes/FinalizerHandler.cls b/trigger-actions-framework/main/default/classes/FinalizerHandler.cls index a038a4a..50b14ef 100644 --- a/trigger-actions-framework/main/default/classes/FinalizerHandler.cls +++ b/trigger-actions-framework/main/default/classes/FinalizerHandler.cls @@ -126,12 +126,38 @@ public with sharing virtual class FinalizerHandler { Context context ) { Object dynamicInstance; + String namespace = finalizerMetadata.Apex_Class_Namespace__c; String className = finalizerMetadata.Apex_Class_Name__c; if (FinalizerHandler.isBypassed(className)) { return; } + try { - dynamicInstance = Type.forName(className).newInstance(); + System.Type actionType = Type.forName(namespace, className); + /** Type.forName(fullyQualifiedName) allowed some messyness and ambiguity in dealing with namespace + * If config does not provide the correct namespace (likely if upgrading from older versions of this framework) we need to fallback in two scenarios + * - package and class namespaced but namespace wasn't specified + * - namespace is actually in the class field in the form namespace.classname + */ + // try shared Namespace + if( actionType == null ) { + // Get the namespace of the current class. + String[] parts = String.valueOf(MetadataTriggerHandler.class).split('\\.', 2); + namespace = parts.size() == 2 ? parts[0] : ''; + + // try again with the new namespace + actionType = Type.forName(namespace, className); + } + // try namespace in Class_Name field + if (actionType == null) { + String[] parts = className.split('\\.', 2); + if(parts.size() == 2) { + namespace = parts[0]; + className = parts[1]; + actionType = Type.forName(namespace, className); + } + } + dynamicInstance = actionType.newInstance(); } catch (System.NullPointerException e) { handleFinalizerException(INVALID_CLASS_ERROR_FINALIZER, className); } diff --git a/trigger-actions-framework/main/default/classes/FinalizerHandlerTest.cls b/trigger-actions-framework/main/default/classes/FinalizerHandlerTest.cls index 637fbb8..4eed964 100644 --- a/trigger-actions-framework/main/default/classes/FinalizerHandlerTest.cls +++ b/trigger-actions-framework/main/default/classes/FinalizerHandlerTest.cls @@ -73,6 +73,27 @@ private with sharing class FinalizerHandlerTest { ); } + @IsTest + private static void ambiguousNamespacedFinalizerShouldExecute() { + // Invalid namespace is the same as a missing one. + // Can't have mixed namespace unit tests so using invalid to trigger the same fallback behavior + handler.allFinalizers = new List{ + new DML_Finalizer__mdt( + Apex_Class_Namespace__c = 'NOT_A_NAMEPACE', + Apex_Class_Name__c = TEST_FOO_FINALIZER, + Bypass_Permission__c = BYPASS_PERMISSION + ) + }; + + handler.handleDynamicFinalizers(); + + System.Assert.areEqual( + FOO, + finalizerLedger[0], + 'The finalizer should be dynamically instantiated and executed' + ); + } + @IsTest private static void bypassedFinalizerShouldNotExecute() { handler.allFinalizers = new List{ diff --git a/trigger-actions-framework/main/default/classes/MetadataTriggerHandler.cls b/trigger-actions-framework/main/default/classes/MetadataTriggerHandler.cls index a5e871b..e0bf441 100644 --- a/trigger-actions-framework/main/default/classes/MetadataTriggerHandler.cls +++ b/trigger-actions-framework/main/default/classes/MetadataTriggerHandler.cls @@ -86,6 +86,7 @@ public inherited sharing class MetadataTriggerHandler extends TriggerBase implem private static final String QUERY_TEMPLATE = String.join( new List{ 'SELECT Apex_Class_Name__c,', + 'Apex_Class_Namespace__c,', 'Order__c,', 'Flow_Name__c,', 'Bypass_Permission__c,', @@ -384,9 +385,34 @@ public inherited sharing class MetadataTriggerHandler extends TriggerBase implem } for (Trigger_Action__mdt triggerMetadata : actionMetadata) { Object triggerAction; + String namespace = triggerMetadata.Apex_Class_Namespace__c; + String className = triggerMetadata.Apex_Class_Name__c; try { - triggerAction = Type.forName(triggerMetadata.Apex_Class_Name__c) - .newInstance(); + System.Type actionType = Type.forName(namespace, className); + /** Type.forName(fullyQualifiedName) allowed some messyness and ambiguity in dealing with namespace + * If config does not provide the correct namespace (likely if upgrading from older versions of this framework) we need to fallback in two scenarios + * - package and class namespaced but namespace wasn't specified + * - namespace is actually in the class field in the form namespace.classname + */ + // try shared Namespace + if( actionType == null ) { + // Get the namespace of the current class. + String[] parts = String.valueOf(MetadataTriggerHandler.class).split('\\.', 2); + namespace = parts.size() == 2 ? parts[0] : ''; + + // try again with the new namespace + actionType = Type.forName(namespace, className); + } + // try namespace in Class_Name field + if (actionType == null) { + String[] parts = className.split('\\.', 2); + if(parts.size() == 2) { + namespace = parts[0]; + className = parts[1]; + actionType = Type.forName(namespace, className); + } + } + triggerAction = actionType.newInstance(); if (triggerMetadata.Flow_Name__c != null) { ((TriggerActionFlow) triggerAction) .flowName = triggerMetadata.Flow_Name__c; diff --git a/trigger-actions-framework/main/default/classes/MetadataTriggerHandlerTest.cls b/trigger-actions-framework/main/default/classes/MetadataTriggerHandlerTest.cls index b089be2..6a82e26 100644 --- a/trigger-actions-framework/main/default/classes/MetadataTriggerHandlerTest.cls +++ b/trigger-actions-framework/main/default/classes/MetadataTriggerHandlerTest.cls @@ -737,6 +737,26 @@ private class MetadataTriggerHandlerTest { System.Assert.areEqual(null, myException, EXCEPTION_SHOULD_NOT_BE_THROWN); } + @IsTest + private static void actionShouldFallbackIfBadNamespace() { + // Invalid namespace is the same as a missing one. + // Can't have mixed namespace unit tests so using invalid to trigger the same fallback behavior + handler.beforeInsertActionMetadata = new List{ + new Trigger_Action__mdt( + Apex_Class_Namespace__c = 'NOT_A_NAMEPACE', + Apex_Class_Name__c = TEST_BEFORE_INSERT, + Before_Insert__r = setting, + Before_Insert__c = setting.Id, + Order__c = 1, + Bypass_Execution__c = false + ) + }; + + handler.beforeInsert(handler.triggerNew); + + System.Assert.isTrue(executed, ACTION_SHOULD_EXECUTE); + } + public class TestBeforeInsert implements TriggerAction.BeforeInsert { public void beforeInsert(List newList) { MetadataTriggerHandlerTest.executed = true; diff --git a/trigger-actions-framework/main/default/layouts/DML_Finalizer__mdt-DML Finalizer Layout.layout-meta.xml b/trigger-actions-framework/main/default/layouts/DML_Finalizer__mdt-DML Finalizer Layout.layout-meta.xml index e87be1f..da67b8b 100644 --- a/trigger-actions-framework/main/default/layouts/DML_Finalizer__mdt-DML Finalizer Layout.layout-meta.xml +++ b/trigger-actions-framework/main/default/layouts/DML_Finalizer__mdt-DML Finalizer Layout.layout-meta.xml @@ -33,6 +33,10 @@ true + + Edit + Apex_Class_Namespace__c + Required Apex_Class_Name__c diff --git a/trigger-actions-framework/main/default/layouts/Trigger_Action__mdt-Trigger Action Layout.layout-meta.xml b/trigger-actions-framework/main/default/layouts/Trigger_Action__mdt-Trigger Action Layout.layout-meta.xml index 496c3e9..e4ddc52 100644 --- a/trigger-actions-framework/main/default/layouts/Trigger_Action__mdt-Trigger Action Layout.layout-meta.xml +++ b/trigger-actions-framework/main/default/layouts/Trigger_Action__mdt-Trigger Action Layout.layout-meta.xml @@ -33,6 +33,10 @@ true + + Edit + Apex_Class_Namespace__c + Required Apex_Class_Name__c diff --git a/trigger-actions-framework/main/default/objects/DML_Finalizer__mdt/fields/Apex_Class_Namespace__c.field-meta.xml b/trigger-actions-framework/main/default/objects/DML_Finalizer__mdt/fields/Apex_Class_Namespace__c.field-meta.xml new file mode 100644 index 0000000..8d0febd --- /dev/null +++ b/trigger-actions-framework/main/default/objects/DML_Finalizer__mdt/fields/Apex_Class_Namespace__c.field-meta.xml @@ -0,0 +1,13 @@ + + + Apex_Class_Namespace__c + Enter the apex class namespace + false + SubscriberControlled + Enter the apex class namespace + + 15 + false + Text + false + \ No newline at end of file diff --git a/trigger-actions-framework/main/default/objects/Trigger_Action__mdt/fields/Apex_Class_Namespace__c.field-meta.xml b/trigger-actions-framework/main/default/objects/Trigger_Action__mdt/fields/Apex_Class_Namespace__c.field-meta.xml new file mode 100644 index 0000000..8d0febd --- /dev/null +++ b/trigger-actions-framework/main/default/objects/Trigger_Action__mdt/fields/Apex_Class_Namespace__c.field-meta.xml @@ -0,0 +1,13 @@ + + + Apex_Class_Namespace__c + Enter the apex class namespace + false + SubscriberControlled + Enter the apex class namespace + + 15 + false + Text + false + \ No newline at end of file