Improvements to the trigger handler
Ability to disable trigger action in meta-data, and adds a stateful option Signed-off-by: WimVelzeboer <wimvelzeboer@protonmail.com>
This commit is contained in:
+27
-1
@@ -66,7 +66,6 @@ public virtual inherited sharing class fflib_TriggerActionConfig
|
|||||||
else if (triggerAction.ExecutionContext__c == 'Dedicated Queued') this.dedicatedQueued = true;
|
else if (triggerAction.ExecutionContext__c == 'Dedicated Queued') this.dedicatedQueued = true;
|
||||||
|
|
||||||
// Get SObjectType
|
// Get SObjectType
|
||||||
System.debug('triggerAction: '+triggerAction);
|
|
||||||
String ObjectTypeApiName =
|
String ObjectTypeApiName =
|
||||||
String.isNotBlank(triggerAction.ObjectType__c)
|
String.isNotBlank(triggerAction.ObjectType__c)
|
||||||
? triggerAction.ObjectType__r?.QualifiedApiName?.toLowerCase().trim()
|
? triggerAction.ObjectType__r?.QualifiedApiName?.toLowerCase().trim()
|
||||||
@@ -74,6 +73,14 @@ public virtual inherited sharing class fflib_TriggerActionConfig
|
|||||||
this.sObjectType = Schema.describeSObjects(new List<String> {ObjectTypeApiName}).get(0).getSObjectType();
|
this.sObjectType = Schema.describeSObjects(new List<String> {ObjectTypeApiName}).get(0).getSObjectType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the Implementation Type of the Trigger Action
|
||||||
|
*/
|
||||||
|
public Type getImplementationType()
|
||||||
|
{
|
||||||
|
return this.implementationType;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Returns the sequence number of the trigger action
|
* @return Returns the sequence number of the trigger action
|
||||||
*/
|
*/
|
||||||
@@ -90,6 +97,14 @@ public virtual inherited sharing class fflib_TriggerActionConfig
|
|||||||
return (this.dedicatedQueued != null && this.dedicatedQueued);
|
return (this.dedicatedQueued != null && this.dedicatedQueued);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns TRUE if the trigger action needs to be kept in memory and reused by the next TriggerHandler run
|
||||||
|
*/
|
||||||
|
public Boolean isStateful()
|
||||||
|
{
|
||||||
|
return this.stateful;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Returns TRUE if the trigger action can be executed in an Queueable context
|
* @return Returns TRUE if the trigger action can be executed in an Queueable context
|
||||||
*/
|
*/
|
||||||
@@ -107,4 +122,15 @@ public virtual inherited sharing class fflib_TriggerActionConfig
|
|||||||
{
|
{
|
||||||
return (fflib_ITriggerAction) implementationType.newInstance();
|
return (fflib_ITriggerAction) implementationType.newInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean hasTriggerContext(TriggerOperation triggerOperation)
|
||||||
|
{
|
||||||
|
return (triggerOperation == System.TriggerOperation.BEFORE_INSERT && beforeInsert)
|
||||||
|
|| (triggerOperation == System.TriggerOperation.BEFORE_UPDATE && beforeUpdate)
|
||||||
|
|| (triggerOperation == System.TriggerOperation.BEFORE_DELETE && beforeDelete)
|
||||||
|
|| (triggerOperation == System.TriggerOperation.AFTER_DELETE && afterDelete)
|
||||||
|
|| (triggerOperation == System.TriggerOperation.AFTER_INSERT && afterInsert)
|
||||||
|
|| (triggerOperation == System.TriggerOperation.AFTER_UNDELETE && afterUndelete)
|
||||||
|
|| (triggerOperation == System.TriggerOperation.AFTER_UPDATE && afterUpdate);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+2
-2
@@ -65,12 +65,12 @@ public virtual inherited sharing class fflib_TriggerActionConfigSelector
|
|||||||
/**
|
/**
|
||||||
* @return Query all fflib_TriggerAction__mdt records
|
* @return Query all fflib_TriggerAction__mdt records
|
||||||
*/
|
*/
|
||||||
public virtual List<fflib_TriggerAction__mdt> selectAll()
|
public virtual List<fflib_TriggerAction__mdt> selectAllActive()
|
||||||
{
|
{
|
||||||
return (List <fflib_TriggerAction__mdt>) Database.query(
|
return (List <fflib_TriggerAction__mdt>) Database.query(
|
||||||
|
|
||||||
String.format(
|
String.format(
|
||||||
'SELECT {0} FROM {1}',
|
'SELECT {0} FROM {1} WHERE IsActive__c=TRUE',
|
||||||
new List<String>
|
new List<String>
|
||||||
{
|
{
|
||||||
getFieldListString() + ', ObjectType__r.QualifiedApiName',
|
getFieldListString() + ', ObjectType__r.QualifiedApiName',
|
||||||
|
|||||||
@@ -48,6 +48,12 @@ public virtual with sharing class fflib_TriggerHandler
|
|||||||
|
|
||||||
protected fflib_ITriggerContext triggerContext;
|
protected fflib_ITriggerContext triggerContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retain instance of Trigger Action classes implementing stateful
|
||||||
|
*/
|
||||||
|
@TestVisible
|
||||||
|
private static Map<Type, fflib_ITriggerAction> triggerActionStateByClass;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides access to the trigger action configurations and allowing it to be mocked in unit-tests
|
* Provides access to the trigger action configurations and allowing it to be mocked in unit-tests
|
||||||
*/
|
*/
|
||||||
@@ -101,7 +107,9 @@ public virtual with sharing class fflib_TriggerHandler
|
|||||||
fflib_UnitOfWork unitOfWork = new fflib_UnitOfWork();
|
fflib_UnitOfWork unitOfWork = new fflib_UnitOfWork();
|
||||||
for (fflib_ITriggerActionConfig triggerActionConfig : configs)
|
for (fflib_ITriggerActionConfig triggerActionConfig : configs)
|
||||||
{
|
{
|
||||||
fflib_ITriggerAction triggerAction = triggerActionConfig.newInstance();
|
if (isValidExecutionContext(ctx, triggerActionConfig) == false) continue;
|
||||||
|
|
||||||
|
fflib_ITriggerAction triggerAction = getTriggerActionInstance(triggerActionConfig);
|
||||||
|
|
||||||
triggerAction.setContext(ctx);
|
triggerAction.setContext(ctx);
|
||||||
triggerAction.setSequence(triggerActionConfig.getSequence());
|
triggerAction.setSequence(triggerActionConfig.getSequence());
|
||||||
@@ -117,12 +125,29 @@ public virtual with sharing class fflib_TriggerHandler
|
|||||||
unitOfWork.doWork();
|
unitOfWork.doWork();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Boolean isValidExecutionContext(fflib_ITriggerContext ctx, fflib_ITriggerActionConfig config)
|
||||||
|
{
|
||||||
|
return config.hasTriggerContext(ctx.getTriggerOperation());
|
||||||
|
}
|
||||||
|
|
||||||
|
private fflib_ITriggerAction getTriggerActionInstance(fflib_ITriggerActionConfig triggerActionConfig)
|
||||||
|
{
|
||||||
|
if (triggerActionConfig.isStateful()
|
||||||
|
&& triggerActionStateByClass != null
|
||||||
|
&& triggerActionStateByClass.containsKey(triggerActionConfig.getImplementationType()))
|
||||||
|
{
|
||||||
|
return triggerActionStateByClass.get(triggerActionConfig.getImplementationType());
|
||||||
|
}
|
||||||
|
|
||||||
|
return triggerActionConfig.newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
@TestVisible
|
@TestVisible
|
||||||
protected void loadConfiguration()
|
protected void loadConfiguration()
|
||||||
{
|
{
|
||||||
if (configs.isEmpty() == false) return;
|
if (configs.isEmpty() == false) return;
|
||||||
|
|
||||||
for (fflib_TriggerAction__mdt action : new fflib_TriggerActionConfigSelector().selectAll())
|
for (fflib_TriggerAction__mdt action : new fflib_TriggerActionConfigSelector().selectAllActive())
|
||||||
{
|
{
|
||||||
configs.add( new fflib_TriggerActionConfig(action));
|
configs.add( new fflib_TriggerActionConfig(action));
|
||||||
}
|
}
|
||||||
|
|||||||
+19
@@ -29,16 +29,33 @@
|
|||||||
*/
|
*/
|
||||||
public interface fflib_ITriggerActionConfig
|
public interface fflib_ITriggerActionConfig
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @return Returns the Implementation Type of the Trigger Action
|
||||||
|
*/
|
||||||
|
System.Type getImplementationType();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Returns the sequence number of the trigger action
|
* @return Returns the sequence number of the trigger action
|
||||||
*/
|
*/
|
||||||
Integer getSequence();
|
Integer getSequence();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param triggerOperation The Trigger operation to validate the configuration against.
|
||||||
|
*
|
||||||
|
* @return Returns TRUE is the given operation type matches the one of the configuration
|
||||||
|
*/
|
||||||
|
Boolean hasTriggerContext(TriggerOperation triggerOperation);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Returns TRUE if the trigger action should be executed in a dedicated Queueable context
|
* @return Returns TRUE if the trigger action should be executed in a dedicated Queueable context
|
||||||
*/
|
*/
|
||||||
Boolean isDedicatedQueued();
|
Boolean isDedicatedQueued();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns TRUE if the trigger action needs to be kept in memory and reused by the next TriggerHandler run
|
||||||
|
*/
|
||||||
|
Boolean isStateful();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Returns TRUE if the trigger action can be executed in an Queueable context
|
* @return Returns TRUE if the trigger action can be executed in an Queueable context
|
||||||
*/
|
*/
|
||||||
@@ -50,4 +67,6 @@ public interface fflib_ITriggerActionConfig
|
|||||||
* @return Instance of the Trigger Action class
|
* @return Instance of the Trigger Action class
|
||||||
*/
|
*/
|
||||||
fflib_ITriggerAction newInstance();
|
fflib_ITriggerAction newInstance();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
+1
-1
@@ -32,5 +32,5 @@ public interface fflib_ITriggerActionConfigSelector
|
|||||||
/**
|
/**
|
||||||
* @return Query all fflib_TriggerAction__mdt records
|
* @return Query all fflib_TriggerAction__mdt records
|
||||||
*/
|
*/
|
||||||
List<fflib_TriggerAction__mdt> selectAll();
|
List<fflib_TriggerAction__mdt> selectAllActive();
|
||||||
}
|
}
|
||||||
+4
@@ -24,6 +24,10 @@
|
|||||||
<behavior>Required</behavior>
|
<behavior>Required</behavior>
|
||||||
<field>NamespacePrefix</field>
|
<field>NamespacePrefix</field>
|
||||||
</layoutItems>
|
</layoutItems>
|
||||||
|
<layoutItems>
|
||||||
|
<behavior>Edit</behavior>
|
||||||
|
<field>IsActive__c</field>
|
||||||
|
</layoutItems>
|
||||||
</layoutColumns>
|
</layoutColumns>
|
||||||
<style>TwoColumnsTopToBottom</style>
|
<style>TwoColumnsTopToBottom</style>
|
||||||
</layoutSections>
|
</layoutSections>
|
||||||
|
|||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||||
|
<fullName>IsActive__c</fullName>
|
||||||
|
<defaultValue>true</defaultValue>
|
||||||
|
<externalId>false</externalId>
|
||||||
|
<fieldManageability>DeveloperControlled</fieldManageability>
|
||||||
|
<label>Is Active</label>
|
||||||
|
<type>Checkbox</type>
|
||||||
|
</CustomField>
|
||||||
@@ -252,7 +252,49 @@ private class fflib_TriggerHandlerTest
|
|||||||
System.assert(ctx.isUpdate());
|
System.assert(ctx.isUpdate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@IsTest
|
||||||
|
private static void itShouldUseTheStatefulTriggerActionClass()
|
||||||
|
{
|
||||||
|
fflib_LogEntry__c record = new fflib_LogEntry__c(LogLevel__c = 'INFO', Message__c = 'Unit Test');
|
||||||
|
List<fflib_LogEntry__c> records = new List<fflib_LogEntry__c> {record};
|
||||||
|
|
||||||
|
fflib_ApexMocks mocks = new fflib_ApexMocks();
|
||||||
|
fflib_ITriggerAction triggerActionMock = (fflib_ITriggerAction) mocks.mock(fflib_ITriggerAction.class);
|
||||||
|
|
||||||
|
mocks.startStubbing();
|
||||||
|
mocks.when(triggerActionMock.isQueueable()).thenReturn(false);
|
||||||
|
mocks.when(triggerActionMock.isNotQueueable()).thenReturn(true);
|
||||||
|
mocks.stopStubbing();
|
||||||
|
|
||||||
|
fflib_TriggerHandler.triggerActionStateByClass =
|
||||||
|
new Map<Type, fflib_ITriggerAction>{ MyTriggerAction.class => triggerActionMock };
|
||||||
|
|
||||||
|
fflib_TriggerAction__mdt triggerAction = new fflib_TriggerAction__mdt(
|
||||||
|
ObjectType__c = null,
|
||||||
|
ObjectTypeAlternate__c = Schema.fflib_LogEntry__c.SObjectType.getDescribe().getName(),
|
||||||
|
BeforeInsert__c = true,
|
||||||
|
ImplementationType__c = 'fflib_TriggerHandlerTest.MyTriggerAction',
|
||||||
|
ExecutionContext__c = 'Realtime',
|
||||||
|
Stateful__c = true
|
||||||
|
);
|
||||||
|
|
||||||
|
fflib_TriggerHandler triggerHandler = new fflib_TriggerHandler();
|
||||||
|
String ctxClassName = triggerHandler.triggerContextType.getName() + '.Constructor';
|
||||||
|
fflib_TriggerContext ctx =
|
||||||
|
(fflib_TriggerContext)
|
||||||
|
((fflib_IDomainConstructor) Type.forName(ctxClassName).newInstance())
|
||||||
|
.construct(new List<fflib_LogEntry__c> {record});
|
||||||
|
ctx.triggerOperation = System.TriggerOperation.BEFORE_INSERT;
|
||||||
|
|
||||||
|
// WHEN a record is inserted
|
||||||
|
System.Test.startTest();
|
||||||
|
triggerHandler.addTriggerActionConfig(triggerAction);
|
||||||
|
triggerHandler.setTriggerContext(ctx).run();
|
||||||
|
System.Test.stopTest();
|
||||||
|
|
||||||
|
// THEN the stateful trigger action should be re-used
|
||||||
|
((fflib_ITriggerAction) mocks.verify(triggerActionMock)).doWork();
|
||||||
|
}
|
||||||
|
|
||||||
public class MyTriggerAction extends fflib_TriggerAction // Todo - rename handler to action
|
public class MyTriggerAction extends fflib_TriggerAction // Todo - rename handler to action
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user