├── .circleci └── config.yml ├── .forceignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets ├── pmd-ruleset.xml └── server.key.enc ├── config └── project-scratch-def.json ├── force-app └── default │ └── main │ ├── classes │ ├── TriggerHandler.cls │ ├── TriggerHandler.cls-meta.xml │ ├── TriggerHandlerMetadataProvider.cls │ ├── TriggerHandlerMetadataProvider.cls-meta.xml │ ├── TriggerHandlerTest.cls │ └── TriggerHandlerTest.cls-meta.xml │ └── objects │ ├── Trigger_Handler_Method__mdt │ ├── Trigger_Handler_Method__mdt.object-meta.xml │ ├── fields │ │ ├── Description__c.field-meta.xml │ │ ├── Is_Enabled__c.field-meta.xml │ │ ├── Order_of_execution__c.field-meta.xml │ │ ├── Trigger_Event_Type__c.field-meta.xml │ │ └── Trigger_Handler_Name__c.field-meta.xml │ └── validationRules │ │ └── Prevent_TestTriggerHandler_name.validationRule-meta.xml │ └── Trigger_Handler_Settings__mdt │ ├── Trigger_Handler_Settings__mdt.object-meta.xml │ ├── fields │ ├── Description__c.field-meta.xml │ ├── Is_After_Delete_Enabled__c.field-meta.xml │ ├── Is_After_Insert_Enabled__c.field-meta.xml │ ├── Is_After_Undelete_Enabled__c.field-meta.xml │ ├── Is_After_Update_Enabled__c.field-meta.xml │ ├── Is_Before_Delete_Enabled__c.field-meta.xml │ ├── Is_Before_Insert_Enabled__c.field-meta.xml │ ├── Is_Before_Update_Enabled__c.field-meta.xml │ └── Is_Globally_Enabled__c.field-meta.xml │ └── validationRules │ └── Prevent_TestTriggerHandler_record_name.validationRule-meta.xml ├── manifest └── package.xml └── sfdx-project.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | static-analysis: 4 | docker: 5 | - image: circleci/openjdk:latest 6 | environment: 7 | - PMD_URL: https://github.com/pmd/pmd/releases/download/pmd_releases/6.23.0/pmd-bin-6.23.0.zip 8 | steps: 9 | - checkout 10 | - restore_cache: 11 | keys: 12 | - pmd-v6.23.0 13 | - run: 14 | name: Install PMD 15 | command: | 16 | if [ ! -d pmd-bin-6.23.0 ]; then 17 | curl -L $PMD_URL -o pmd-bin-6.23.0.zip 18 | unzip pmd-bin-6.23.0.zip 19 | rm pmd-bin-6.23.0.zip 20 | fi 21 | - save_cache: 22 | key: pmd-v6.23.0 23 | paths: 24 | - pmd-bin-6.23.0 25 | - run: 26 | name: Run Static Analysis 27 | command: | 28 | pmd-bin-6.23.0/bin/run.sh pmd -d . -R assets/pmd-ruleset.xml -f text -l apex -r static-analysis.txt -cache nonexistingfile.cache 29 | - store_artifacts: 30 | path: static-analysis.txt 31 | unit-tests: 32 | docker: 33 | - image: circleci/openjdk:latest 34 | working_directory: ~/ci_app 35 | environment: 36 | - DX_CLI_URL: https://developer.salesforce.com/media/salesforce-cli/sfdx-linux-amd64.tar.xz 37 | steps: 38 | - checkout 39 | - restore_cache: 40 | keys: 41 | - sfdx-cli 42 | - run: 43 | name: Download CLI 44 | command: | 45 | if [ ! -d sfdx ]; then 46 | mkdir sfdx 47 | wget -qO- $DX_CLI_URL | tar xJ -C sfdx --strip-components 1 48 | fi 49 | - save_cache: 50 | key: sfdx-cli 51 | paths: 52 | - sfdx 53 | - run: 54 | name: Install CLI 55 | command: | 56 | ./sfdx/install 57 | sfdx 58 | - run: 59 | name: Create hub key 60 | command: | 61 | echo 'make hub key' 62 | openssl enc -nosalt -aes-256-cbc -d -in assets/server.key.enc -out assets/server.key -base64 -K $DECRYPTION_KEY -iv $DECRYPTION_IV 63 | - run: 64 | name: Create Scratch Org 65 | command: | 66 | sfdx force:auth:jwt:grant --clientid $HUB_CONSUMER_KEY --jwtkeyfile assets/server.key --username $HUB_SFDC_USER --setdefaultdevhubusername -a DevHub 67 | sfdx force:org:create -s -f config/project-scratch-def.json -a circle_build_$CIRCLE_BUILD_NUM --wait 5 68 | - run: 69 | name: Remove Server Key 70 | when: always 71 | command: | 72 | rm assets/server.key 73 | - run: 74 | name: Push Source 75 | command: | 76 | sfdx force:source:push -u circle_build_$CIRCLE_BUILD_NUM 77 | - run: 78 | name: Run Apex Tests 79 | command: | 80 | mkdir ~/tests 81 | mkdir ~/tests/apex 82 | sfdx force:apex:test:run -u circle_build_$CIRCLE_BUILD_NUM -c -r human -d ~/tests/apex -w 9999 83 | - run: 84 | name: Push to Codecov.io 85 | command: | 86 | cp ~/tests/apex/test-result-codecoverage.json . 87 | bash <(curl -s https://codecov.io/bash) 88 | - run: 89 | name: Clean Up 90 | when: always 91 | command: | 92 | sfdx force:org:delete -u circle_build_$CIRCLE_BUILD_NUM -p 93 | rm ~/tests/apex/*.txt ~/tests/apex/test-result-7*.json 94 | - store_artifacts: 95 | path: ~/tests 96 | - store_test_results: 97 | path: ~/tests 98 | workflows: 99 | version: 2 100 | continuous-integration: 101 | jobs: 102 | - static-analysis: 103 | filters: 104 | branches: 105 | ignore: example-of-usage 106 | - unit-tests: 107 | requires: 108 | - static-analysis 109 | filters: 110 | branches: 111 | ignore: example-of-usage 112 | -------------------------------------------------------------------------------- /.forceignore: -------------------------------------------------------------------------------- 1 | # List files or directories below to ignore them when running force:source:push, force:source:pull, and force:source:status 2 | # More information: https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_exclude_source.htm 3 | # 4 | 5 | # LWC configuration files 6 | **/jsconfig.json 7 | **/.eslintrc.json 8 | 9 | # LWC Jest 10 | **/__tests__/** -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.1.0 4 | - Migrated repo from MDAPI format to SFDX 5 | 6 | ## 1.0.0 7 | - First release, updated readme and examples of usage 8 | 9 | ## 0.9.0 10 | - Changed custom interfaces to Callable 11 | 12 | ## 0.7.0 13 | - Added TriggerHandler settings support 14 | 15 | ## 0.5.1 16 | - Updated README and example 17 | 18 | ## 0.5.0 19 | - Main functionality -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 AndreyFilonenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Salesforce Apex trigger framework 2 | 3 | [![CircleCI](https://circleci.com/gh/AndreyFilonenko/sfdc-declarative-trigger-framework.svg?style=svg)](https://circleci.com/gh/AndreyFilonenko/sfdc-declarative-trigger-framework) [![codecov](https://codecov.io/gh/AndreyFilonenko/sfdc-declarative-trigger-framework/branch/master/graph/badge.svg)](https://codecov.io/gh/AndreyFilonenko/sfdc-declarative-trigger-framework) 4 | 5 | 6 | Deploy to Salesforce 8 | 9 | 10 | ## Overview 11 | A simple and minimal framework for Salesforce Apex triggers with declarative trigger handlers management - in accordance with Salesforce development best practices, defines a single entry point for sObject trigger with dispatching handler functions by a specific trigger event. Also gives an ability to manage trigger handlers with the no-code approach, just managing custom metadata definitions via point and clicks. 12 | 13 | 14 | There are three main blocks of functionality: 15 | * Trigger handler backbone with event handlers and dispatcher logic. 16 | * Trigger enablement logic, which allows you to disable the trigger hanler globally or by for a specific event (optionally). 17 | * Trigger handler logic management for determining the handlers and order of their execution for specific event (optionally). 18 | 19 | 20 | ## *TriggerHandler* public API 21 | #### Properties: 22 | * `List newList` - readonly, returns the Trigger.new records. 23 | * `Map newMap` - readonly, returns the Trigger.newMap records. 24 | * `List oldList` - readonly, returns the Trigger.old records. 25 | * `Map oldMap` - readonly, returns the Trigger.oldMap records. 26 | * `Integer size` - readonly, returns the quantity of trigger records. 27 | 28 | #### Exceptions 29 | * `TriggerHandlerException` - will be thrown in the next cases: 30 | 1. Call of the `newList` property on `Before Delete` or `After Delete` trigger events. 31 | 2. Call of the `newMap` property on `Before Insert`, `Before Delete` or `After Delete` trigger events. 32 | 3. Call of the `oldList` property on `Before Insert`, `After Insert` or `After Undelete` trigger events. 33 | 4. Call of the `oldMap` property on `Before Insert`, `After Insert` or `After Undelete` trigger events. 34 | 35 | #### Overridable methods 36 | Also, you can directly define all your logic in your sObject trigger handler just overriding the next methods: 37 | 38 | * `beforeInsert()` 39 | * `beforeUpdate()` 40 | * `beforeDelete()` 41 | * `afterInsert()` 42 | * `afterUpdate()` 43 | * `afterDelete()` 44 | * `afterUndelete()` 45 | 46 | But keep in mind - to prevent blocking of other features your method overrides must include base class method call, also your overriden functionality will be executed after all declarative handler methods ([see here](#Override-example)). 47 | 48 | ## Usage 49 | ### Main functionality 50 | First, create an Apex trigger for sObject: 51 | ```java 52 | trigger AccountTrigger on Account (before insert, 53 | before update, 54 | before delete, 55 | after insert, 56 | after update, 57 | after delete, 58 | after undelete) { 59 | new AccountTriggerHandler( 60 | Trigger.operationType, 61 | Trigger.new, 62 | Trigger.newMap, 63 | Trigger.old, 64 | Trigger.oldMap, 65 | Trigger.size 66 | ).dispatch(); 67 | } 68 | ``` 69 | 70 | Then, extend the base **TriggerHandler** class and create a trigger handler for your sObject: 71 | ```java 72 | public class AccountTriggerHandler extends TriggerHandler { 73 | public AccountTriggerHandler(System.TriggerOperation triggerOperation, 74 | List newList, 75 | Map newMap, 76 | List oldList, 77 | Map oldMap, 78 | Integer size) { 79 | super( 80 | triggerOperation, 81 | newList, 82 | newMap, 83 | oldList, 84 | oldMap, 85 | size 86 | ); 87 | } 88 | } 89 | ``` 90 | 91 | Finally, override the base methods with logic you need: 92 | ###### Override example 93 | ```java 94 | public class AccountTriggerHandler extends TriggerHandler { 95 | ... 96 | 97 | public override void beforeInsert() { 98 | // base method call 99 | super.beforeInsert(); 100 | 101 | // put your overriden logic here... 102 | } 103 | } 104 | ``` 105 | ### Trigger enablement management (optionally) 106 | Create a **Trigger_Handler_Settings__mdt** record to describe the sObject trigger behavior globally or on the specific event: 107 | ![image](https://user-images.githubusercontent.com/23140402/81047299-cf3e7d00-8ec2-11ea-9ba0-1990d573fa88.png) 108 | By default the trigger handler enabled globally with all events. 109 | 110 | ### Trigger handlers management (optionally) 111 | Define the handler with the next pattern (implement the [Callable interface](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_interface_System_Callable.htm)): 112 | ```java 113 | 114 | public class AccountTriggerHandlerMethodBeforeInsert implements Callable { 115 | /** 116 | * See possible params values below: 117 | * @param action - will contain the string name of TriggerOperation enum value for the current context 118 | * @param args - will contain a map of Trigger props with the prop names as keys 119 | * For example, you can retrieve newList by the key 'newList' for BEFORE_INSERT event handler 120 | */ 121 | Object call(String action, Map args) { 122 | // put your logic here... 123 | } 124 | } 125 | ``` 126 | 127 | ... and register it in the Custom metadata by creating the correspondent **Trigger_Handler_Method__mdt** record: 128 | ![image](https://user-images.githubusercontent.com/23140402/80317415-5a0ce100-880c-11ea-9cdb-7f5c4f6a8239.png) 129 | 130 | #### Please see the detailed example of the usage [here!](https://github.com/AndreyFilonenko/sfdc-declarative-trigger-framework/tree/example-of-usage) 131 | 132 | ## Change log 133 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 134 | 135 | ## References 136 | The idea of this framework was based on the main concepts of [TDTM](https://powerofus.force.com/s/article/EDA-TDTM-Overview "TDTM Overview") framework and Kevin O'Hara`s [sfdc-trigger-framework](https://github.com/kevinohara80/sfdc-trigger-framework "sfdc-trigger-framework"). 137 | 138 | ## License 139 | The MIT License (MIT). Please see [License File](LICENSE) for more information. 140 | -------------------------------------------------------------------------------- /assets/pmd-ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | Quickstart configuration of PMD for Salesforce.com Apex. Includes the rules that are most likely to apply everywhere. 7 | 8 | 9 | 10 | 3 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 3 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 3 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 3 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 3 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 3 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 3 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 3 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 3 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 3 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 3 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 3 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 3 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 3 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 3 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 3 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 3 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 3 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 3 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 3 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 3 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 3 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 3 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 3 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 3 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 3 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 282 | 283 | 3 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 3 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 3 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 3 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 3 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 3 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 3 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 353 | 354 | 3 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 3 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 3 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 3 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 3 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 3 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 3 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 3 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 3 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 3 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 3 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 3 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 478 | 479 | 480 | 481 | 3 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | -------------------------------------------------------------------------------- /assets/server.key.enc: -------------------------------------------------------------------------------- 1 | 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 36 | -------------------------------------------------------------------------------- /config/project-scratch-def.json: -------------------------------------------------------------------------------- 1 | { 2 | "orgName": "Demo company", 3 | "edition": "Developer", 4 | "features": [] 5 | } 6 | -------------------------------------------------------------------------------- /force-app/default/main/classes/TriggerHandler.cls: -------------------------------------------------------------------------------- 1 | public virtual class TriggerHandler { 2 | @TestVisible 3 | private System.TriggerOperation triggerOperation; 4 | @TestVisible 5 | private TriggerHandlerMetadataProvider metadataProvider = new TriggerHandlerMetadataProvider(); 6 | private String triggerHandlerName; 7 | private Trigger_Handler_Settings__mdt triggerHandlerSettings; 8 | 9 | protected TriggerHandler(System.TriggerOperation triggerOperation, 10 | List newList, 11 | Map newMap, 12 | List oldList, 13 | Map oldMap, 14 | Integer size) { 15 | this.triggerOperation = triggerOperation; 16 | this.newList = newList; 17 | this.newMap = newMap; 18 | this.oldList = oldList; 19 | this.oldMap = oldMap; 20 | this.size = size; 21 | this.triggerHandlerName = String.valueOf(this).substring(0, String.valueOf(this).indexOf(':')); 22 | } 23 | 24 | @testVisible 25 | protected List newList { 26 | get { 27 | if (triggerOperation == System.TriggerOperation.BEFORE_DELETE || 28 | triggerOperation == System.TriggerOperation.AFTER_DELETE) { 29 | throw new TriggerHandlerException('Trigger.new is not available for current operation: ' + triggerOperation.name()); 30 | } 31 | return this.newList; 32 | } 33 | private set; 34 | } 35 | 36 | @testVisible 37 | protected Map newMap { 38 | get { 39 | if (triggerOperation == System.TriggerOperation.BEFORE_INSERT || 40 | triggerOperation == System.TriggerOperation.BEFORE_DELETE || 41 | triggerOperation == System.TriggerOperation.AFTER_DELETE) { 42 | throw new TriggerHandlerException('Trigger.newMap is not available for current operation: ' + triggerOperation.name()); 43 | } 44 | return this.newMap; 45 | } 46 | private set; 47 | } 48 | 49 | @testVisible 50 | protected List oldList { 51 | get { 52 | if (triggerOperation == System.TriggerOperation.BEFORE_INSERT || 53 | triggerOperation == System.TriggerOperation.AFTER_INSERT || 54 | triggerOperation == System.TriggerOperation.AFTER_UNDELETE) { 55 | throw new TriggerHandlerException('Trigger.old is not available for current operation: ' + triggerOperation.name()); 56 | } 57 | return this.oldList; 58 | } 59 | private set; 60 | } 61 | 62 | @testVisible 63 | protected Map oldMap { 64 | get { 65 | if (triggerOperation == System.TriggerOperation.BEFORE_INSERT || 66 | triggerOperation == System.TriggerOperation.AFTER_INSERT || 67 | triggerOperation == System.TriggerOperation.AFTER_UNDELETE) { 68 | throw new TriggerHandlerException('Trigger.oldMap is not available for current operation: ' + triggerOperation.name()); 69 | } 70 | return this.oldMap; 71 | } 72 | private set; 73 | } 74 | 75 | @testVisible 76 | protected Integer size { protected get; private set; } 77 | 78 | public void dispatch() { 79 | if (isTriggerEnabled()) { 80 | switch on this.triggerOperation { 81 | when BEFORE_INSERT { 82 | this.beforeInsert(); 83 | } 84 | when AFTER_INSERT { 85 | this.afterInsert(); 86 | } 87 | when BEFORE_UPDATE { 88 | this.beforeUpdate(); 89 | } 90 | when AFTER_UPDATE { 91 | this.afterUpdate(); 92 | } 93 | when BEFORE_DELETE { 94 | this.beforeDelete(); 95 | } 96 | when AFTER_DELETE { 97 | this.afterDelete(); 98 | } 99 | when AFTER_UNDELETE { 100 | this.afterUndelete(); 101 | } 102 | } 103 | } 104 | } 105 | 106 | @TestVisible 107 | protected virtual void beforeInsert() { 108 | executeHandlerMethods(this.triggerOperation.name(), this.newList, null); 109 | } 110 | 111 | @TestVisible 112 | protected virtual void afterInsert() { 113 | executeHandlerMethods(this.triggerOperation.name(), this.newList, null); 114 | } 115 | 116 | @TestVisible 117 | protected virtual void beforeUpdate() { 118 | executeHandlerMethods(this.triggerOperation.name(), this.newList, this.oldList); 119 | } 120 | 121 | @TestVisible 122 | protected virtual void afterUpdate() { 123 | executeHandlerMethods(this.triggerOperation.name(), this.newList, this.oldList); 124 | } 125 | 126 | @TestVisible 127 | protected virtual void beforeDelete() { 128 | executeHandlerMethods(this.triggerOperation.name(), null, this.oldList); 129 | } 130 | 131 | @TestVisible 132 | protected virtual void afterDelete() { 133 | executeHandlerMethods(this.triggerOperation.name(), null, this.oldList); 134 | } 135 | 136 | @TestVisible 137 | protected virtual void afterUndelete() { 138 | executeHandlerMethods(this.triggerOperation.name(), this.newList, null); 139 | } 140 | 141 | private Boolean isTriggerEnabled() { 142 | Boolean isTriggerEnabled = true; 143 | List handlerSettings = this.metadataProvider.getHandlerSettings(this.triggerHandlerName); 144 | if (!handlerSettings.isEmpty()) { 145 | isTriggerEnabled = false; 146 | Trigger_Handler_Settings__mdt currentHandlerSettings = handlerSettings.get(0); 147 | if (currentHandlerSettings.Is_Globally_Enabled__c) { 148 | isTriggerEnabled = true; 149 | switch on this.triggerOperation { 150 | when BEFORE_INSERT { 151 | isTriggerEnabled = currentHandlerSettings.Is_Before_Insert_Enabled__c; 152 | } 153 | when AFTER_INSERT { 154 | isTriggerEnabled = currentHandlerSettings.Is_After_Insert_Enabled__c; 155 | } 156 | when BEFORE_UPDATE { 157 | isTriggerEnabled = currentHandlerSettings.Is_Before_Update_Enabled__c; 158 | } 159 | when AFTER_UPDATE { 160 | isTriggerEnabled = currentHandlerSettings.Is_After_Update_Enabled__c; 161 | } 162 | when BEFORE_DELETE { 163 | isTriggerEnabled = currentHandlerSettings.Is_Before_Delete_Enabled__c; 164 | } 165 | when AFTER_DELETE { 166 | isTriggerEnabled = currentHandlerSettings.Is_After_Delete_Enabled__c; 167 | } 168 | when AFTER_UNDELETE { 169 | isTriggerEnabled = currentHandlerSettings.Is_After_Undelete_Enabled__c; 170 | } 171 | } 172 | } 173 | } 174 | 175 | return isTriggerEnabled; 176 | } 177 | 178 | private List getHandlerMethodNames() { 179 | List handlerMethods = this.metadataProvider.getHandlerMethods(this.triggerHandlerName, this.triggerOperation); 180 | List handlerMethodWrappers = new List(); 181 | for (Trigger_Handler_Method__mdt handlerMethod : handlerMethods) { 182 | handlerMethodWrappers.add(new TriggerHandlerMethodWrapper( 183 | handlerMethod.DeveloperName, 184 | handlerMethod.Order_of_execution__c 185 | )); 186 | } 187 | 188 | handlerMethodWrappers.sort(); 189 | 190 | List handlerMethodNames = new List(); 191 | for (Integer i = 0; i < handlerMethodWrappers.size(); i++) { 192 | handlerMethodNames.add(handlerMethodWrappers[i].handlerMethodName); 193 | } 194 | 195 | return handlerMethodNames; 196 | } 197 | 198 | private void executeHandlerMethods(String context, List newList, List oldList) { 199 | List handlerMethodNames = getHandlerMethodNames(); 200 | 201 | if (!handlerMethodNames.isEmpty()) { 202 | for (Integer i = 0; i < handlerMethodNames.size(); i++) { 203 | if (Type.forName(handlerMethodNames[i]) != null) { 204 | Map argsMap = new Map(); 205 | if (newList != null) { 206 | argsMap.put('newList', newList); 207 | } 208 | if (oldList != null) { 209 | argsMap.put('oldList', oldList); 210 | } 211 | 212 | Type handlerMethodType = Type.forName(handlerMethodNames[i]); 213 | Callable currentHandlerMethod = (Callable)handlerMethodType.newInstance(); 214 | currentHandlerMethod.call(context, argsMap); 215 | } 216 | } 217 | } 218 | } 219 | 220 | public class TriggerHandlerException extends Exception {} 221 | 222 | private class TriggerHandlerMethodWrapper implements Comparable { 223 | TriggerHandlerMethodWrapper(String handlerMethodName, Decimal executionOrder) { 224 | this.handlerMethodName = handlerMethodName; 225 | this.executionOrder = Integer.valueOf(executionOrder); 226 | } 227 | 228 | public String handlerMethodName { get; private set; } 229 | public Integer executionOrder { get; private set; } 230 | 231 | public Integer compareTo(Object compareTo) { 232 | TriggerHandlerMethodWrapper compareToMethodWrapper = (TriggerHandlerMethodWrapper)compareTo; 233 | Integer returnValue = 0; 234 | if (executionOrder > compareToMethodWrapper.executionOrder) { 235 | returnValue = 1; 236 | } else if (executionOrder < compareToMethodWrapper.executionOrder) { 237 | returnValue = -1; 238 | } 239 | 240 | return returnValue; 241 | } 242 | } 243 | } -------------------------------------------------------------------------------- /force-app/default/main/classes/TriggerHandler.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 50.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /force-app/default/main/classes/TriggerHandlerMetadataProvider.cls: -------------------------------------------------------------------------------- 1 | public class TriggerHandlerMetadataProvider { 2 | public List getHandlerMethods(String triggerHandlerName, 3 | System.TriggerOperation triggerOperation) { 4 | return [ 5 | SELECT Id, 6 | DeveloperName, 7 | Order_of_execution__c 8 | FROM Trigger_Handler_Method__mdt 9 | WHERE Trigger_Handler_Name__c = :triggerHandlerName 10 | AND Trigger_Event_Type__c = :triggerOperation.name() 11 | AND Is_Enabled__c = true 12 | ]; 13 | } 14 | 15 | public List getHandlerSettings(String triggerHandlerName) { 16 | return [ 17 | SELECT Id, 18 | Is_Globally_Enabled__c, 19 | Is_Before_Insert_Enabled__c, 20 | Is_After_Insert_Enabled__c, 21 | Is_Before_Update_Enabled__c, 22 | Is_After_Update_Enabled__c, 23 | Is_Before_Delete_Enabled__c, 24 | Is_After_Delete_Enabled__c, 25 | Is_After_Undelete_Enabled__c 26 | FROM Trigger_Handler_Settings__mdt 27 | WHERE DeveloperName = :triggerHandlerName 28 | ]; 29 | } 30 | } -------------------------------------------------------------------------------- /force-app/default/main/classes/TriggerHandlerMetadataProvider.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 50.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /force-app/default/main/classes/TriggerHandlerTest.cls: -------------------------------------------------------------------------------- 1 | @isTest 2 | public class TriggerHandlerTest { 3 | private static final String TRIGGER_HANDLER_NAME = 'TestTriggerHandler'; 4 | private static String lastMethodCalled; 5 | private static TriggerHandlerTest.TestTriggerHandler handler = new TriggerHandlerTest.TestTriggerHandler(); 6 | 7 | @isTest 8 | private static void testPropertyNewList() { 9 | List newList; 10 | handler.triggerOperation = System.TriggerOperation.BEFORE_DELETE; 11 | try { 12 | newList = handler.newList; 13 | } catch (TriggerHandler.TriggerHandlerException ex) { 14 | System.assertNotEquals(null, ex.getMessage()); 15 | } 16 | 17 | handler.triggerOperation = System.TriggerOperation.AFTER_DELETE; 18 | try { 19 | newList = handler.newList; 20 | } catch (TriggerHandler.TriggerHandlerException ex) { 21 | System.assertNotEquals(null, ex.getMessage()); 22 | } 23 | 24 | handler.triggerOperation = System.TriggerOperation.BEFORE_INSERT; 25 | newList = handler.newList; 26 | System.assertNotEquals(null, newList); 27 | } 28 | 29 | @isTest 30 | private static void testPropertyNewMap() { 31 | Map newMap; 32 | handler.triggerOperation = System.TriggerOperation.BEFORE_INSERT; 33 | try { 34 | newMap = handler.newMap; 35 | } catch (TriggerHandler.TriggerHandlerException ex) { 36 | System.assertNotEquals(null, ex.getMessage()); 37 | } 38 | 39 | handler.triggerOperation = System.TriggerOperation.BEFORE_DELETE; 40 | try { 41 | newMap = handler.newMap; 42 | } catch (TriggerHandler.TriggerHandlerException ex) { 43 | System.assertNotEquals(null, ex.getMessage()); 44 | } 45 | 46 | handler.triggerOperation = System.TriggerOperation.AFTER_DELETE; 47 | try { 48 | newMap = handler.newMap; 49 | } catch (TriggerHandler.TriggerHandlerException ex) { 50 | System.assertNotEquals(null, ex.getMessage()); 51 | } 52 | 53 | handler.triggerOperation = System.TriggerOperation.BEFORE_UPDATE; 54 | newMap = handler.newMap; 55 | System.assertNotEquals(null, newMap); 56 | } 57 | 58 | @isTest 59 | private static void testPropertyOldList() { 60 | List oldList; 61 | handler.triggerOperation = System.TriggerOperation.BEFORE_INSERT; 62 | try { 63 | oldList = handler.oldList; 64 | } catch (TriggerHandler.TriggerHandlerException ex) { 65 | System.assertNotEquals(null, ex.getMessage()); 66 | } 67 | 68 | handler.triggerOperation = System.TriggerOperation.AFTER_INSERT; 69 | try { 70 | oldList = handler.oldList; 71 | } catch (TriggerHandler.TriggerHandlerException ex) { 72 | System.assertNotEquals(null, ex.getMessage()); 73 | } 74 | 75 | handler.triggerOperation = System.TriggerOperation.AFTER_UNDELETE; 76 | try { 77 | oldList = handler.oldList; 78 | } catch (TriggerHandler.TriggerHandlerException ex) { 79 | System.assertNotEquals(null, ex.getMessage()); 80 | } 81 | 82 | handler.triggerOperation = System.TriggerOperation.BEFORE_UPDATE; 83 | oldList = handler.oldList; 84 | System.assertNotEquals(null, oldList); 85 | } 86 | 87 | @isTest 88 | private static void testPropertyOldMap() { 89 | Map oldMap; 90 | handler.triggerOperation = System.TriggerOperation.BEFORE_INSERT; 91 | try { 92 | oldMap = handler.oldMap; 93 | } catch (TriggerHandler.TriggerHandlerException ex) { 94 | System.assertNotEquals(null, ex.getMessage()); 95 | } 96 | 97 | handler.triggerOperation = System.TriggerOperation.AFTER_INSERT; 98 | try { 99 | oldMap = handler.oldMap; 100 | } catch (TriggerHandler.TriggerHandlerException ex) { 101 | System.assertNotEquals(null, ex.getMessage()); 102 | } 103 | 104 | handler.triggerOperation = System.TriggerOperation.AFTER_UNDELETE; 105 | try { 106 | oldMap = handler.oldMap; 107 | } catch (TriggerHandler.TriggerHandlerException ex) { 108 | System.assertNotEquals(null, ex.getMessage()); 109 | } 110 | 111 | handler.triggerOperation = System.TriggerOperation.BEFORE_UPDATE; 112 | oldMap = handler.oldMap; 113 | System.assertNotEquals(null, oldMap); 114 | } 115 | 116 | @isTest 117 | private static void testPropertySize() { 118 | Integer size = handler.size; 119 | System.assertEquals(10, size); 120 | } 121 | 122 | @isTest 123 | private static void testDispatch() { 124 | handler.triggerOperation = System.TriggerOperation.AFTER_UNDELETE; 125 | 126 | Test.startTest(); 127 | handler.dispatch(); 128 | Test.stopTest(); 129 | 130 | System.assertEquals(null, lastMethodCalled); 131 | } 132 | 133 | @isTest 134 | private static void testBeforeInsert() { 135 | handler.triggerOperation = System.TriggerOperation.BEFORE_INSERT; 136 | handler.metadataProvider = (TriggerHandlerMetadataProvider)Test.createStub( 137 | TriggerHandlerMetadataProvider.class, 138 | new TriggerHandlerMetadataProviderStub() 139 | ); 140 | 141 | Test.startTest(); 142 | handler.dispatch(); 143 | Test.stopTest(); 144 | 145 | System.assertEquals('beforeInsert3', lastMethodCalled); 146 | } 147 | 148 | @isTest 149 | private static void testAfterInsert() { 150 | handler.triggerOperation = System.TriggerOperation.AFTER_INSERT; 151 | handler.metadataProvider = (TriggerHandlerMetadataProvider)Test.createStub( 152 | TriggerHandlerMetadataProvider.class, 153 | new TriggerHandlerMetadataProviderStub() 154 | ); 155 | 156 | Test.startTest(); 157 | handler.dispatch(); 158 | Test.stopTest(); 159 | 160 | System.assertEquals('afterInsert', lastMethodCalled); 161 | } 162 | 163 | @isTest 164 | private static void testBeforeUpdate() { 165 | handler.triggerOperation = System.TriggerOperation.BEFORE_UPDATE; 166 | handler.metadataProvider = (TriggerHandlerMetadataProvider)Test.createStub( 167 | TriggerHandlerMetadataProvider.class, 168 | new TriggerHandlerMetadataProviderStub() 169 | ); 170 | 171 | Test.startTest(); 172 | handler.dispatch(); 173 | Test.stopTest(); 174 | 175 | System.assertEquals('beforeUpdate', lastMethodCalled); 176 | } 177 | 178 | @isTest 179 | private static void testAfterUpdate() { 180 | handler.triggerOperation = System.TriggerOperation.AFTER_UPDATE; 181 | handler.metadataProvider = (TriggerHandlerMetadataProvider)Test.createStub( 182 | TriggerHandlerMetadataProvider.class, 183 | new TriggerHandlerMetadataProviderStub() 184 | ); 185 | 186 | Test.startTest(); 187 | handler.dispatch(); 188 | Test.stopTest(); 189 | 190 | System.assertEquals('afterUpdate', lastMethodCalled); 191 | } 192 | 193 | @isTest 194 | private static void testBeforeDelete() { 195 | handler.triggerOperation = System.TriggerOperation.BEFORE_DELETE; 196 | handler.metadataProvider = (TriggerHandlerMetadataProvider)Test.createStub( 197 | TriggerHandlerMetadataProvider.class, 198 | new TriggerHandlerMetadataProviderStub() 199 | ); 200 | 201 | Test.startTest(); 202 | handler.dispatch(); 203 | Test.stopTest(); 204 | 205 | System.assertEquals('beforeDelete', lastMethodCalled); 206 | } 207 | 208 | @isTest 209 | private static void testAfterDelete() { 210 | handler.triggerOperation = System.TriggerOperation.AFTER_DELETE; 211 | handler.metadataProvider = (TriggerHandlerMetadataProvider)Test.createStub( 212 | TriggerHandlerMetadataProvider.class, 213 | new TriggerHandlerMetadataProviderStub() 214 | ); 215 | 216 | Test.startTest(); 217 | handler.dispatch(); 218 | Test.stopTest(); 219 | 220 | System.assertEquals('afterDelete', lastMethodCalled); 221 | } 222 | 223 | @isTest 224 | private static void testAfterUndelete() { 225 | handler.triggerOperation = System.TriggerOperation.AFTER_UNDELETE; 226 | handler.metadataProvider = (TriggerHandlerMetadataProvider)Test.createStub( 227 | TriggerHandlerMetadataProvider.class, 228 | new TriggerHandlerMetadataProviderStub() 229 | ); 230 | 231 | Test.startTest(); 232 | handler.dispatch(); 233 | Test.stopTest(); 234 | 235 | System.assertEquals('afterUndelete', lastMethodCalled); 236 | } 237 | 238 | private class TestTriggerHandler extends TriggerHandler { 239 | public TestTriggerHandler() { 240 | super( 241 | null, 242 | new List(), 243 | new Map(), 244 | new List(), 245 | new Map(), 246 | 10 247 | ); 248 | } 249 | } 250 | 251 | private class TriggerHandlerMetadataProviderStub implements System.StubProvider { 252 | public Object handleMethodCall(Object stubbedObject, 253 | String stubbedMethodName, 254 | Type returnType, 255 | List listOfParamTypes, 256 | List listOfParamNames, 257 | List listOfArgs) { 258 | Object returnObject; 259 | switch on stubbedMethodName { 260 | when 'getHandlerMethods' { 261 | returnObject = getHandlerMethodsStub((System.TriggerOperation)listOfArgs[1]); 262 | } 263 | when 'getHandlerSettings' { 264 | returnObject = getHandlerSettingsStub(); 265 | } 266 | when else { 267 | returnObject = null; 268 | } 269 | } 270 | return returnObject; 271 | } 272 | 273 | private List getHandlerMethodsStub(System.TriggerOperation triggerOperation) { 274 | List testTriggerHandlerMethods = new List(); 275 | if (triggerOperation == System.TriggerOperation.BEFORE_INSERT) { 276 | Trigger_Handler_Method__mdt testHandlerMethod1 = new Trigger_Handler_Method__mdt(); 277 | testHandlerMethod1.DeveloperName = 'TriggerHandlerTest.' + TRIGGER_HANDLER_NAME + triggerOperation.name() + '2'; 278 | testHandlerMethod1.Is_Enabled__c = true; 279 | testHandlerMethod1.Order_of_execution__c = 2; 280 | testHandlerMethod1.Trigger_Event_Type__c = triggerOperation.name(); 281 | testHandlerMethod1.Trigger_Handler_Name__c = TRIGGER_HANDLER_NAME; 282 | testTriggerHandlerMethods.add(testHandlerMethod1); 283 | 284 | Trigger_Handler_Method__mdt testHandlerMethod2 = new Trigger_Handler_Method__mdt(); 285 | testHandlerMethod2.DeveloperName = 'TriggerHandlerTest.' + TRIGGER_HANDLER_NAME + triggerOperation.name() + '1'; 286 | testHandlerMethod2.Is_Enabled__c = true; 287 | testHandlerMethod2.Order_of_execution__c = 1; 288 | testHandlerMethod2.Trigger_Event_Type__c = triggerOperation.name(); 289 | testHandlerMethod2.Trigger_Handler_Name__c = TRIGGER_HANDLER_NAME; 290 | testTriggerHandlerMethods.add(testHandlerMethod2); 291 | 292 | Trigger_Handler_Method__mdt testHandlerMethod3 = new Trigger_Handler_Method__mdt(); 293 | testHandlerMethod3.DeveloperName = 'TriggerHandlerTest.' + TRIGGER_HANDLER_NAME + triggerOperation.name() + '3'; 294 | testHandlerMethod3.Is_Enabled__c = true; 295 | testHandlerMethod3.Order_of_execution__c = 3; 296 | testHandlerMethod3.Trigger_Event_Type__c = triggerOperation.name(); 297 | testHandlerMethod3.Trigger_Handler_Name__c = TRIGGER_HANDLER_NAME; 298 | testTriggerHandlerMethods.add(testHandlerMethod3); 299 | } else { 300 | Trigger_Handler_Method__mdt testHandlerMethod = new Trigger_Handler_Method__mdt(); 301 | testHandlerMethod.DeveloperName = 'TriggerHandlerTest.' + TRIGGER_HANDLER_NAME + triggerOperation.name(); 302 | testHandlerMethod.Is_Enabled__c = true; 303 | testHandlerMethod.Order_of_execution__c = 1; 304 | testHandlerMethod.Trigger_Event_Type__c = triggerOperation.name(); 305 | testHandlerMethod.Trigger_Handler_Name__c = TRIGGER_HANDLER_NAME; 306 | testTriggerHandlerMethods.add(testHandlerMethod); 307 | } 308 | return testTriggerHandlerMethods; 309 | } 310 | 311 | private List getHandlerSettingsStub() { 312 | return new List { 313 | new Trigger_Handler_Settings__mdt( 314 | DeveloperName = TRIGGER_HANDLER_NAME, 315 | Is_Globally_Enabled__c = true, 316 | Is_Before_Insert_Enabled__c = true, 317 | Is_After_Insert_Enabled__c = true, 318 | Is_Before_Update_Enabled__c = true, 319 | Is_After_Update_Enabled__c = true, 320 | Is_Before_Delete_Enabled__c = true, 321 | Is_After_Delete_Enabled__c = true, 322 | Is_After_Undelete_Enabled__c = true 323 | ) 324 | }; 325 | } 326 | } 327 | 328 | public class TestTriggerHandlerBEFORE_INSERT1 implements Callable { 329 | public Object call(String action, Map args) { 330 | TriggerHandlerTest.lastMethodCalled = 'beforeInsert1'; 331 | return null; 332 | } 333 | } 334 | 335 | public class TestTriggerHandlerBEFORE_INSERT2 implements Callable { 336 | public Object call(String action, Map args) { 337 | TriggerHandlerTest.lastMethodCalled = 'beforeInsert2'; 338 | return null; 339 | } 340 | } 341 | 342 | public class TestTriggerHandlerBEFORE_INSERT3 implements Callable { 343 | public Object call(String action, Map args) { 344 | TriggerHandlerTest.lastMethodCalled = 'beforeInsert3'; 345 | return null; 346 | } 347 | } 348 | 349 | public class TestTriggerHandlerAFTER_INSERT implements Callable { 350 | public Object call(String action, Map args) { 351 | TriggerHandlerTest.lastMethodCalled = 'afterInsert'; 352 | return null; 353 | } 354 | } 355 | 356 | public class TestTriggerHandlerBEFORE_UPDATE implements Callable { 357 | public Object call(String action, Map args) { 358 | TriggerHandlerTest.lastMethodCalled = 'beforeUpdate'; 359 | return null; 360 | } 361 | } 362 | 363 | public class TestTriggerHandlerAFTER_UPDATE implements Callable { 364 | public Object call(String action, Map args) { 365 | TriggerHandlerTest.lastMethodCalled = 'afterUpdate'; 366 | return null; 367 | } 368 | } 369 | 370 | public class TestTriggerHandlerBEFORE_DELETE implements Callable { 371 | public Object call(String action, Map args) { 372 | TriggerHandlerTest.lastMethodCalled = 'beforeDelete'; 373 | return null; 374 | } 375 | } 376 | 377 | public class TestTriggerHandlerAFTER_DELETE implements Callable { 378 | public Object call(String action, Map args) { 379 | TriggerHandlerTest.lastMethodCalled = 'afterDelete'; 380 | return null; 381 | } 382 | } 383 | 384 | public class TestTriggerHandlerAFTER_UNDELETE implements Callable { 385 | public Object call(String action, Map args) { 386 | TriggerHandlerTest.lastMethodCalled = 'afterUndelete'; 387 | return null; 388 | } 389 | } 390 | } -------------------------------------------------------------------------------- /force-app/default/main/classes/TriggerHandlerTest.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 50.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /force-app/default/main/objects/Trigger_Handler_Method__mdt/Trigger_Handler_Method__mdt.object-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Trigger Handler Methods 5 | Public 6 | 7 | -------------------------------------------------------------------------------- /force-app/default/main/objects/Trigger_Handler_Method__mdt/fields/Description__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Description__c 4 | false 5 | SubscriberControlled 6 | 7 | 32768 8 | LongTextArea 9 | 3 10 | 11 | -------------------------------------------------------------------------------- /force-app/default/main/objects/Trigger_Handler_Method__mdt/fields/Is_Enabled__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Is_Enabled__c 4 | false 5 | false 6 | SubscriberControlled 7 | 8 | Checkbox 9 | 10 | -------------------------------------------------------------------------------- /force-app/default/main/objects/Trigger_Handler_Method__mdt/fields/Order_of_execution__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Order_of_execution__c 4 | false 5 | SubscriberControlled 6 | 7 | 18 8 | true 9 | 0 10 | Number 11 | false 12 | 13 | -------------------------------------------------------------------------------- /force-app/default/main/objects/Trigger_Handler_Method__mdt/fields/Trigger_Event_Type__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Trigger_Event_Type__c 4 | false 5 | SubscriberControlled 6 | 7 | true 8 | Picklist 9 | 10 | true 11 | 12 | false 13 | 14 | BEFORE_INSERT 15 | true 16 | 17 | 18 | 19 | AFTER_INSERT 20 | false 21 | 22 | 23 | 24 | BEFORE_UPDATE 25 | false 26 | 27 | 28 | 29 | AFTER_UPDATE 30 | false 31 | 32 | 33 | 34 | BEFORE_DELETE 35 | false 36 | 37 | 38 | 39 | AFTER_DELETE 40 | false 41 | 42 | 43 | 44 | AFTER_UNDELETE 45 | false 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /force-app/default/main/objects/Trigger_Handler_Method__mdt/fields/Trigger_Handler_Name__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Trigger_Handler_Name__c 4 | false 5 | SubscriberControlled 6 | 7 | 255 8 | true 9 | Text 10 | false 11 | 12 | -------------------------------------------------------------------------------- /force-app/default/main/objects/Trigger_Handler_Method__mdt/validationRules/Prevent_TestTriggerHandler_name.validationRule-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Prevent_TestTriggerHandler_name 4 | true 5 | Trigger_Handler_Name__c = 'TestTriggerHandler' 6 | Trigger_Handler_Name__c 7 | The name "TestTriggerHandler" is reserved for testing purposes! 8 | 9 | -------------------------------------------------------------------------------- /force-app/default/main/objects/Trigger_Handler_Settings__mdt/Trigger_Handler_Settings__mdt.object-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Trigger Handler Settings 5 | Public 6 | 7 | -------------------------------------------------------------------------------- /force-app/default/main/objects/Trigger_Handler_Settings__mdt/fields/Description__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Description__c 4 | false 5 | SubscriberControlled 6 | 7 | 32768 8 | LongTextArea 9 | 3 10 | 11 | -------------------------------------------------------------------------------- /force-app/default/main/objects/Trigger_Handler_Settings__mdt/fields/Is_After_Delete_Enabled__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Is_After_Delete_Enabled__c 4 | false 5 | false 6 | SubscriberControlled 7 | 8 | Checkbox 9 | 10 | -------------------------------------------------------------------------------- /force-app/default/main/objects/Trigger_Handler_Settings__mdt/fields/Is_After_Insert_Enabled__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Is_After_Insert_Enabled__c 4 | false 5 | false 6 | SubscriberControlled 7 | 8 | Checkbox 9 | 10 | -------------------------------------------------------------------------------- /force-app/default/main/objects/Trigger_Handler_Settings__mdt/fields/Is_After_Undelete_Enabled__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Is_After_Undelete_Enabled__c 4 | false 5 | false 6 | SubscriberControlled 7 | 8 | Checkbox 9 | 10 | -------------------------------------------------------------------------------- /force-app/default/main/objects/Trigger_Handler_Settings__mdt/fields/Is_After_Update_Enabled__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Is_After_Update_Enabled__c 4 | false 5 | false 6 | SubscriberControlled 7 | 8 | Checkbox 9 | 10 | -------------------------------------------------------------------------------- /force-app/default/main/objects/Trigger_Handler_Settings__mdt/fields/Is_Before_Delete_Enabled__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Is_Before_Delete_Enabled__c 4 | false 5 | false 6 | SubscriberControlled 7 | 8 | Checkbox 9 | 10 | -------------------------------------------------------------------------------- /force-app/default/main/objects/Trigger_Handler_Settings__mdt/fields/Is_Before_Insert_Enabled__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Is_Before_Insert_Enabled__c 4 | false 5 | false 6 | SubscriberControlled 7 | 8 | Checkbox 9 | 10 | -------------------------------------------------------------------------------- /force-app/default/main/objects/Trigger_Handler_Settings__mdt/fields/Is_Before_Update_Enabled__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Is_Before_Update_Enabled__c 4 | false 5 | false 6 | SubscriberControlled 7 | 8 | Checkbox 9 | 10 | -------------------------------------------------------------------------------- /force-app/default/main/objects/Trigger_Handler_Settings__mdt/fields/Is_Globally_Enabled__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Is_Globally_Enabled__c 4 | false 5 | false 6 | SubscriberControlled 7 | 8 | Checkbox 9 | 10 | -------------------------------------------------------------------------------- /force-app/default/main/objects/Trigger_Handler_Settings__mdt/validationRules/Prevent_TestTriggerHandler_record_name.validationRule-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Prevent_TestTriggerHandler_record_name 4 | true 5 | DeveloperName = 'TestTriggerHandler' 6 | DeveloperName 7 | The name "TestTriggerHandler" is reserved for testing purposes! 8 | 9 | -------------------------------------------------------------------------------- /manifest/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | * 5 | ApexClass 6 | 7 | 8 | * 9 | CustomObject 10 | 11 | 50.0 12 | 13 | -------------------------------------------------------------------------------- /sfdx-project.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageDirectories": [ 3 | { 4 | "path": "force-app", 5 | "default": true 6 | } 7 | ], 8 | "namespace": "", 9 | "sfdcLoginUrl": "https://login.salesforce.com", 10 | "sourceApiVersion": "50.0" 11 | } --------------------------------------------------------------------------------