Trigger Handler functionality
A trigger handler based on trigger actions configured via custom metadata. It separates the trigger logic per class, and is utilizing the fflib_UnitOfWork to execute the trigger actions. Signed-off-by: WimVelzeboer <wimvelzeboer@protonmail.com>
This commit is contained in:
+1
-3
@@ -42,9 +42,7 @@ public virtual class fflib_AppBindingModule
|
||||
|
||||
public virtual void addBinding(fflib_IAppBinding binding)
|
||||
{
|
||||
if (null == this.bindings) this.bindings = new List<fflib_IAppBinding>();
|
||||
|
||||
this.bindings.add(binding);
|
||||
addBindings(new List<fflib_IAppBinding>{ binding });
|
||||
}
|
||||
|
||||
public virtual void addBindings(List<fflib_IAppBinding> bindings)
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>51.0</apiVersion>
|
||||
<apiVersion>53.0</apiVersion>
|
||||
<status>Active</status>
|
||||
</ApexClass>
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>51.0</apiVersion>
|
||||
<apiVersion>53.0</apiVersion>
|
||||
<status>Active</status>
|
||||
</ApexClass>
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* File Name: fflib_TriggerAction
|
||||
*
|
||||
* @author architect ir. Wilhelmus G.J. Velzeboer
|
||||
*
|
||||
* Copyright (c), W.G.J. Velzeboer,
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above author notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* - Neither the name of the author nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
public abstract inherited sharing class fflib_TriggerAction
|
||||
extends fflib_DoWork
|
||||
implements fflib_ITriggerAction
|
||||
{
|
||||
protected fflib_ITriggerContext triggerContext;
|
||||
|
||||
/**
|
||||
* Override this to perform logic during the after delete phase, this is called by the handleAfterDelete method
|
||||
**/
|
||||
public virtual void onAfterDelete() { }
|
||||
|
||||
/**
|
||||
* Override this to perform logic during the after insert phase, this is called by the handleAfterInsert method
|
||||
**/
|
||||
public virtual void onAfterInsert() { }
|
||||
|
||||
/**
|
||||
* Override this to perform logic during the after undelete phase, this is called by the handleAfterDelete method
|
||||
**/
|
||||
public virtual void onAfterUnDelete() { }
|
||||
|
||||
/**
|
||||
* Override this to perform logic during the after update phase, this is called by the handleAfterUpdate method
|
||||
**/
|
||||
public virtual void onAfterUpdate() { }
|
||||
|
||||
/**
|
||||
* Override this to perform logic during the before delete phase, this is called by the handleBeforeDelete method
|
||||
**/
|
||||
public virtual void onBeforeDelete() { }
|
||||
|
||||
/**
|
||||
* Override this to perform logic during the before insert phase, this is called by the handleBeforeInsert method
|
||||
**/
|
||||
public virtual void onBeforeInsert() { }
|
||||
|
||||
/**
|
||||
* Override this to perform logic during the before update phase, this is called by the handleBeforeUpdate method
|
||||
**/
|
||||
public virtual void onBeforeUpdate() { }
|
||||
|
||||
public void doWork()
|
||||
{
|
||||
switch on this.triggerContext.getTriggerOperation()
|
||||
{
|
||||
when AFTER_DELETE { onAfterDelete(); }
|
||||
when AFTER_INSERT { onAfterInsert(); }
|
||||
when AFTER_UNDELETE { onAfterUndelete(); }
|
||||
when AFTER_UPDATE { onAfterUpdate(); }
|
||||
when BEFORE_DELETE { onBeforeDelete(); }
|
||||
when BEFORE_INSERT { onBeforeInsert(); }
|
||||
when BEFORE_UPDATE { onBeforeUpdate(); }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used by the TriggerHandler and us to pass in the trigger context
|
||||
*
|
||||
* @param ctx Instance of fflib_ITriggerContext
|
||||
*
|
||||
* @return Instance of itself to allow for method chaining
|
||||
*/
|
||||
public fflib_ITriggerAction setContext(fflib_ITriggerContext ctx)
|
||||
{
|
||||
this.triggerContext = ctx;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>53.0</apiVersion>
|
||||
<status>Active</status>
|
||||
</ApexClass>
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* File Name: fflib_TriggerActionConfig
|
||||
*
|
||||
* @author architect ir. Wilhelmus G.J. Velzeboer
|
||||
*
|
||||
* Copyright (c), W.G.J. Velzeboer,
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above author notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* - Neither the name of the author nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
public virtual inherited sharing class fflib_TriggerActionConfig
|
||||
implements fflib_ITriggerActionConfig
|
||||
{
|
||||
protected Boolean afterDelete = false;
|
||||
protected Boolean afterInsert = false;
|
||||
protected Boolean afterUndelete = false;
|
||||
protected Boolean afterUpdate = false;
|
||||
protected Boolean beforeDelete = false;
|
||||
protected Boolean beforeInsert = false;
|
||||
protected Boolean beforeUpdate = false;
|
||||
protected Boolean stateful = false;
|
||||
protected System.Type implementationType { get; set; }
|
||||
protected Schema.SObjectType sObjectType { get; set; }
|
||||
protected Integer sequence = 0;
|
||||
protected Boolean dedicatedQueued;
|
||||
|
||||
/**
|
||||
* Class Constructor
|
||||
* Takes a custom metadata records and translates that into an instance of fflib_TriggerActionConfig
|
||||
*
|
||||
* @param triggerAction Instance of fflib_TriggerActionConfig
|
||||
*/
|
||||
public fflib_TriggerActionConfig(fflib_TriggerAction__mdt triggerAction)
|
||||
{
|
||||
this.afterDelete = triggerAction.AfterDelete__c == null ? false : triggerAction.AfterDelete__c;
|
||||
this.afterInsert = triggerAction.AfterInsert__c == null ? false : triggerAction.AfterInsert__c;
|
||||
this.afterUndelete = triggerAction.AfterUndelete__c == null ? false : triggerAction.AfterUndelete__c;
|
||||
this.afterUpdate = triggerAction.AfterUpdate__c == null ? false : triggerAction.AfterUpdate__c;
|
||||
this.beforeDelete = triggerAction.BeforeDelete__c == null ? false : triggerAction.BeforeDelete__c;
|
||||
this.beforeInsert = triggerAction.BeforeInsert__c == null ? false : triggerAction.BeforeInsert__c;
|
||||
this.beforeUpdate = triggerAction.BeforeUpdate__c == null ? false : triggerAction.BeforeUpdate__c;
|
||||
this.implementationType = System.Type.forName(triggerAction.ImplementationType__c);
|
||||
this.sequence = triggerAction.Sequence__c == null ? 0 : Integer.valueOf(triggerAction.Sequence__c);
|
||||
this.stateful = triggerAction.Stateful__c == null ? false : triggerAction.Stateful__c;
|
||||
|
||||
if (triggerAction.ExecutionContext__c == 'Queued') this.dedicatedQueued = false;
|
||||
else if (triggerAction.ExecutionContext__c == 'Dedicated Queued') this.dedicatedQueued = true;
|
||||
|
||||
// Get SObjectType
|
||||
System.debug('triggerAction: '+triggerAction);
|
||||
String ObjectTypeApiName =
|
||||
String.isNotBlank(triggerAction.ObjectType__c)
|
||||
? triggerAction.ObjectType__r?.QualifiedApiName?.toLowerCase().trim()
|
||||
: triggerAction.ObjectTypeAlternate__c.toLowerCase().trim();
|
||||
this.sObjectType = Schema.describeSObjects(new List<String> {ObjectTypeApiName}).get(0).getSObjectType();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the sequence number of the trigger action
|
||||
*/
|
||||
public Integer getSequence()
|
||||
{
|
||||
return this.sequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns TRUE if the trigger action should be executed in a dedicated Queueable context
|
||||
*/
|
||||
public Boolean isDedicatedQueued()
|
||||
{
|
||||
return (this.dedicatedQueued != null && this.dedicatedQueued);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns TRUE if the trigger action can be executed in an Queueable context
|
||||
*/
|
||||
public Boolean isQueued()
|
||||
{
|
||||
return (this.dedicatedQueued != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of the trigger action class based on the provided implementation type in the configuration
|
||||
*
|
||||
* @return Instance of the Trigger Action class
|
||||
*/
|
||||
public fflib_ITriggerAction newInstance()
|
||||
{
|
||||
return (fflib_ITriggerAction) implementationType.newInstance();
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>53.0</apiVersion>
|
||||
<status>Active</status>
|
||||
</ApexClass>
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* File Name: fflib_TriggerActionConfigSelector
|
||||
*
|
||||
* @author architect ir. Wilhelmus G.J. Velzeboer
|
||||
*
|
||||
* Copyright (c), W.G.J. Velzeboer,
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above author notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* - Neither the name of the author nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
public virtual inherited sharing class fflib_TriggerActionConfigSelector
|
||||
extends fflib_SObjectSelector
|
||||
implements fflib_ITriggerActionConfigSelector
|
||||
{
|
||||
public fflib_TriggerActionConfigSelector()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
public List<Schema.SObjectField> getSObjectFieldList()
|
||||
{
|
||||
return new List<Schema.SObjectField>
|
||||
{
|
||||
fflib_TriggerAction__mdt.Id,
|
||||
fflib_TriggerAction__mdt.AfterDelete__c,
|
||||
fflib_TriggerAction__mdt.AfterInsert__c,
|
||||
fflib_TriggerAction__mdt.AfterUndelete__c,
|
||||
fflib_TriggerAction__mdt.AfterUpdate__c,
|
||||
fflib_TriggerAction__mdt.BeforeDelete__c,
|
||||
fflib_TriggerAction__mdt.BeforeInsert__c,
|
||||
fflib_TriggerAction__mdt.BeforeUpdate__c,
|
||||
fflib_TriggerAction__mdt.ExecutionContext__c,
|
||||
fflib_TriggerAction__mdt.ImplementationType__c,
|
||||
fflib_TriggerAction__mdt.ObjectType__c,
|
||||
fflib_TriggerAction__mdt.ObjectTypeAlternate__c,
|
||||
fflib_TriggerAction__mdt.Sequence__c,
|
||||
fflib_TriggerAction__mdt.Stateful__c
|
||||
};
|
||||
}
|
||||
|
||||
public Schema.SObjectType getSObjectType()
|
||||
{
|
||||
return Schema.fflib_TriggerAction__mdt.SObjectType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Query all fflib_TriggerAction__mdt records
|
||||
*/
|
||||
public virtual List<fflib_TriggerAction__mdt> selectAll()
|
||||
{
|
||||
return (List <fflib_TriggerAction__mdt>) Database.query(
|
||||
|
||||
String.format(
|
||||
'SELECT {0} FROM {1}',
|
||||
new List<String>
|
||||
{
|
||||
getFieldListString() + ', ObjectType__r.QualifiedApiName',
|
||||
getSObjectType().getDescribe().getName()
|
||||
}
|
||||
)
|
||||
|
||||
/* Please replace the above String.format method call for that below,
|
||||
when the following issue in fflib-apex-commons is resolved:
|
||||
https://github.com/apex-enterprise-patterns/fflib-apex-common/issues/208
|
||||
|
||||
newQueryFactory()
|
||||
.selectField('ObjectType__r.QualifiedApiName')
|
||||
.toSOQL()
|
||||
|
||||
*/
|
||||
);
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>53.0</apiVersion>
|
||||
<status>Active</status>
|
||||
</ApexClass>
|
||||
@@ -0,0 +1,240 @@
|
||||
/**
|
||||
* File Name: fflib_TriggerContext
|
||||
*
|
||||
* @author architect ir. Wilhelmus G.J. Velzeboer
|
||||
*
|
||||
* Copyright (c), W.G.J. Velzeboer,
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above author notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* - Neither the name of the author nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
public virtual inherited sharing class fflib_TriggerContext
|
||||
extends fflib_SObjects2
|
||||
implements fflib_ITriggerContext
|
||||
{
|
||||
/**
|
||||
* Provides access to the Trigger Operation and allowing it to be mocked in unit-tests
|
||||
*/
|
||||
@TestVisible
|
||||
protected System.TriggerOperation triggerOperation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (triggerOperation == null)
|
||||
{
|
||||
triggerOperation = Trigger.operationType;
|
||||
}
|
||||
return triggerOperation;
|
||||
}
|
||||
set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to the Trigger.new and allowing it to be mocked in unit-tests
|
||||
*/
|
||||
@TestVisible
|
||||
protected List<SObject> records {
|
||||
get
|
||||
{
|
||||
if (records == null)
|
||||
{
|
||||
records = getRecords();
|
||||
}
|
||||
return records;
|
||||
}
|
||||
set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to the Trigger.oldMap and allowing it to be mocked in unit-tests
|
||||
*/
|
||||
@TestVisible
|
||||
protected Map<Id, SObject> existingRecords
|
||||
{
|
||||
get
|
||||
{
|
||||
if (existingRecords == null)
|
||||
{
|
||||
existingRecords = Trigger.oldMap;
|
||||
}
|
||||
return existingRecords;
|
||||
}
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
public fflib_TriggerContext(List<SObject> records)
|
||||
{
|
||||
super(records);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to the data represented by this Trigger Context
|
||||
**/
|
||||
public System.TriggerOperation getTriggerOperation()
|
||||
{
|
||||
return triggerOperation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects whether any values in context records have changed for given field as string
|
||||
* Returns list of SObject records that have changes in the specified fields
|
||||
**/
|
||||
public List<SObject> getChangedRecords(String fieldName)
|
||||
{
|
||||
return getChangedRecords(new Set<String> {fieldName});
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects whether any values in context records have changed for given fields as strings
|
||||
* Returns list of SObject records that have changes in the specified fields
|
||||
**/
|
||||
public List<SObject> getChangedRecords(Set<String> fieldNames)
|
||||
{
|
||||
List<SObject> changedRecords = new List<SObject>();
|
||||
for (SObject newRecord : getRecords())
|
||||
{
|
||||
Id recordId = (Id) newRecord.get('Id');
|
||||
if (getExistingRecords() == null || !getExistingRecords().containsKey(recordId)) continue;
|
||||
|
||||
SObject oldRecord = getExistingRecords().get(recordId);
|
||||
for (String fieldName : fieldNames)
|
||||
{
|
||||
if (oldRecord.get(fieldName) != newRecord.get(fieldName))
|
||||
{
|
||||
changedRecords.add(newRecord);
|
||||
break; // prevents the records from being added multiple times
|
||||
}
|
||||
}
|
||||
}
|
||||
return changedRecords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects whether any values in context records have changed for given field as token
|
||||
* Returns list of SObject records that have changes in the specified fields
|
||||
**/
|
||||
public List<SObject> getChangedRecords(Schema.SObjectField fieldToken)
|
||||
{
|
||||
return getChangedRecords(new Set<Schema.SObjectField> {fieldToken});
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects whether any values in context records have changed for given fields as tokens
|
||||
* Returns list of SObject records that have changes in the specified fields
|
||||
**/
|
||||
public List<SObject> getChangedRecords(Set<Schema.SObjectField> fieldTokens)
|
||||
{
|
||||
List<SObject> changedRecords = new List<SObject>();
|
||||
for (SObject newRecord : getRecords())
|
||||
{
|
||||
Id recordId = (Id) newRecord.get('Id');
|
||||
if (getExistingRecords() == null || !getExistingRecords().containsKey(recordId)) continue;
|
||||
|
||||
SObject oldRecord = getExistingRecords().get(recordId);
|
||||
for (Schema.SObjectField fieldToken : fieldTokens)
|
||||
{
|
||||
if (oldRecord.get(fieldToken) != newRecord.get(fieldToken))
|
||||
{
|
||||
changedRecords.add(newRecord);
|
||||
break; // prevents the records from being added multiple times
|
||||
}
|
||||
}
|
||||
}
|
||||
return changedRecords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to Trigger.oldMap or the mock when configured in a unit-test
|
||||
**/
|
||||
public Map<Id, SObject> getExistingRecords()
|
||||
{
|
||||
return this.existingRecords;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns TRUE if the Trigger Context is an INSERT operation
|
||||
*/
|
||||
public Boolean isInsert()
|
||||
{
|
||||
return this.triggerOperation == System.TriggerOperation.AFTER_INSERT
|
||||
|| this.triggerOperation == System.TriggerOperation.BEFORE_INSERT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns TRUE if the Trigger Context is an UPDATE operation
|
||||
*/
|
||||
public Boolean isUpdate()
|
||||
{
|
||||
return this.triggerOperation == System.TriggerOperation.AFTER_UPDATE
|
||||
|| this.triggerOperation == System.TriggerOperation.BEFORE_UPDATE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns TRUE if the Trigger Context is an DELETE operation
|
||||
*/
|
||||
public Boolean isDelete()
|
||||
{
|
||||
return this.triggerOperation == System.TriggerOperation.AFTER_DELETE
|
||||
|| this.triggerOperation == System.TriggerOperation.BEFORE_DELETE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns TRUE if the Trigger Context is an UNDELETE operation
|
||||
*/
|
||||
public Boolean isUndelete()
|
||||
{
|
||||
return this.triggerOperation == System.TriggerOperation.AFTER_UNDELETE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns TRUE if the Trigger Context is an BEFORE operation
|
||||
*/
|
||||
public Boolean isBefore()
|
||||
{
|
||||
return this.triggerOperation == System.TriggerOperation.BEFORE_DELETE
|
||||
|| this.triggerOperation == System.TriggerOperation.BEFORE_INSERT
|
||||
|| this.triggerOperation == System.TriggerOperation.BEFORE_UPDATE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns TRUE if the Trigger Context is an AFTER operation
|
||||
*/
|
||||
public Boolean isAfter()
|
||||
{
|
||||
return this.triggerOperation == System.TriggerOperation.AFTER_DELETE
|
||||
|| this.triggerOperation == System.TriggerOperation.AFTER_INSERT
|
||||
|| this.triggerOperation == System.TriggerOperation.AFTER_UNDELETE
|
||||
|| this.triggerOperation == System.TriggerOperation.AFTER_UPDATE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor to construct the Trigger Context class
|
||||
*/
|
||||
public class Constructor implements fflib_IDomainConstructor
|
||||
{
|
||||
public fflib_IDomain construct(List<Object> objects)
|
||||
{
|
||||
return new fflib_TriggerContext((List<SObject>) objects);
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>53.0</apiVersion>
|
||||
<status>Active</status>
|
||||
</ApexClass>
|
||||
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* File Name: fflib_TriggerHandler
|
||||
*
|
||||
* @author architect ir. Wilhelmus G.J. Velzeboer
|
||||
*
|
||||
* Copyright (c), W.G.J. Velzeboer,
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above author notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* - Neither the name of the author nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
public virtual with sharing class fflib_TriggerHandler
|
||||
implements fflib_ITriggerHandler
|
||||
{
|
||||
public static final System.Type DEFAUT_TRIGGERCONTEXT_TYPE = fflib_TriggerContext.class;
|
||||
|
||||
@TestVisible
|
||||
protected System.Type triggerContextType
|
||||
{
|
||||
get
|
||||
{
|
||||
if (triggerContextType == null)
|
||||
{
|
||||
triggerContextType = DEFAUT_TRIGGERCONTEXT_TYPE;
|
||||
}
|
||||
return triggerContextType;
|
||||
}
|
||||
private set;
|
||||
}
|
||||
|
||||
protected fflib_ITriggerContext triggerContext;
|
||||
|
||||
/**
|
||||
* Provides access to the trigger action configurations and allowing it to be mocked in unit-tests
|
||||
*/
|
||||
@TestVisible
|
||||
private static List<fflib_ITriggerActionConfig> configs = new List<fflib_ITriggerActionConfig>();
|
||||
|
||||
/**
|
||||
* Main method to execute the trigger handler to call the trigger actions
|
||||
*/
|
||||
public void run()
|
||||
{
|
||||
loadConfiguration();
|
||||
fflib_ITriggerContext ctx = getTriggerContext();
|
||||
execute(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the trigger handler in which trigger context it is running
|
||||
*
|
||||
* @param ctx A configured Trigger Context instance
|
||||
*
|
||||
* @return Instance of itself to allow for method chaining
|
||||
*/
|
||||
public fflib_ITriggerHandler setTriggerContext(fflib_ITriggerContext ctx)
|
||||
{
|
||||
this.triggerContext = ctx;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the trigger handler in which trigger context it is running
|
||||
*
|
||||
* @param classType A reference to the Trigger Context class to use when executing the trigger handler
|
||||
*
|
||||
* @return Instance of itself to allow for method chaining
|
||||
*/
|
||||
public fflib_ITriggerHandler setTriggerContextType(Type classType)
|
||||
{
|
||||
this.triggerContextType = classType;
|
||||
return this;
|
||||
}
|
||||
|
||||
@TestVisible
|
||||
protected void addTriggerActionConfig(fflib_TriggerAction__mdt config)
|
||||
{
|
||||
configs.add(new fflib_TriggerActionConfig(config));
|
||||
}
|
||||
|
||||
protected void execute(fflib_ITriggerContext ctx)
|
||||
{
|
||||
fflib_UnitOfWork unitOfWork = new fflib_UnitOfWork();
|
||||
for (fflib_ITriggerActionConfig triggerActionConfig : configs)
|
||||
{
|
||||
fflib_ITriggerAction triggerAction = triggerActionConfig.newInstance();
|
||||
|
||||
triggerAction.setContext(ctx);
|
||||
triggerAction.setSequence(triggerActionConfig.getSequence());
|
||||
|
||||
// Queueable only makes sense in an onAfter context.
|
||||
if (triggerActionConfig.isQueued() && ctx.isAfter())
|
||||
{
|
||||
triggerAction.enableQueueable(triggerActionConfig.isDedicatedQueued());
|
||||
}
|
||||
|
||||
unitOfWork.addWork(triggerAction);
|
||||
}
|
||||
unitOfWork.doWork();
|
||||
}
|
||||
|
||||
@TestVisible
|
||||
protected void loadConfiguration()
|
||||
{
|
||||
if (configs.isEmpty() == false) return;
|
||||
|
||||
for (fflib_TriggerAction__mdt action : new fflib_TriggerActionConfigSelector().selectAll())
|
||||
{
|
||||
configs.add( new fflib_TriggerActionConfig(action));
|
||||
}
|
||||
}
|
||||
|
||||
private fflib_ITriggerContext constructTriggerContext()
|
||||
{
|
||||
// Construct the trigger context domain class constructor class
|
||||
String triggerContextClassName = triggerContextType.getName();
|
||||
Type constructableClass =
|
||||
triggerContextClassName.endsWith('Constructor')
|
||||
? Type.forName(triggerContextClassName)
|
||||
: Type.forName(triggerContextClassName + '.Constructor');
|
||||
fflib_IDomainConstructor domainConstructor = (fflib_IDomainConstructor) constructableClass.newInstance();
|
||||
|
||||
// Construct the trigger context domain class with the appropriate record set
|
||||
if (Trigger.isInsert) return (fflib_ITriggerContext) domainConstructor.construct(Trigger.new);
|
||||
else if (Trigger.isUpdate) return (fflib_ITriggerContext) domainConstructor.construct(Trigger.new);
|
||||
else if (Trigger.isDelete) return (fflib_ITriggerContext) domainConstructor.construct(Trigger.old);
|
||||
else if (Trigger.isUndelete) return (fflib_ITriggerContext) domainConstructor.construct(Trigger.new);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private fflib_ITriggerContext getTriggerContext()
|
||||
{
|
||||
if (triggerContext == null) constructTriggerContext();
|
||||
return triggerContext;
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>53.0</apiVersion>
|
||||
<status>Active</status>
|
||||
</ApexClass>
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* File Name: fflib_ALogAppenderFilter
|
||||
* Description: Log Appender that includes a filter for logginglevel
|
||||
*
|
||||
* @author architect ir. Wilhelmus G.J. Velzeboer
|
||||
*
|
||||
* Copyright (c), W.G.J. Velzeboer,
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above author notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* - Neither the name of the author nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
public interface fflib_ITriggerAction extends fflib_IDoWork
|
||||
{
|
||||
/**
|
||||
* Perform logic during the after delete phase
|
||||
**/
|
||||
void onAfterDelete();
|
||||
|
||||
/**
|
||||
* Perform logic during the after Insert phase
|
||||
**/
|
||||
void onAfterInsert();
|
||||
|
||||
/**
|
||||
* Perform logic during the after Undelete phase
|
||||
**/
|
||||
void onAfterUnDelete();
|
||||
|
||||
/**
|
||||
* Perform logic during the after update phase
|
||||
**/
|
||||
void onAfterUpdate();
|
||||
|
||||
/**
|
||||
* Perform logic during the before delete phase
|
||||
**/
|
||||
void onBeforeDelete();
|
||||
|
||||
/**
|
||||
* Perform logic during the before insert phase
|
||||
**/
|
||||
void onBeforeInsert();
|
||||
|
||||
/**
|
||||
* Perform logic during the before update phase
|
||||
**/
|
||||
void onBeforeUpdate();
|
||||
|
||||
/**
|
||||
* Method used by the TriggerHandler and us to pass in the trigger context
|
||||
*
|
||||
* @param ctx Instance of fflib_ITriggerContext
|
||||
*
|
||||
* @return Instance of itself to allow for method chaining
|
||||
*/
|
||||
fflib_ITriggerAction setContext(fflib_ITriggerContext ctx);
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>53.0</apiVersion>
|
||||
<status>Active</status>
|
||||
</ApexClass>
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* File Name: fflib_ITriggerActionConfig
|
||||
*
|
||||
* @author architect ir. Wilhelmus G.J. Velzeboer
|
||||
*
|
||||
* Copyright (c), W.G.J. Velzeboer,
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above author notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* - Neither the name of the author nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
public interface fflib_ITriggerActionConfig
|
||||
{
|
||||
/**
|
||||
* @return Returns the sequence number of the trigger action
|
||||
*/
|
||||
Integer getSequence();
|
||||
|
||||
/**
|
||||
* @return Returns TRUE if the trigger action should be executed in a dedicated Queueable context
|
||||
*/
|
||||
Boolean isDedicatedQueued();
|
||||
|
||||
/**
|
||||
* @return Returns TRUE if the trigger action can be executed in an Queueable context
|
||||
*/
|
||||
Boolean isQueued();
|
||||
|
||||
/**
|
||||
* Creates a new instance of the trigger action class based on the provided implementation type in the configuration
|
||||
*
|
||||
* @return Instance of the Trigger Action class
|
||||
*/
|
||||
fflib_ITriggerAction newInstance();
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>53.0</apiVersion>
|
||||
<status>Active</status>
|
||||
</ApexClass>
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* File Name: fflib_ITriggerActionConfigSelector
|
||||
*
|
||||
* @author architect ir. Wilhelmus G.J. Velzeboer
|
||||
*
|
||||
* Copyright (c), W.G.J. Velzeboer,
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above author notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* - Neither the name of the author nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
public interface fflib_ITriggerActionConfigSelector
|
||||
{
|
||||
/**
|
||||
* @return Query all fflib_TriggerAction__mdt records
|
||||
*/
|
||||
List<fflib_TriggerAction__mdt> selectAll();
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>53.0</apiVersion>
|
||||
<status>Active</status>
|
||||
</ApexClass>
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* File Name: fflib_ITriggerContext
|
||||
*
|
||||
* @author architect ir. Wilhelmus G.J. Velzeboer
|
||||
*
|
||||
* Copyright (c), W.G.J. Velzeboer,
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above author notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* - Neither the name of the author nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
public interface fflib_ITriggerContext
|
||||
{
|
||||
/**
|
||||
* Detects whether any values in context records have changed for given field as string
|
||||
* Returns list of SObject records that have changes in the specified fields
|
||||
**/
|
||||
List<SObject> getChangedRecords(String fieldName);
|
||||
|
||||
/**
|
||||
* Detects whether any values in context records have changed for given fields as strings
|
||||
* Returns list of SObject records that have changes in the specified fields
|
||||
**/
|
||||
List<SObject> getChangedRecords(Set<String> fieldNames);
|
||||
|
||||
/**
|
||||
* Detects whether any values in context records have changed for given field as token
|
||||
* Returns list of SObject records that have changes in the specified fields
|
||||
**/
|
||||
List<SObject> getChangedRecords(Schema.SObjectField fieldToken);
|
||||
|
||||
/**
|
||||
* Detects whether any values in context records have changed for given fields as tokens
|
||||
* Returns list of SObject records that have changes in the specified fields
|
||||
**/
|
||||
List<SObject> getChangedRecords(Set<Schema.SObjectField> fieldTokens);
|
||||
|
||||
/**
|
||||
* Provides access to Trigger.oldMap or the mock when configured in a unit-test
|
||||
**/
|
||||
Map<Id, SObject> getExistingRecords();
|
||||
|
||||
/**
|
||||
* @return Returns the System.TriggerOperation enum value
|
||||
*/
|
||||
System.TriggerOperation getTriggerOperation();
|
||||
|
||||
/**
|
||||
* Provides access to the data represented by this Trigger Context
|
||||
**/
|
||||
List<SObject> getRecords();
|
||||
|
||||
/**
|
||||
* @return Returns TRUE if the Trigger Context is an INSERT operation
|
||||
*/
|
||||
Boolean isInsert();
|
||||
|
||||
/**
|
||||
* @return Returns TRUE if the Trigger Context is an UPDATE operation
|
||||
*/
|
||||
Boolean isUpdate();
|
||||
|
||||
/**
|
||||
* @return Returns TRUE if the Trigger Context is an DELETE operation
|
||||
*/
|
||||
Boolean isDelete();
|
||||
|
||||
/**
|
||||
* @return Returns TRUE if the Trigger Context is an UNDELETE operation
|
||||
*/
|
||||
Boolean isUndelete();
|
||||
|
||||
/**
|
||||
* @return Returns TRUE if the Trigger Context is an BEFORE operation
|
||||
*/
|
||||
Boolean isBefore();
|
||||
|
||||
/**
|
||||
* @return Returns TRUE if the Trigger Context is an AFTER operation
|
||||
*/
|
||||
Boolean isAfter();
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>53.0</apiVersion>
|
||||
<status>Active</status>
|
||||
</ApexClass>
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* File Name: fflib_ALogAppenderFilter
|
||||
* Description: Log Appender that includes a filter for logginglevel
|
||||
*
|
||||
* @author architect ir. Wilhelmus G.J. Velzeboer
|
||||
*
|
||||
* Copyright (c), W.G.J. Velzeboer,
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above author notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* - Neither the name of the author nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
public interface fflib_ITriggerHandler
|
||||
{
|
||||
/**
|
||||
* Main method to execute the trigger handler to call the trigger actions
|
||||
*/
|
||||
void run();
|
||||
|
||||
/**
|
||||
* Tells the trigger handler in which trigger context it is running
|
||||
*
|
||||
* @param ctx A configured Trigger Context instance
|
||||
*
|
||||
* @return Instance of itself to allow for method chaining
|
||||
*/
|
||||
fflib_ITriggerHandler setTriggerContext(fflib_ITriggerContext ctx);
|
||||
|
||||
/**
|
||||
* Tells the trigger handler in which trigger context it is running
|
||||
*
|
||||
* @param classType A reference to the Trigger Context class to use when executing the trigger handler
|
||||
*
|
||||
* @return Instance of itself to allow for method chaining
|
||||
*/
|
||||
fflib_ITriggerHandler setTriggerContextType(System.Type classType);
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>53.0</apiVersion>
|
||||
<status>Active</status>
|
||||
</ApexClass>
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Layout xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<layoutSections>
|
||||
<customLabel>false</customLabel>
|
||||
<detailHeading>false</detailHeading>
|
||||
<editHeading>true</editHeading>
|
||||
<label>Information</label>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Required</behavior>
|
||||
<field>MasterLabel</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Required</behavior>
|
||||
<field>DeveloperName</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>IsProtected</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Required</behavior>
|
||||
<field>NamespacePrefix</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<style>TwoColumnsTopToBottom</style>
|
||||
</layoutSections>
|
||||
<layoutSections>
|
||||
<customLabel>true</customLabel>
|
||||
<detailHeading>true</detailHeading>
|
||||
<editHeading>true</editHeading>
|
||||
<label>Trigger Action Configuration</label>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>ObjectType__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>ObjectTypeAlternate__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Required</behavior>
|
||||
<field>ImplementationType__c</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Required</behavior>
|
||||
<field>ExecutionContext__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Sequence__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>Stateful__c</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<style>TwoColumnsLeftToRight</style>
|
||||
</layoutSections>
|
||||
<layoutSections>
|
||||
<customLabel>true</customLabel>
|
||||
<detailHeading>true</detailHeading>
|
||||
<editHeading>true</editHeading>
|
||||
<label>Trigger Operation Type</label>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>BeforeInsert__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>BeforeUpdate__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>BeforeDelete__c</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>AfterInsert__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>AfterUpdate__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>AfterDelete__c</field>
|
||||
</layoutItems>
|
||||
<layoutItems>
|
||||
<behavior>Edit</behavior>
|
||||
<field>AfterUndelete__c</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<style>TwoColumnsLeftToRight</style>
|
||||
</layoutSections>
|
||||
<layoutSections>
|
||||
<customLabel>false</customLabel>
|
||||
<detailHeading>false</detailHeading>
|
||||
<editHeading>true</editHeading>
|
||||
<label>System Information</label>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Readonly</behavior>
|
||||
<field>CreatedById</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<layoutColumns>
|
||||
<layoutItems>
|
||||
<behavior>Readonly</behavior>
|
||||
<field>LastModifiedById</field>
|
||||
</layoutItems>
|
||||
</layoutColumns>
|
||||
<style>TwoColumnsTopToBottom</style>
|
||||
</layoutSections>
|
||||
<layoutSections>
|
||||
<customLabel>true</customLabel>
|
||||
<detailHeading>true</detailHeading>
|
||||
<editHeading>false</editHeading>
|
||||
<label>Custom Links</label>
|
||||
<layoutColumns/>
|
||||
<layoutColumns/>
|
||||
<layoutColumns/>
|
||||
<style>CustomLinks</style>
|
||||
</layoutSections>
|
||||
<showEmailCheckbox>false</showEmailCheckbox>
|
||||
<showHighlightsPanel>false</showHighlightsPanel>
|
||||
<showInteractionLogPanel>false</showInteractionLogPanel>
|
||||
<showRunAssignmentRulesCheckbox>false</showRunAssignmentRulesCheckbox>
|
||||
<showSubmitAndAttachButton>false</showSubmitAndAttachButton>
|
||||
<summaryLayout>
|
||||
<masterLabel>00h1g000003HGOK</masterLabel>
|
||||
<sizeX>4</sizeX>
|
||||
<sizeY>0</sizeY>
|
||||
<summaryLayoutStyle>Default</summaryLayoutStyle>
|
||||
</summaryLayout>
|
||||
</Layout>
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<label>Trigger Action</label>
|
||||
<pluralLabel>Trigger Actions</pluralLabel>
|
||||
<visibility>Protected</visibility>
|
||||
</CustomObject>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>AfterDelete__c</fullName>
|
||||
<defaultValue>false</defaultValue>
|
||||
<externalId>false</externalId>
|
||||
<fieldManageability>DeveloperControlled</fieldManageability>
|
||||
<label>After Delete</label>
|
||||
<type>Checkbox</type>
|
||||
</CustomField>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>AfterInsert__c</fullName>
|
||||
<defaultValue>false</defaultValue>
|
||||
<externalId>false</externalId>
|
||||
<fieldManageability>DeveloperControlled</fieldManageability>
|
||||
<label>After Insert</label>
|
||||
<type>Checkbox</type>
|
||||
</CustomField>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>AfterUndelete__c</fullName>
|
||||
<defaultValue>false</defaultValue>
|
||||
<externalId>false</externalId>
|
||||
<fieldManageability>DeveloperControlled</fieldManageability>
|
||||
<label>After Undelete</label>
|
||||
<type>Checkbox</type>
|
||||
</CustomField>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>AfterUpdate__c</fullName>
|
||||
<defaultValue>false</defaultValue>
|
||||
<externalId>false</externalId>
|
||||
<fieldManageability>DeveloperControlled</fieldManageability>
|
||||
<label>After Update</label>
|
||||
<type>Checkbox</type>
|
||||
</CustomField>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>BeforeDelete__c</fullName>
|
||||
<defaultValue>false</defaultValue>
|
||||
<externalId>false</externalId>
|
||||
<fieldManageability>DeveloperControlled</fieldManageability>
|
||||
<label>Before Delete</label>
|
||||
<type>Checkbox</type>
|
||||
</CustomField>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>BeforeInsert__c</fullName>
|
||||
<defaultValue>false</defaultValue>
|
||||
<externalId>false</externalId>
|
||||
<fieldManageability>DeveloperControlled</fieldManageability>
|
||||
<label>Before Insert</label>
|
||||
<type>Checkbox</type>
|
||||
</CustomField>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>BeforeUpdate__c</fullName>
|
||||
<defaultValue>false</defaultValue>
|
||||
<externalId>false</externalId>
|
||||
<fieldManageability>DeveloperControlled</fieldManageability>
|
||||
<label>Before Update</label>
|
||||
<type>Checkbox</type>
|
||||
</CustomField>
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>ExecutionContext__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<fieldManageability>DeveloperControlled</fieldManageability>
|
||||
<label>Execution Context</label>
|
||||
<required>true</required>
|
||||
<type>Picklist</type>
|
||||
<valueSet>
|
||||
<restricted>true</restricted>
|
||||
<valueSetDefinition>
|
||||
<sorted>false</sorted>
|
||||
<value>
|
||||
<fullName>Realtime</fullName>
|
||||
<default>true</default>
|
||||
<label>Realtime</label>
|
||||
</value>
|
||||
<value>
|
||||
<fullName>Queued</fullName>
|
||||
<default>false</default>
|
||||
<label>Queued</label>
|
||||
</value>
|
||||
<value>
|
||||
<fullName>Dedicated Queued</fullName>
|
||||
<default>false</default>
|
||||
<label>Dedicated Queued</label>
|
||||
</value>
|
||||
</valueSetDefinition>
|
||||
</valueSet>
|
||||
</CustomField>
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>ImplementationType__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<fieldManageability>SubscriberControlled</fieldManageability>
|
||||
<label>Implementation Type</label>
|
||||
<length>255</length>
|
||||
<required>true</required>
|
||||
<type>Text</type>
|
||||
<unique>false</unique>
|
||||
</CustomField>
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>ObjectTypeAlternate__c</fullName>
|
||||
<description>Use this field to specify the SObject API name to bind to when the "Object Type" field does not list that particular SObject. Only specify the "Object Type" field or this one; not both.</description>
|
||||
<externalId>false</externalId>
|
||||
<fieldManageability>SubscriberControlled</fieldManageability>
|
||||
<inlineHelpText>Use this field to specify the SObject API name to bind to when the "Object Type" field does not list that particular SObject. Only specify the "Object Type" field or this one; not both.</inlineHelpText>
|
||||
<label>ObjectType Alternate</label>
|
||||
<length>255</length>
|
||||
<required>false</required>
|
||||
<type>Text</type>
|
||||
<unique>false</unique>
|
||||
</CustomField>
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>ObjectType__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<fieldManageability>DeveloperControlled</fieldManageability>
|
||||
<label>Object Type</label>
|
||||
<referenceTo>EntityDefinition</referenceTo>
|
||||
<relationshipLabel>TriggerActions</relationshipLabel>
|
||||
<relationshipName>TriggerActions</relationshipName>
|
||||
<required>false</required>
|
||||
<type>MetadataRelationship</type>
|
||||
<unique>false</unique>
|
||||
</CustomField>
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Sequence__c</fullName>
|
||||
<externalId>false</externalId>
|
||||
<fieldManageability>SubscriberControlled</fieldManageability>
|
||||
<label>Sequence</label>
|
||||
<precision>18</precision>
|
||||
<required>false</required>
|
||||
<scale>0</scale>
|
||||
<type>Number</type>
|
||||
<unique>false</unique>
|
||||
</CustomField>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>Stateful__c</fullName>
|
||||
<defaultValue>false</defaultValue>
|
||||
<externalId>false</externalId>
|
||||
<fieldManageability>DeveloperControlled</fieldManageability>
|
||||
<label>Stateful</label>
|
||||
<type>Checkbox</type>
|
||||
</CustomField>
|
||||
@@ -30,15 +30,24 @@
|
||||
*/
|
||||
public abstract class fflib_DoWork implements fflib_IDoWork, Comparable
|
||||
{
|
||||
protected Integer priority = 0;
|
||||
protected OrderType orderingType = OrderType.PRIORITY; // Set the default ordering to using Priority over Sequencing
|
||||
protected Integer orderNumber = 0; // Either the Priority or Sequence value, based on the orderingType
|
||||
|
||||
protected Boolean dedicatedQueueable;
|
||||
|
||||
public virtual Integer compareTo(Object compareToObject)
|
||||
{
|
||||
fflib_IDoWork compareTo = (fflib_IDoWork) compareToObject;
|
||||
if (priority == compareTo.getPriority()) return 0;
|
||||
if (priority > compareTo.getPriority()) return -1;
|
||||
return 1;
|
||||
|
||||
if (orderNumber == compareTo.getOrderNumber()) return 0;
|
||||
if (orderingType == OrderType.PRIORITY)
|
||||
{
|
||||
if (orderNumber > compareTo.getOrderNumber()) return -1; else return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (orderNumber > compareTo.getOrderNumber()) return 1; else return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,13 +74,13 @@ public abstract class fflib_DoWork implements fflib_IDoWork, Comparable
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the priority of this work item. The higher the number the higher the priority
|
||||
* Gets the priority or sequence number of this work item. The higher the number the higher the priority
|
||||
*
|
||||
* @return Returns the priority
|
||||
*/
|
||||
public virtual Integer getPriority()
|
||||
public virtual Integer getOrderNumber()
|
||||
{
|
||||
return this.priority;
|
||||
return this.orderNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,7 +116,16 @@ public abstract class fflib_DoWork implements fflib_IDoWork, Comparable
|
||||
*/
|
||||
public virtual fflib_IDoWork setPriority(Integer priority)
|
||||
{
|
||||
this.priority = priority;
|
||||
this.orderNumber = priority;
|
||||
this.orderingType = OrderType.PRIORITY;
|
||||
return this;
|
||||
}
|
||||
public virtual fflib_IDoWork setSequence(Integer sequence)
|
||||
{
|
||||
this.orderNumber = sequence;
|
||||
this.orderingType = OrderType.SEQUENCE;
|
||||
return this;
|
||||
}
|
||||
|
||||
private enum OrderType { PRIORITY, SEQUENCE }
|
||||
}
|
||||
@@ -129,10 +129,17 @@ public virtual class fflib_UnitOfWork
|
||||
*/
|
||||
public virtual void doWork()
|
||||
{
|
||||
sortAndDoWork();
|
||||
doNextWork();
|
||||
}
|
||||
|
||||
private void sortAndDoWork()
|
||||
{
|
||||
if (this.work.isEmpty()) return;
|
||||
|
||||
this.work.sort();
|
||||
doRealTimeWork();
|
||||
doQueuedWork();
|
||||
doNextWork();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -52,11 +52,11 @@ public interface fflib_IDoWork extends Comparable
|
||||
fflib_IDoWork enableQueueable(Boolean dedicated);
|
||||
|
||||
/**
|
||||
* Gets the priority of this work item. The higher the number the higher the priority
|
||||
* Gets the priority or sequence number of this work item. The higher the number the higher the priority
|
||||
*
|
||||
* @return Returns the priority
|
||||
*/
|
||||
Integer getPriority();
|
||||
Integer getOrderNumber();
|
||||
|
||||
/**
|
||||
* @return Returns true when the work item is intended to be executed in separate UnitOfWork executed in a Queueable context
|
||||
@@ -81,4 +81,5 @@ public interface fflib_IDoWork extends Comparable
|
||||
* @return Instance of itself to allow for chaining
|
||||
*/
|
||||
fflib_IDoWork setPriority(Integer priority);
|
||||
fflib_IDoWork setSequence(Integer sequence);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* File Name: MyTest
|
||||
* Description:
|
||||
*
|
||||
* @author: architect ir. Wilhelmus G.J. Velzeboer | wvelzebo@its.jnj.com
|
||||
*/
|
||||
public with sharing class MyTest
|
||||
{
|
||||
|
||||
public class WorkA extends fflib_DoWork
|
||||
{
|
||||
public void doWork()
|
||||
{
|
||||
System.debug('Work A executing');
|
||||
insert new fflib_LogEntry__c(
|
||||
LogLevel__c = 'INFO',
|
||||
Message__c = 'WorkA'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public class WorkB extends fflib_DoWork
|
||||
{
|
||||
public void doWork()
|
||||
{
|
||||
System.debug('Work B executing');
|
||||
insert new fflib_LogEntry__c(
|
||||
LogLevel__c = 'INFO',
|
||||
Message__c = 'WorkB'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public class WorkC extends fflib_DoWork
|
||||
{
|
||||
public void doWork()
|
||||
{
|
||||
System.debug('Work C executing');
|
||||
insert new fflib_LogEntry__c(
|
||||
LogLevel__c = 'INFO',
|
||||
Message__c = 'WorkC'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>53.0</apiVersion>
|
||||
<status>Active</status>
|
||||
</ApexClass>
|
||||
+266
@@ -0,0 +1,266 @@
|
||||
/**
|
||||
* File Name: fflib_TriggerHandlerTest
|
||||
* Description: Unit Test class for fflib_TriggerHandler implementation
|
||||
*
|
||||
* @author architect ir. Wilhelmus G.J. Velzeboer
|
||||
*
|
||||
* Copyright (c), W.G.J. Velzeboer,
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above author notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* - Neither the name of the author nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
@IsTest(IsParallel=true)
|
||||
private class fflib_TriggerHandlerTest
|
||||
{
|
||||
private static Boolean handlerGotInvoked = false;
|
||||
@IsTest
|
||||
static void testBehavior()
|
||||
{
|
||||
// GIVEN a set of records with a configured trigger handler
|
||||
|
||||
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_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'
|
||||
);
|
||||
|
||||
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.loadConfiguration();
|
||||
triggerHandler.addTriggerActionConfig(triggerAction);
|
||||
triggerHandler
|
||||
.setTriggerContextType(fflib_TriggerContext.class)
|
||||
.setTriggerContext(ctx)
|
||||
.run();
|
||||
System.Test.stopTest();
|
||||
|
||||
// THEN the trigger handler should be invoked
|
||||
System.assert(handlerGotInvoked, 'Whoops, the Trigger Action was not invoked');
|
||||
System.assertEquals(records, ctx.records);
|
||||
System.assert(ctx.isInsert());
|
||||
System.assert(ctx.isBefore());
|
||||
System.assert(ctx.isDelete() == false);
|
||||
System.assert(ctx.isUndelete() == false);
|
||||
}
|
||||
|
||||
@IsTest
|
||||
private static void itShouldReturnTheChangedRecordsBySObjectFields()
|
||||
{
|
||||
// GIVEN a domain with old and changed records
|
||||
|
||||
Id idLuke = fflib_IDGenerator.generate(Account.SObjectType);
|
||||
Id idHan = fflib_IDGenerator.generate(Account.SObjectType);
|
||||
Id idLeia = fflib_IDGenerator.generate(Account.SObjectType);
|
||||
List<Account> oldRecords = new List<Account>
|
||||
{
|
||||
new Account(Id = idLuke, Name = 'Luke', Description = 'Jedi'),
|
||||
new Account(Id = idHan, Name = 'Han', Description = 'Pilot'),
|
||||
new Account(Id = idLeia, Name = 'Leia')
|
||||
};
|
||||
|
||||
List<Account> newRecords = oldRecords.deepClone(true, true, true);
|
||||
newRecords.get(0).Name = 'Luke SkyWalker';
|
||||
newRecords.get(0).Description = 'Jedi Master';
|
||||
newRecords.get(1).Name = 'Han Solo';
|
||||
|
||||
fflib_TriggerContext ctx = new fflib_TriggerContext(newRecords);
|
||||
ctx.existingRecords = new Map<Id, SObject>(oldRecords);
|
||||
|
||||
// WHEN we create a domain with ExistingRecords and request the changed records
|
||||
List<SObject> result = ctx.getChangedRecords(Account.Name);
|
||||
|
||||
// THEN it should only return the changed records
|
||||
Map<Id, SObject> resultMap = new Map<Id, SObject>(result);
|
||||
System.assertEquals(2, result.size());
|
||||
System.assert(resultMap.containsKey(idLuke));
|
||||
System.assert(resultMap.containsKey(idHan));
|
||||
}
|
||||
|
||||
@IsTest
|
||||
private static void itShouldReturnTheChangedRecordsByStringFields()
|
||||
{
|
||||
// GIVEN a domain with old and changed records
|
||||
|
||||
Id idLuke = fflib_IDGenerator.generate(Account.SObjectType);
|
||||
Id idHan = fflib_IDGenerator.generate(Account.SObjectType);
|
||||
Id idLeia = fflib_IDGenerator.generate(Account.SObjectType);
|
||||
List<Account> oldRecords = new List<Account>
|
||||
{
|
||||
new Account(Id = idLuke, Name = 'Luke', Description = 'Jedi'),
|
||||
new Account(Id = idHan, Name = 'Han', Description = 'Pilot'),
|
||||
new Account(Id = idLeia, Name = 'Leia')
|
||||
};
|
||||
|
||||
List<Account> newRecords = oldRecords.deepClone(true, true, true);
|
||||
newRecords.get(0).Name = 'Luke SkyWalker';
|
||||
newRecords.get(0).Description = 'Jedi Master';
|
||||
newRecords.get(1).Name = 'Han Solo';
|
||||
|
||||
fflib_TriggerContext ctx = new fflib_TriggerContext(newRecords);
|
||||
ctx.existingRecords = new Map<Id, SObject>(oldRecords);
|
||||
|
||||
// WHEN we create a domain with ExistingRecords and request the changed records
|
||||
List<SObject> result = ctx.getChangedRecords('Description');
|
||||
|
||||
// THEN it should only return the changed records
|
||||
Map<Id, SObject> resultMap = new Map<Id, SObject>(result);
|
||||
System.assertEquals(1, result.size());
|
||||
System.assert(resultMap.containsKey(idLuke));
|
||||
}
|
||||
|
||||
@IsTest
|
||||
private static void itShouldCallTriggerActionOnAfterDelete()
|
||||
{
|
||||
List<Account> records = new List<Account>
|
||||
{
|
||||
new Account(Id = fflib_IDGenerator.generate(Account.SObjectType))
|
||||
};
|
||||
|
||||
fflib_TriggerContext ctx = new fflib_TriggerContext(records);
|
||||
ctx.triggerOperation = System.TriggerOperation.AFTER_DELETE;
|
||||
new MyTriggerAction().setContext(ctx).doWork();
|
||||
|
||||
System.assert(ctx.isAfter());
|
||||
System.assert(ctx.isDelete());
|
||||
}
|
||||
|
||||
@IsTest
|
||||
private static void itShouldCallTriggerActionOnAfterInsert()
|
||||
{
|
||||
List<Account> records = new List<Account>
|
||||
{
|
||||
new Account(Id = fflib_IDGenerator.generate(Account.SObjectType))
|
||||
};
|
||||
|
||||
fflib_TriggerContext ctx = new fflib_TriggerContext(records);
|
||||
ctx.triggerOperation = System.TriggerOperation.AFTER_INSERT;
|
||||
new MyTriggerAction().setContext(ctx).doWork();
|
||||
|
||||
System.assert(ctx.isAfter());
|
||||
System.assert(ctx.isInsert());
|
||||
}
|
||||
|
||||
@IsTest
|
||||
private static void itShouldCallTriggerActionOnAfterUndelete()
|
||||
{
|
||||
List<Account> records = new List<Account>
|
||||
{
|
||||
new Account(Id = fflib_IDGenerator.generate(Account.SObjectType))
|
||||
};
|
||||
|
||||
fflib_TriggerContext ctx = new fflib_TriggerContext(records);
|
||||
ctx.triggerOperation = System.TriggerOperation.AFTER_UNDELETE;
|
||||
new MyTriggerAction().setContext(ctx).doWork();
|
||||
|
||||
System.assert(ctx.isAfter());
|
||||
System.assert(ctx.isUndelete());
|
||||
}
|
||||
|
||||
@IsTest
|
||||
private static void itShouldCallTriggerActionOnAfterUpdate()
|
||||
{
|
||||
List<Account> records = new List<Account>
|
||||
{
|
||||
new Account(Id = fflib_IDGenerator.generate(Account.SObjectType))
|
||||
};
|
||||
|
||||
fflib_TriggerContext ctx = new fflib_TriggerContext(records);
|
||||
ctx.triggerOperation = System.TriggerOperation.AFTER_UPDATE;
|
||||
new MyTriggerAction().setContext(ctx).doWork();
|
||||
|
||||
System.assert(ctx.isAfter());
|
||||
System.assert(ctx.isUpdate());
|
||||
}
|
||||
|
||||
@IsTest
|
||||
private static void itShouldCallTriggerActionOnBeforeDelete()
|
||||
{
|
||||
List<Account> records = new List<Account>
|
||||
{
|
||||
new Account(Id = fflib_IDGenerator.generate(Account.SObjectType))
|
||||
};
|
||||
|
||||
fflib_TriggerContext ctx = new fflib_TriggerContext(records);
|
||||
ctx.triggerOperation = System.TriggerOperation.BEFORE_DELETE;
|
||||
new MyTriggerAction().setContext(ctx).doWork();
|
||||
|
||||
System.assert(ctx.isBefore());
|
||||
System.assert(ctx.isDelete());
|
||||
}
|
||||
|
||||
@IsTest
|
||||
private static void itShouldCallTriggerActionOnBeforeInsert()
|
||||
{
|
||||
List<Account> records = new List<Account>
|
||||
{
|
||||
new Account(Id = fflib_IDGenerator.generate(Account.SObjectType))
|
||||
};
|
||||
|
||||
fflib_TriggerContext ctx = new fflib_TriggerContext(records);
|
||||
ctx.triggerOperation = System.TriggerOperation.BEFORE_INSERT;
|
||||
new MyTriggerAction().setContext(ctx).doWork();
|
||||
|
||||
System.assert(ctx.isBefore());
|
||||
System.assert(ctx.isInsert());
|
||||
}
|
||||
|
||||
@IsTest
|
||||
private static void itShouldCallTriggerActionOnBeforeUpdate()
|
||||
{
|
||||
List<Account> records = new List<Account>
|
||||
{
|
||||
new Account(Id = fflib_IDGenerator.generate(Account.SObjectType))
|
||||
};
|
||||
|
||||
fflib_TriggerContext ctx = new fflib_TriggerContext(records);
|
||||
ctx.triggerOperation = System.TriggerOperation.BEFORE_UPDATE;
|
||||
new MyTriggerAction().setContext(ctx).doWork();
|
||||
|
||||
System.assert(ctx.isBefore());
|
||||
System.assert(ctx.isUpdate());
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class MyTriggerAction extends fflib_TriggerAction // Todo - rename handler to action
|
||||
{
|
||||
public override void onBeforeInsert()
|
||||
{
|
||||
fflib_TriggerHandlerTest.handlerGotInvoked = true;
|
||||
|
||||
List<SObject> records = triggerContext.getRecords();
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>53.0</apiVersion>
|
||||
<status>Active</status>
|
||||
</ApexClass>
|
||||
@@ -101,7 +101,7 @@ private class fflib_UnitOfWorkTest
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void itShouldExecuteAllItemsInTheCorrectOrder()
|
||||
static void itShouldExecuteAllItemsInTheCorrectOrderOfPriority()
|
||||
{
|
||||
// GIVEN - Three different work items added to the fflib_DoWork
|
||||
Work workA = (Work) new WorkA().setPriority(1);
|
||||
@@ -122,6 +122,28 @@ private class fflib_UnitOfWorkTest
|
||||
System.assert(orderOfExecution.get(2).contains('WorkA'), 'Expected Work B with priority 1 to be executed last');
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void itShouldExecuteAllItemsInTheCorrectOrderOfSequence()
|
||||
{
|
||||
// GIVEN - Three different work items added to the fflib_DoWork
|
||||
Work workA = (Work) new WorkA().setSequence(3);
|
||||
Work workB = (Work) new WorkB().setSequence(2);
|
||||
Work workC = (Work) new WorkC().setSequence(1);
|
||||
fflib_UnitOfWork myWork = new fflib_UnitOfWork().addWork(
|
||||
new List<fflib_IDoWork> {workA, workB, workC}
|
||||
);
|
||||
|
||||
// WHEN - The work is being executed
|
||||
System.Test.startTest();
|
||||
myWork.doWork();
|
||||
System.Test.stopTest();
|
||||
|
||||
// THEN - Then all three work items should be executed
|
||||
System.assert(orderOfExecution.get(0).contains('WorkC'), 'Expected Work C with priority 3 to be executed first');
|
||||
System.assert(orderOfExecution.get(1).contains('WorkB'), 'Expected Work B with priority 2 to be executed second');
|
||||
System.assert(orderOfExecution.get(2).contains('WorkA'), 'Expected Work B with priority 1 to be executed last');
|
||||
}
|
||||
|
||||
private virtual class Work extends fflib_DoWork
|
||||
{
|
||||
public Boolean didWork = false;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>51.0</apiVersion>
|
||||
<apiVersion>53.0</apiVersion>
|
||||
<status>Active</status>
|
||||
</ApexClass>
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>51.0</apiVersion>
|
||||
<apiVersion>53.0</apiVersion>
|
||||
<status>Active</status>
|
||||
</ApexClass>
|
||||
|
||||
Reference in New Issue
Block a user