From 5357cd11890eb7b3808d274d73bfe1e0b929694a Mon Sep 17 00:00:00 2001 From: wimvelzeboer Date: Wed, 27 Oct 2021 15:26:35 +0100 Subject: [PATCH] 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 --- README.md | 4 +- .../classes/fflib_AppBindingModule.cls | 4 +- .../fflib_ISObjectTriggerHandler.cls-meta.xml | 2 +- .../fflib_SObjectTriggerHandler.cls-meta.xml | 2 +- .../classes/fflib_TriggerAction.cls | 97 +++++++ .../classes/fflib_TriggerAction.cls-meta.xml | 5 + .../classes/fflib_TriggerActionConfig.cls | 110 ++++++++ .../fflib_TriggerActionConfig.cls-meta.xml | 5 + .../fflib_TriggerActionConfigSelector.cls | 92 ++++++ ...b_TriggerActionConfigSelector.cls-meta.xml | 5 + .../classes/fflib_TriggerContext.cls | 240 ++++++++++++++++ .../classes/fflib_TriggerContext.cls-meta.xml | 5 + .../classes/fflib_TriggerHandler.cls | 155 ++++++++++ .../classes/fflib_TriggerHandler.cls-meta.xml | 5 + .../interfaces/fflib_ITriggerAction.cls | 76 +++++ .../fflib_ITriggerAction.cls-meta.xml | 5 + .../interfaces/fflib_ITriggerActionConfig.cls | 53 ++++ .../fflib_ITriggerActionConfig.cls-meta.xml | 5 + .../fflib_ITriggerActionConfigSelector.cls | 36 +++ ..._ITriggerActionConfigSelector.cls-meta.xml | 5 + .../interfaces/fflib_ITriggerContext.cls | 100 +++++++ .../fflib_ITriggerContext.cls-meta.xml | 5 + .../interfaces/fflib_ITriggerHandler.cls | 55 ++++ .../fflib_ITriggerHandler.cls-meta.xml | 5 + ..._mdt-Trigger Action Layout.layout-meta.xml | 144 ++++++++++ .../fflib_TriggerAction__mdt.object-meta.xml | 6 + .../fields/AfterDelete__c.field-meta.xml | 9 + .../fields/AfterInsert__c.field-meta.xml | 9 + .../fields/AfterUndelete__c.field-meta.xml | 9 + .../fields/AfterUpdate__c.field-meta.xml | 9 + .../fields/BeforeDelete__c.field-meta.xml | 9 + .../fields/BeforeInsert__c.field-meta.xml | 9 + .../fields/BeforeUpdate__c.field-meta.xml | 9 + .../fields/ExecutionContext__c.field-meta.xml | 30 ++ .../ImplementationType__c.field-meta.xml | 11 + .../ObjectTypeAlternate__c.field-meta.xml | 13 + .../fields/ObjectType__c.field-meta.xml | 13 + .../fields/Sequence__c.field-meta.xml | 12 + .../fields/Stateful__c.field-meta.xml | 9 + .../main/unitOfWork/classes/fflib_DoWork.cls | 34 ++- .../unitOfWork/classes/fflib_UnitOfWork.cls | 9 +- .../classes/interfaces/fflib_IDoWork.cls | 5 +- .../apex-extensions/tests/classes/MyTest.cls | 46 +++ .../tests/classes/MyTest.cls-meta.xml | 5 + .../fflib_TriggerHandlerTest.cls | 266 ++++++++++++++++++ .../fflib_TriggerHandlerTest.cls-meta.xml | 5 + .../unitOfWork/fflib_UnitOfWorkTest.cls | 24 +- .../utils/fflib_ArrayUtilsTest.cls-meta.xml | 2 +- .../fflib_OperatorUtilsTest.cls-meta.xml | 2 +- 49 files changed, 1755 insertions(+), 20 deletions(-) create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerAction.cls create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerAction.cls-meta.xml create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerActionConfig.cls create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerActionConfig.cls-meta.xml create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerActionConfigSelector.cls create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerActionConfigSelector.cls-meta.xml create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerContext.cls create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerContext.cls-meta.xml create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerHandler.cls create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerHandler.cls-meta.xml create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerAction.cls create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerAction.cls-meta.xml create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerActionConfig.cls create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerActionConfig.cls-meta.xml create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerActionConfigSelector.cls create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerActionConfigSelector.cls-meta.xml create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerContext.cls create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerContext.cls-meta.xml create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerHandler.cls create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerHandler.cls-meta.xml create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/layouts/fflib_TriggerAction__mdt-Trigger Action Layout.layout-meta.xml create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fflib_TriggerAction__mdt.object-meta.xml create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/AfterDelete__c.field-meta.xml create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/AfterInsert__c.field-meta.xml create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/AfterUndelete__c.field-meta.xml create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/AfterUpdate__c.field-meta.xml create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/BeforeDelete__c.field-meta.xml create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/BeforeInsert__c.field-meta.xml create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/BeforeUpdate__c.field-meta.xml create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/ExecutionContext__c.field-meta.xml create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/ImplementationType__c.field-meta.xml create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/ObjectTypeAlternate__c.field-meta.xml create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/ObjectType__c.field-meta.xml create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/Sequence__c.field-meta.xml create mode 100644 sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/Stateful__c.field-meta.xml create mode 100644 sfdx-source/apex-extensions/tests/classes/MyTest.cls create mode 100644 sfdx-source/apex-extensions/tests/classes/MyTest.cls-meta.xml create mode 100644 sfdx-source/apex-extensions/tests/classes/triggerHandler/fflib_TriggerHandlerTest.cls create mode 100644 sfdx-source/apex-extensions/tests/classes/triggerHandler/fflib_TriggerHandlerTest.cls-meta.xml diff --git a/README.md b/README.md index 4b82487..e6075f8 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ These features are offered in this extension package: - [**Persitant Logger**](https://github.com/wimvelzeboer/fflib-apex-extensions/wiki/Logger)
Ever want to log error messages in a Custom Object? The fflib_Logger will do it for you, even if you have a database-rollback. - [**Extended domain features**](https://github.com/wimvelzeboer/fflib-apex-extensions/wiki/Domain-Layer)
Tons of extra methods in the domains to access the (S)Objects. +- [**TriggerHandler Redesigned**](https://github.com/wimvelzeboer/fflib-apex-extensions/wiki/Trigger-Handler)
A complete redesign of the trigger handler, using the new Force-DI like [application class](https://github.com/wimvelzeboer/fflib-apex-extensions/wiki/application-factories) in combination with the [generic UnitOfWork](https://github.com/wimvelzeboer/fflib-apex-extensions/wiki/UnitOfWork). It brings new advanced features like; enqueueing certain parts of trigger logic, separating trigger logic clearly in a readable structure and the ability to use dependency injection. ## Wiki A full overview of the features in this extension pack and how to use them @@ -50,7 +51,8 @@ Some of the changes that have major impact are listed here; ### October 2021 - **Logger functionality**
A replacement of System.Debug that is using Platform events to store log messages in a Custom Object for you to monitor - **API Upgrade to Winter'22**
All Apex classes are upgraded to API 53.0 -- **UnitOfWork**
Generic UnitOfWork, capable of executing work items in batch at a single instance. Each item will be executed based on its priority and can be queued in a separate execution context when necessary. +- **UnitOfWork**
Generic UnitOfWork, capable of executing work items in batch at a single instance. Each item will be executed based on its priority and can be queued in a separate execution context when necessary. +- **TriggerHandler**
A redesigned trigger handler using custom metadata and the generic UnitOfWork, to dynamicaly call trigger actions. ### August 2021 **Added criteria based on formula; (1 OR 2) AND 3)** diff --git a/sfdx-source/apex-extensions/main/application/dynamic/classes/fflib_AppBindingModule.cls b/sfdx-source/apex-extensions/main/application/dynamic/classes/fflib_AppBindingModule.cls index 7d96af8..ea0fbb8 100644 --- a/sfdx-source/apex-extensions/main/application/dynamic/classes/fflib_AppBindingModule.cls +++ b/sfdx-source/apex-extensions/main/application/dynamic/classes/fflib_AppBindingModule.cls @@ -42,9 +42,7 @@ public virtual class fflib_AppBindingModule public virtual void addBinding(fflib_IAppBinding binding) { - if (null == this.bindings) this.bindings = new List(); - - this.bindings.add(binding); + addBindings(new List{ binding }); } public virtual void addBindings(List bindings) diff --git a/sfdx-source/apex-extensions/main/default/classes/triggerHanders/fflib_ISObjectTriggerHandler.cls-meta.xml b/sfdx-source/apex-extensions/main/default/classes/triggerHanders/fflib_ISObjectTriggerHandler.cls-meta.xml index d75b058..f928c8e 100644 --- a/sfdx-source/apex-extensions/main/default/classes/triggerHanders/fflib_ISObjectTriggerHandler.cls-meta.xml +++ b/sfdx-source/apex-extensions/main/default/classes/triggerHanders/fflib_ISObjectTriggerHandler.cls-meta.xml @@ -1,5 +1,5 @@ - 51.0 + 53.0 Active diff --git a/sfdx-source/apex-extensions/main/default/classes/triggerHanders/fflib_SObjectTriggerHandler.cls-meta.xml b/sfdx-source/apex-extensions/main/default/classes/triggerHanders/fflib_SObjectTriggerHandler.cls-meta.xml index d75b058..f928c8e 100644 --- a/sfdx-source/apex-extensions/main/default/classes/triggerHanders/fflib_SObjectTriggerHandler.cls-meta.xml +++ b/sfdx-source/apex-extensions/main/default/classes/triggerHanders/fflib_SObjectTriggerHandler.cls-meta.xml @@ -1,5 +1,5 @@ - 51.0 + 53.0 Active diff --git a/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerAction.cls b/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerAction.cls new file mode 100644 index 0000000..f24e759 --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerAction.cls @@ -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; + } +} \ No newline at end of file diff --git a/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerAction.cls-meta.xml b/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerAction.cls-meta.xml new file mode 100644 index 0000000..f928c8e --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerAction.cls-meta.xml @@ -0,0 +1,5 @@ + + + 53.0 + Active + diff --git a/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerActionConfig.cls b/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerActionConfig.cls new file mode 100644 index 0000000..541d97b --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerActionConfig.cls @@ -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 {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(); + } +} \ No newline at end of file diff --git a/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerActionConfig.cls-meta.xml b/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerActionConfig.cls-meta.xml new file mode 100644 index 0000000..f928c8e --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerActionConfig.cls-meta.xml @@ -0,0 +1,5 @@ + + + 53.0 + Active + diff --git a/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerActionConfigSelector.cls b/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerActionConfigSelector.cls new file mode 100644 index 0000000..3ae7b3e --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerActionConfigSelector.cls @@ -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 getSObjectFieldList() + { + return new List + { + 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 selectAll() + { + return (List ) Database.query( + + String.format( + 'SELECT {0} FROM {1}', + new List + { + 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() + + */ + ); + } +} \ No newline at end of file diff --git a/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerActionConfigSelector.cls-meta.xml b/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerActionConfigSelector.cls-meta.xml new file mode 100644 index 0000000..f928c8e --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerActionConfigSelector.cls-meta.xml @@ -0,0 +1,5 @@ + + + 53.0 + Active + diff --git a/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerContext.cls b/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerContext.cls new file mode 100644 index 0000000..5062870 --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerContext.cls @@ -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 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 existingRecords + { + get + { + if (existingRecords == null) + { + existingRecords = Trigger.oldMap; + } + return existingRecords; + } + set; + } + + + public fflib_TriggerContext(List 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 getChangedRecords(String fieldName) + { + return getChangedRecords(new Set {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 getChangedRecords(Set fieldNames) + { + List changedRecords = new List(); + 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 getChangedRecords(Schema.SObjectField fieldToken) + { + return getChangedRecords(new Set {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 getChangedRecords(Set fieldTokens) + { + List changedRecords = new List(); + 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 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 objects) + { + return new fflib_TriggerContext((List) objects); + } + } +} \ No newline at end of file diff --git a/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerContext.cls-meta.xml b/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerContext.cls-meta.xml new file mode 100644 index 0000000..f928c8e --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerContext.cls-meta.xml @@ -0,0 +1,5 @@ + + + 53.0 + Active + diff --git a/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerHandler.cls b/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerHandler.cls new file mode 100644 index 0000000..8e6f960 --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerHandler.cls @@ -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 configs = new List(); + + /** + * 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; + } +} \ No newline at end of file diff --git a/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerHandler.cls-meta.xml b/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerHandler.cls-meta.xml new file mode 100644 index 0000000..f928c8e --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/classes/fflib_TriggerHandler.cls-meta.xml @@ -0,0 +1,5 @@ + + + 53.0 + Active + diff --git a/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerAction.cls b/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerAction.cls new file mode 100644 index 0000000..7333f22 --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerAction.cls @@ -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); +} \ No newline at end of file diff --git a/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerAction.cls-meta.xml b/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerAction.cls-meta.xml new file mode 100644 index 0000000..f928c8e --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerAction.cls-meta.xml @@ -0,0 +1,5 @@ + + + 53.0 + Active + diff --git a/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerActionConfig.cls b/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerActionConfig.cls new file mode 100644 index 0000000..34ed98d --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerActionConfig.cls @@ -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(); +} \ No newline at end of file diff --git a/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerActionConfig.cls-meta.xml b/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerActionConfig.cls-meta.xml new file mode 100644 index 0000000..f928c8e --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerActionConfig.cls-meta.xml @@ -0,0 +1,5 @@ + + + 53.0 + Active + diff --git a/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerActionConfigSelector.cls b/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerActionConfigSelector.cls new file mode 100644 index 0000000..fb3b461 --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerActionConfigSelector.cls @@ -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 selectAll(); +} \ No newline at end of file diff --git a/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerActionConfigSelector.cls-meta.xml b/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerActionConfigSelector.cls-meta.xml new file mode 100644 index 0000000..f928c8e --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerActionConfigSelector.cls-meta.xml @@ -0,0 +1,5 @@ + + + 53.0 + Active + diff --git a/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerContext.cls b/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerContext.cls new file mode 100644 index 0000000..29511b8 --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerContext.cls @@ -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 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 getChangedRecords(Set 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 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 getChangedRecords(Set fieldTokens); + + /** + * Provides access to Trigger.oldMap or the mock when configured in a unit-test + **/ + Map getExistingRecords(); + + /** + * @return Returns the System.TriggerOperation enum value + */ + System.TriggerOperation getTriggerOperation(); + + /** + * Provides access to the data represented by this Trigger Context + **/ + List 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(); +} \ No newline at end of file diff --git a/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerContext.cls-meta.xml b/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerContext.cls-meta.xml new file mode 100644 index 0000000..f928c8e --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerContext.cls-meta.xml @@ -0,0 +1,5 @@ + + + 53.0 + Active + diff --git a/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerHandler.cls b/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerHandler.cls new file mode 100644 index 0000000..a8d5cae --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerHandler.cls @@ -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); +} \ No newline at end of file diff --git a/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerHandler.cls-meta.xml b/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerHandler.cls-meta.xml new file mode 100644 index 0000000..f928c8e --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/classes/interfaces/fflib_ITriggerHandler.cls-meta.xml @@ -0,0 +1,5 @@ + + + 53.0 + Active + diff --git a/sfdx-source/apex-extensions/main/triggerHandler/layouts/fflib_TriggerAction__mdt-Trigger Action Layout.layout-meta.xml b/sfdx-source/apex-extensions/main/triggerHandler/layouts/fflib_TriggerAction__mdt-Trigger Action Layout.layout-meta.xml new file mode 100644 index 0000000..1635563 --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/layouts/fflib_TriggerAction__mdt-Trigger Action Layout.layout-meta.xml @@ -0,0 +1,144 @@ + + + + false + false + true + + + + Required + MasterLabel + + + Required + DeveloperName + + + + + Edit + IsProtected + + + Required + NamespacePrefix + + + + + + true + true + true + + + + Edit + ObjectType__c + + + Edit + ObjectTypeAlternate__c + + + Required + ImplementationType__c + + + + + Required + ExecutionContext__c + + + Edit + Sequence__c + + + Edit + Stateful__c + + + + + + true + true + true + + + + Edit + BeforeInsert__c + + + Edit + BeforeUpdate__c + + + Edit + BeforeDelete__c + + + + + Edit + AfterInsert__c + + + Edit + AfterUpdate__c + + + Edit + AfterDelete__c + + + Edit + AfterUndelete__c + + + + + + false + false + true + + + + Readonly + CreatedById + + + + + Readonly + LastModifiedById + + + + + + true + true + false + + + + + + + false + false + false + false + false + + 00h1g000003HGOK + 4 + 0 + Default + + diff --git a/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fflib_TriggerAction__mdt.object-meta.xml b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fflib_TriggerAction__mdt.object-meta.xml new file mode 100644 index 0000000..090e3c5 --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fflib_TriggerAction__mdt.object-meta.xml @@ -0,0 +1,6 @@ + + + + Trigger Actions + Protected + diff --git a/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/AfterDelete__c.field-meta.xml b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/AfterDelete__c.field-meta.xml new file mode 100644 index 0000000..c54bf2a --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/AfterDelete__c.field-meta.xml @@ -0,0 +1,9 @@ + + + AfterDelete__c + false + false + DeveloperControlled + + Checkbox + diff --git a/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/AfterInsert__c.field-meta.xml b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/AfterInsert__c.field-meta.xml new file mode 100644 index 0000000..741c43d --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/AfterInsert__c.field-meta.xml @@ -0,0 +1,9 @@ + + + AfterInsert__c + false + false + DeveloperControlled + + Checkbox + diff --git a/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/AfterUndelete__c.field-meta.xml b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/AfterUndelete__c.field-meta.xml new file mode 100644 index 0000000..e8b30b3 --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/AfterUndelete__c.field-meta.xml @@ -0,0 +1,9 @@ + + + AfterUndelete__c + false + false + DeveloperControlled + + Checkbox + diff --git a/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/AfterUpdate__c.field-meta.xml b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/AfterUpdate__c.field-meta.xml new file mode 100644 index 0000000..b44dc4e --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/AfterUpdate__c.field-meta.xml @@ -0,0 +1,9 @@ + + + AfterUpdate__c + false + false + DeveloperControlled + + Checkbox + diff --git a/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/BeforeDelete__c.field-meta.xml b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/BeforeDelete__c.field-meta.xml new file mode 100644 index 0000000..976797d --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/BeforeDelete__c.field-meta.xml @@ -0,0 +1,9 @@ + + + BeforeDelete__c + false + false + DeveloperControlled + + Checkbox + diff --git a/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/BeforeInsert__c.field-meta.xml b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/BeforeInsert__c.field-meta.xml new file mode 100644 index 0000000..2c4b828 --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/BeforeInsert__c.field-meta.xml @@ -0,0 +1,9 @@ + + + BeforeInsert__c + false + false + DeveloperControlled + + Checkbox + diff --git a/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/BeforeUpdate__c.field-meta.xml b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/BeforeUpdate__c.field-meta.xml new file mode 100644 index 0000000..09429c4 --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/BeforeUpdate__c.field-meta.xml @@ -0,0 +1,9 @@ + + + BeforeUpdate__c + false + false + DeveloperControlled + + Checkbox + diff --git a/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/ExecutionContext__c.field-meta.xml b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/ExecutionContext__c.field-meta.xml new file mode 100644 index 0000000..69d8868 --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/ExecutionContext__c.field-meta.xml @@ -0,0 +1,30 @@ + + + ExecutionContext__c + false + DeveloperControlled + + true + Picklist + + true + + false + + Realtime + true + + + + Queued + false + + + + Dedicated Queued + false + + + + + diff --git a/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/ImplementationType__c.field-meta.xml b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/ImplementationType__c.field-meta.xml new file mode 100644 index 0000000..18a8aad --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/ImplementationType__c.field-meta.xml @@ -0,0 +1,11 @@ + + + ImplementationType__c + false + SubscriberControlled + + 255 + true + Text + false + diff --git a/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/ObjectTypeAlternate__c.field-meta.xml b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/ObjectTypeAlternate__c.field-meta.xml new file mode 100644 index 0000000..a5b3b26 --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/ObjectTypeAlternate__c.field-meta.xml @@ -0,0 +1,13 @@ + + + ObjectTypeAlternate__c + 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. + false + SubscriberControlled + 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. + + 255 + false + Text + false + diff --git a/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/ObjectType__c.field-meta.xml b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/ObjectType__c.field-meta.xml new file mode 100644 index 0000000..fc3f0fb --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/ObjectType__c.field-meta.xml @@ -0,0 +1,13 @@ + + + ObjectType__c + false + DeveloperControlled + + EntityDefinition + TriggerActions + TriggerActions + false + MetadataRelationship + false + diff --git a/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/Sequence__c.field-meta.xml b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/Sequence__c.field-meta.xml new file mode 100644 index 0000000..6946c41 --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/Sequence__c.field-meta.xml @@ -0,0 +1,12 @@ + + + Sequence__c + false + SubscriberControlled + + 18 + false + 0 + Number + false + diff --git a/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/Stateful__c.field-meta.xml b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/Stateful__c.field-meta.xml new file mode 100644 index 0000000..09eb954 --- /dev/null +++ b/sfdx-source/apex-extensions/main/triggerHandler/objects/fflib_TriggerAction__mdt/fields/Stateful__c.field-meta.xml @@ -0,0 +1,9 @@ + + + Stateful__c + false + false + DeveloperControlled + + Checkbox + diff --git a/sfdx-source/apex-extensions/main/unitOfWork/classes/fflib_DoWork.cls b/sfdx-source/apex-extensions/main/unitOfWork/classes/fflib_DoWork.cls index 94530ce..f2dd700 100644 --- a/sfdx-source/apex-extensions/main/unitOfWork/classes/fflib_DoWork.cls +++ b/sfdx-source/apex-extensions/main/unitOfWork/classes/fflib_DoWork.cls @@ -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 } } \ No newline at end of file diff --git a/sfdx-source/apex-extensions/main/unitOfWork/classes/fflib_UnitOfWork.cls b/sfdx-source/apex-extensions/main/unitOfWork/classes/fflib_UnitOfWork.cls index 3ba9f17..ab130d0 100644 --- a/sfdx-source/apex-extensions/main/unitOfWork/classes/fflib_UnitOfWork.cls +++ b/sfdx-source/apex-extensions/main/unitOfWork/classes/fflib_UnitOfWork.cls @@ -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(); } /** diff --git a/sfdx-source/apex-extensions/main/unitOfWork/classes/interfaces/fflib_IDoWork.cls b/sfdx-source/apex-extensions/main/unitOfWork/classes/interfaces/fflib_IDoWork.cls index 6480692..d2801f5 100644 --- a/sfdx-source/apex-extensions/main/unitOfWork/classes/interfaces/fflib_IDoWork.cls +++ b/sfdx-source/apex-extensions/main/unitOfWork/classes/interfaces/fflib_IDoWork.cls @@ -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); } \ No newline at end of file diff --git a/sfdx-source/apex-extensions/tests/classes/MyTest.cls b/sfdx-source/apex-extensions/tests/classes/MyTest.cls new file mode 100644 index 0000000..e973f41 --- /dev/null +++ b/sfdx-source/apex-extensions/tests/classes/MyTest.cls @@ -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' + ); + } + } + +} \ No newline at end of file diff --git a/sfdx-source/apex-extensions/tests/classes/MyTest.cls-meta.xml b/sfdx-source/apex-extensions/tests/classes/MyTest.cls-meta.xml new file mode 100644 index 0000000..f928c8e --- /dev/null +++ b/sfdx-source/apex-extensions/tests/classes/MyTest.cls-meta.xml @@ -0,0 +1,5 @@ + + + 53.0 + Active + diff --git a/sfdx-source/apex-extensions/tests/classes/triggerHandler/fflib_TriggerHandlerTest.cls b/sfdx-source/apex-extensions/tests/classes/triggerHandler/fflib_TriggerHandlerTest.cls new file mode 100644 index 0000000..8908533 --- /dev/null +++ b/sfdx-source/apex-extensions/tests/classes/triggerHandler/fflib_TriggerHandlerTest.cls @@ -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 records = new List {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 {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 oldRecords = new List + { + new Account(Id = idLuke, Name = 'Luke', Description = 'Jedi'), + new Account(Id = idHan, Name = 'Han', Description = 'Pilot'), + new Account(Id = idLeia, Name = 'Leia') + }; + + List 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(oldRecords); + + // WHEN we create a domain with ExistingRecords and request the changed records + List result = ctx.getChangedRecords(Account.Name); + + // THEN it should only return the changed records + Map resultMap = new Map(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 oldRecords = new List + { + new Account(Id = idLuke, Name = 'Luke', Description = 'Jedi'), + new Account(Id = idHan, Name = 'Han', Description = 'Pilot'), + new Account(Id = idLeia, Name = 'Leia') + }; + + List 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(oldRecords); + + // WHEN we create a domain with ExistingRecords and request the changed records + List result = ctx.getChangedRecords('Description'); + + // THEN it should only return the changed records + Map resultMap = new Map(result); + System.assertEquals(1, result.size()); + System.assert(resultMap.containsKey(idLuke)); + } + + @IsTest + private static void itShouldCallTriggerActionOnAfterDelete() + { + List records = new List + { + 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 records = new List + { + 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 records = new List + { + 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 records = new List + { + 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 records = new List + { + 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 records = new List + { + 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 records = new List + { + 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 records = triggerContext.getRecords(); + } + } +} \ No newline at end of file diff --git a/sfdx-source/apex-extensions/tests/classes/triggerHandler/fflib_TriggerHandlerTest.cls-meta.xml b/sfdx-source/apex-extensions/tests/classes/triggerHandler/fflib_TriggerHandlerTest.cls-meta.xml new file mode 100644 index 0000000..f928c8e --- /dev/null +++ b/sfdx-source/apex-extensions/tests/classes/triggerHandler/fflib_TriggerHandlerTest.cls-meta.xml @@ -0,0 +1,5 @@ + + + 53.0 + Active + diff --git a/sfdx-source/apex-extensions/tests/classes/unitOfWork/fflib_UnitOfWorkTest.cls b/sfdx-source/apex-extensions/tests/classes/unitOfWork/fflib_UnitOfWorkTest.cls index 620490e..ee3b865 100644 --- a/sfdx-source/apex-extensions/tests/classes/unitOfWork/fflib_UnitOfWorkTest.cls +++ b/sfdx-source/apex-extensions/tests/classes/unitOfWork/fflib_UnitOfWorkTest.cls @@ -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 {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; diff --git a/sfdx-source/apex-extensions/tests/classes/utils/fflib_ArrayUtilsTest.cls-meta.xml b/sfdx-source/apex-extensions/tests/classes/utils/fflib_ArrayUtilsTest.cls-meta.xml index d75b058..f928c8e 100644 --- a/sfdx-source/apex-extensions/tests/classes/utils/fflib_ArrayUtilsTest.cls-meta.xml +++ b/sfdx-source/apex-extensions/tests/classes/utils/fflib_ArrayUtilsTest.cls-meta.xml @@ -1,5 +1,5 @@ - 51.0 + 53.0 Active diff --git a/sfdx-source/apex-extensions/tests/classes/utils/fflib_OperatorUtilsTest.cls-meta.xml b/sfdx-source/apex-extensions/tests/classes/utils/fflib_OperatorUtilsTest.cls-meta.xml index d75b058..f928c8e 100644 --- a/sfdx-source/apex-extensions/tests/classes/utils/fflib_OperatorUtilsTest.cls-meta.xml +++ b/sfdx-source/apex-extensions/tests/classes/utils/fflib_OperatorUtilsTest.cls-meta.xml @@ -1,5 +1,5 @@ - 51.0 + 53.0 Active