Monday, 29 April 2019

Lightning Web Component



Lightning web Component :
=========================

ex 1 :


<template>
  <lightning-card title="Hello" icon-name="custom:custom14">
    <div class="slds-m-around_medium">
       Hello {greeting} !
    </div>
 
  </lightning-card>

</template>

import { LightningElement } from 'lwc';

export default class Hello extends LightningElement {
     greeting ='world';
}

ex 2 :
=========

<div class ="slds-m-around_medium">
     <p> Hello ,{greeting}
    <lightning-input label="Name" value={greeting} onchange={handleChange}><lightning-input>

</div>

import { LightningElement,track } from 'lwc';

export default class HelloBinding extends LightningElement {
  @track greeting ='world';

 handleChange (event){
   this.greeting = event.target.value;
 }

}

@track :
========

@track is going to notice that the state
changed.

To track a private property's value and
re-render a component when it changes,
decorate the property with @track.
Tracked properties are also called private
reactive properties.

Kabab style :
===============
kebab style is instead of CamelCase or
underscores, we're using hyphen between
the words in the variable.

@api :
=========
To expose a public property,decorate
it with @api.

public properties are reactive.
if the value of reactive property
changes, the component's template
rerenders any content that references
the property.

import {LightningElement,api} from 'lwc';

export default class Hello extends LightningElement
{
  @api greeting = 'world';
}

import :
=========
import allows us to pull in information
from other modules,like functions and
properties and access to them , and then
the export is what our module's going
to give to the world.

@wire :
=======
To read Salesforce data, Lightning web
components use a reactive wire service.
when the wire service provisions data,
the component rerenders.

Components use @wire in their javascript
class to specify a wire adaptor or an
apex method.

ex 3 :

Use Getters Instead of Expressions :
===================================

To compute a value for a property, use a
javascript getter.

<lightning-input name='firstName' label="First Name" onchange={handleChange}></lightning-input>
<lightning-input name='lastName' label='Last Name' onchnage={handleChange}></lightning-input>
<p class="slds-m-top_medium"> Uppercased Full Name : {uppercasedFullName} </p>

import { LightningElement , track } from 'lwc';

export default class HelloExpressions extends LightningElement {
  @track firstName ='';
  @track lastName ='';

 handleChange(event){
    const field = event.target.name;
      if(field == 'firstName'){
           this.firstname = event.target.value;
       } else if(field == 'lastName'){
           this.lastName = event.target.value;
        }
  }

 get uppercaseFullName(){
      return `${this.firstName} ${this.lastName}`.toUpperCase();
  }

}

Note : decorating a property with @track
makes it private and reactive. To mark a
property public and reactive, use @api.


Iteration and Directives :
===========================
To render a list of items, use for each
directive or the iterator directive to
iterate over an array.

ex 4 :

 <template for:each={contacts} for:item="contact">
   <li key={contact.Id}>
        {contact.Name},{contact.Title}
    </li>
 </template>

import { LightningElement } from 'lwc'

export default class HelloForEach extends LightningElement{

   contacts = [
      {
        Id : '5785637567',
        Name : 'test 1',
        Title : 'Enggineering',
      },
      {
        Id : '5785637568',
        Name : 'test 2',
        Title : 'Sales',
      },
      {
        Id : '5785637566',
        Name : 'test 3',
        Title : 'CEO',
      },
   ];
}

Conditional Rendering :
========================

<lightning-input type="checkbox" label="Show details"
onchange={handleChange}></lightning-input>

<div class="slds-m-vertical_medium">
  <template if:true={areDetailsVisible}>
    These are the details !
  </template>
  <template if:false={areDetailsVisible}>
    Not showing details.
  </template>
</div>

imort {LightningElement,track} from 'lwc';

export default class HelloConditionalRendering extends LightningElement {
   @track areDetailsVisible = false;

 handleChange(event){
     this.areDetailsVisible = event.target.checked;
  }

}

Accessing server Data in LWC with Lightning Data service:
=========================================================

1.Lightning-record-edit-form



a.Display a record edit layout for editing
a specified records.
b.Display a record create the layout for
creating a new record.

ex :

<lightning-record-edit-form
object-api-name={objectApiName}
record-id={recordId}>

<lightning-messages></lightning-messages>
<lightning-input-field field-name={nameField}></lightning-input-field>
<lightning-input-field field-name={titleField}></lightning-input-field>
<lightning-input-field field-name ={phoneField}></lightning-input-field>
<lightning-input-field field-name={emailField}></lightning-input-field>
<div class="slds-m-top_medium">
 <lightning-button variant="brand" type="submit" name="save" label="save"></lightning-button>
</div>

</lightning-record-edit-form>

import { LightningElement,api } from 'lwc';

import NAME_FIELD from '@salesforce/schema/Contact.Name';
import TITLE_FIELD from '@salesforce/schema/Contact.Title';
import PHONE_FIELD from '@salesforce/schema/Contact.Phone';
import EMAIL_FIELD from '@salesforce/schema/Contact.Email';

export default class RecordEditFormStaticContact extends LightningElement{
   
    nameField = NAME_FIELD;
    titleField = TITLE_FIELD;
    phoneField = PHONE_FIELD;
    emailField = EMAIL_FIELD;

  @api recordId;
  @api objectApiName;

}

Note : if you try to delete a field,they'll
get an error because they see the dependency
because it's static,and if they rename
the field,Salesforce will rewrite your code.


ex: apex wire method to property

<template if:true={contacts.data}>
 <template for:each={contacts.data} for:item="contact">
   <p key={contact.Id}>{contact.Name}</p>
  </template>
 <template if:true={contact.error}>
   <c-error-panel errors={contacts.error}></c-error-panel>
 </template>

import {LightningElement,wire} from 'lwc';
import getContactList from '@salesforce/apex/ContcatControler.getContactList';

export default class ApexWireMethodToProperty extends LightningElement{
  @wire(getContactList) contacts;
}


ex : apex wire method to function

 import { LightningElement,wire,track } from 'lwc'
 import getContactList from '@salesforce/apex/ContactController.getContactList';

 export default class ApexWireMethodToFunction extends LightningElement {
    @track contacts;
    @trck  error;
   
    @wire(getContactList)
    wiredContacts({ error,data}){
      if(data){
       this.contacts=data;
      } else if (error){
         this.error =error;
       }
   }
 }

 <template if:true={contacts}>
   <template for:each={contacts} for:item="contact">
     <p key={contact.Id}>{contact.Name}>/p>
    </template>
   <template if:true={error}>
    <c-error-panel errors={error}></c-error-panel>
   </template>

ex: Apex imperative Method

  import {LightningElement,track} from 'lwc';
  import getContactList from '@salesforce/apex/ContactController.getContactList';

 export default class ApexImperativeMethod extends LightningElement{
   @track contacts;
   @track error;

   handleLoad(){
     getContactList()
       .then(result => {
          this.contacts= result;
         })
        .ctach( onrejected: error=>{
            this.error = error;
          });
   }

  }

Sunday, 17 March 2019

SOQL used in Apex




workflow rule :
================
1. you can do field updates on the base record and maybe its parent record.
2.Cross object field updates are extremely limited here,and only sometimes available
    if you have a master detail relationship between them.
3. Even then, the type of field updates you can do are limited to simple formulas.


process builder :
===================
1. you can do field updates on the base record and may be its parent record.

2.you can do more cross object updates,but only if the objects are strictly associated with each other via a lookup or master detail relationship field.


SOQL :
=======

1.Salesforce object query language
2. The language to find any record(s) in your Salesforce org.

Note : Capitalization and spacing is ignored by SOQL.

1.cross -object queries
2.grouping Results

Querying parent Data :

select Id,
  Amount,
 StageName,
 Account.Name,
 Account.Industry,
 Account.Website,
 Account.Owner.UserName
 from Opportunity where
 Account.Industry ='Energy'
 AND  Account.AnnualRevenue > 50000
 AND  CreatedBy.Email !=null


select Id,Amount,StageName,
 Sales_Plan__r.Goal__c,
 Sales_Plan__r.Sponsor__c,
 Sales_Plan__r.Qtr__r.Id,
 Account.Industry
 from Opportunity
 where Sales_Plan__r.Target__c>0

Querying Child Data :
=========================

select Id,Amount,StageName,
  Account.Name
 (select Role,ContactId ,
   Contact.FirstName,
   Contact.Email
  from
 OpportunityContactRoles
  where Role !=null AND Role !='Influencer')
 from Opportunity


select Id, Amount,StageName,
 (Select Id, Name,Price__c from Competition__r)
 from Opportunity

grouping SOQL :
===============

select StageName,
       SUM(Amount),
       Max(CloseDate),
       MIN(ExpectedRevenue),
       AVG(Amount),
       COUNT(Amount)
  from Opportunity
  where Amount !=null
group by StageName

understanding soql with apex :
===============================

why we use soql in apex ?

First we use SOQL to access fields that aren't in Trigger.new.
Trigger.new has less fields included in it than you'd expect.

Second, we use SOQL to modify records that aren't in trigger.new.

Note :
Trigger.new only stores, one,records entering our trigger, and two,base fields of those records entering our trigger.
For everyhing else, we need to query the data using SOQL first.

ex :
Access Fields Not in Trigger.new

trigger MatchAccountOwner on Contact (before insert){
  for(Contact myCon : Trigger.new){
      if(myCon.AccountId !=null){
         myCon.OwnerId = myCon.Account.OwnerId; // the outside field is NULL
     }
 }
}

All fields outside of Trigger.new's memory are considered null.

we must use SOQL to query data that doesn't exist in Trigger.new.


why Apex needs SOQL ?

1. Access fields and records that exist outside of Trigger.new.


System.assertEquals() :
=======================
A method used to check if two values are equal.

Commonly seen in test classes of experienced coders.

System.assertEquals(Expected Value,Actual value,Error message);

The message to display if the actual value !=expected value

System.assertEquals() is only used in test classes.

Every test class should have at least one assertion.


Sunday, 24 February 2019

Trigger Design Pattern in Apex


Trigger Design Patterns Without Framework :
=================================

Frameworks improve reliability and maintainability,and make probelms easier to debug.

ex :
1.Follow up on lead account(Process Builder)
a. Insert lead with 'Open not Contacted' with good email
b. Create a follow up task.

2.taskTriggerSetStatus
a.Task created for lead
b.if the lead has a task, set its status to 'Working-Contacted'
or 'Working - harder '.

3.leadTriggerSetFollowup
a.if a lead has a working status
b.Create a follow-up task (reminder).

4.taskTriggerTrackCount
a.Task is created,updated or deleted
b.Update # of tasks on the lead

5.First Owner Worked (Process Builder)
a.Lead status starts with Working and Owner is a user.
b.Set first owner worked field.

Note :
=========
1.controling the trigger order
2.Combining DML operations
3.controlling reentrancy


Dynamic Trigger Handler :
===========================

public interface ITriggerExtension
{

  void HandlerTrigger(TriggerOperation operationType,
              List<SObject> newList,List<SObject> oldList,
              Map<ID,SObject> newMap,Map<ID,SObject> oldMap);
}


public with sharing class dynamicTaskTriggerHandler implements ITriggerExtension{

public void HandleTrigger(TriggerOperation operationType,List<SObject> newList,List<SObject> oldList,
                         Map<ID,SObject> newMap,Map<ID,SObject> oldMap)
 {
   system.debug('Trigger execution was called');
 }

}


trigger taskTrigger on Task (before insert,after insert,before update,after update,before delete,after delete){

simpleLeadUpdater leadUpdater = new simpleLeadUpdater();

// What happens when you change the order of these two triggers handlers?

  if(trigger.operationType == TriggerOperation.AFTER_INSERT)
    {
      taskSetStatus.handleTrigger(trigger.new,leadUpdater);
    }

  if(trigger.operationType == TriggerOperation.AFTER_INSERT ||
     trigger.operationType == TriggerOperation.AFTER_UPDATE ||
     trigger.operationType == TriggerOperation.AFTER_DELETE )
     {
     
       taskTrackCount.handleTrigger (trigger.operationType,trigger.new,trigger.old,leadUpdater);
    }

 List<ITriggerExtension> dynamicTriggers = TriggerExtensionSupport.getTriggerClasses('Task');
  for(ITriggerExtension trig : dynamic Triggers)
   {
      trig.HandleTrigger(trigger.operationType,trigger.new,trigger.old,trigger.newMap,trigger.oldMap);
 
   }

  leadUpdater.updateLeads();
}

public with sharing class TriggerExtensionSupport{

public static List<ITriggerExtension> getTriggerClasses(String objectType)
 {
    List<Dynamic_Trigger_mdt> triggerSettings = [ Select Class_Name__c from Dynamic_Trigger__mdt where
                                                  Object_Type__c = : objectType OrderBy Priority__c ASC];
    List<ITriggerExtension> results = new List<ITriggerExtension>();

    for(Dynamic_Trigger__mdt setting : triggerSettings)
    {
      System.Type thetype = Type.forName(setting.Class_Name__c);
       if(thetype == null)
        thetype = Type.forName('',setting.Class_Name__c); // Try resolving local class
       if(thetype!=null){
           Object theobject = thetype.newInstance();
           if(theobject instanceof ITriggerExtension) results.add ((ITriggerExtension) theobject);
        }
    }

   return results ;
 }

}

public with sharing class taskTrackCount {

public static void handleTrigger (TriggerOperation operationType, List<Task> newList,List<Task> oldList,simpleLeadUpdater leadUpdater)
  {
     Set<ID> leadIds = new Set<ID>();
   
     if(operationType == TriggerOperation.AFTER_UPDATE || operationType == TriggerOperation.AFTER_INSERT)
       {
          for(Task t : newList)
           {
             if(t.WhoId!=null && t.WhoId.getSObjectType()==Schema.Lead.SObjectType)
              leadIds.add(t.WhoId);
           }
       }
 
     if(operationType == TriggerOperation.AFTER_UPDATE || operationType == TriggerOperation.AFTER_DELETE)
         {
            for(Task t : oldList)
             if(t.whoId !=null && t.whoID.getSObjectType() == Schema.Lead.SObjectType)
               leadIds.add(t.whoId);
         }

     List<Lead> leads = [Select ID,Task_Count__c from Lead where ID in : leadIds];
     List<AggregateResult> tasks = [Select Count(ID) items,WhoId from Task where WhoId in : leadIds group by WhoID];
     Map<ID,Interger> taskCounts = new Map<ID,Interger>();
 
     for(AggregateResult ar : tasks)
     {
       taskCounts.put((ID)ar.get('WhoId'),(Interger) ar.get('items));
     }
   
     for(Lead ld : leads)
      {
        if(ld.Task_Count__c != taskCounts.get(ld.Id))
          {
            Lead toUpdate = leadUpdater.getLead(ld.id);
            toUpdate.Task_Count__c = taskCounts.get(ld.id);
          }
      }
   }
}

public with sharing class taskSetStatus {

private static Set<ID> statusUpdated = new Set<ID>();

public static void handleTrigger (List<Task> newTasks,simpleLeadUpdater leadUpdater){

    Set<ID> leadIds = new Set<ID>();

     for(Task t : newTasks)
      {
        if(t.whoId!=null && t.whoId.getSObjectType() == Schema.Lead.SObjectType && !t.Declarative_Created__c)
            leadIds.add(t.whoId);
      }

    List<Lead> leads = [Slect ID,Status from Lead where ID in : leadids];

   for(Lead ld : leads)
    {
       if(statusUpdated.contains(ld.id)) continue; // Skip those already updated

       switch on ld.status
        {
           when 'Open - Not Contacted'
            {
              Lead toUpdate = leadUpdater.getLead(ld.id);
               toUpdate.status = 'Working - Contacted ';
               statusUpdated.add(ld.id);
            }
           when 'Working - Contacted'
            {
              Lead toUpdate = leadUpdater.getLead(ld.id);
               toUpdate.status = 'Working Harder ';
                statusUpdated.add(ld.id);
            }
        }
    }
 }

}

public with sharing class simpleLeadUpdater {
  Map<ID,Lead> leadsToUpdate = new Map<ID, Lead>();

 public Lead getLead(ID leadID)
  {
    Lead targetLead = leadsToUpdate.get(leadID);
     if(targetLead == null)
      {
        targetLead = new Lead (ID = leadID);
         leadsToUpdate.put(leadID,targetLead);
      }
    return tragetLead;
  }

public void updateLeads()
  {
    if(leadsToUpdate.size()>0)
      update leadsToUpdate.values();
  }

}

Test Class :
===============
@istest
public with sharing class TestLeadActions{

@istest
public static void TestInsertSingleLead(){
    Test.startTest();
    InsertLeads(1,'Open-Not Contacted');
    Test.stopTest();
  }

@isTest
public static void TestInsertBulkLead()
 {
   Long startTime = DateTime.now().getTime();
   Long startCPU = Limits.getCpuTime();
   Test.startTest();
   InsertLeads(200,'Open - Not Contacted');
   Test.stopTest();
   system.debug ('Elapsed : ' + (Datetime.Now().getTime() - startTime));
   system.debug ('Elapsed CPU : ' + (Limits.getCpuTime() - startCPU));

 }

public static void InsertLeads(Interger count,String leadstatus)
{
  List<Lead> leadsToInsert = new List<Lead>();
  for(Interger x=0;x<count;x++)
   {
      String xs = string.valueOf(x);
      leadsToInsert.add(new Lead(FirstName = 'f' +xs,LastName = 'l'+xs,
                         status=leadstatus,
                         Company = 'c' + xs,
                         email = 'e' + 'xs' + '@pluralsight.com',
                         Bypass_Declarative__c = 0 ));

   }
   insert leadsToInsert;
}

 Public static void UpdateLeadStatus()
  {
    List<Lead> leads = [ select Id, status from Lead ];
     for(Lead ld : leads)
       ld.status = 'Working - Contacted ';
     Test.startTest();
      update leads ;
    Test.stopTest();

  }
}

Sunday, 3 February 2019

Asynchronous Apex


Asynchronous Apex :
===================
Asynchronous Apex fires on Salesforce's queue and outside the context of triggers, which allows for massive workloads, expensive computations,and parallel processing.

Note : Asynchronous processes run on their own time in their own run context.

Across run contexts : NO guarantees on order of execution

On the Salesforce platform, we will sometimes need Asynchronous logic to bypass governor limits,manipulate certain objects when certain conditions occur,contact web services,or perform
high-volume workloads.

1.future methods
2.queueable jobs
3.scheduled apex
4.batch apex

Note : Queueable jobs offer monitoring, chaining and non-primitive data types.

Async Benefits :
==================
Asynchronous Processing or Async processing allows running of parallel processes.

Using Future methods for Higher Limits or Mixed DML :
========================================

Future method runs in the background.you can call a future method for executing long-running operations,such as callouts to external web services or any operation you'd like to run in its own thread, on its own time.

Future methods can be used for :
a. Taking advantage of higher governor limits.
b. Long running web service callouts
c. Mixed DML operations.

Note : you might often use future methods for mixed DML operations in unit tests.


you can add on the "@future" annotation to any function that uses primitive data types.

ex :

@future
public static void updateUserManager(Set<Id> userIds,Id managerId)
{
  // update all users in specified IDs to have new specified Manager ID
    List<User> userToUpdate = new List<User>();
   for(Id userId : userIds)
     {
       usersToUpdate.add(new User(Id=userId,Manager = managerId));
     }

  Database.SaveResult[] updateResults= Database.update(usersToUpdate);
}


ex :

public without sharing class AccountUtils
{
  public static void sortOwners(List<Account> oldAccounts,List<Account> newAccounts)
   {
      Map<Id,List<Account>> userIdToAccounts = getUserToAccounts(newAccounts);
       // For each user,calculate Account Revenue and decide if
       // performance tiers should be updated
      Map<Id,Decimal> userIdToRevenue = getUserRevenue(userIdToAccounts);
      // calls the future method to make AnnualRevenue updated on the user
      if(userIdToAccounts.keySet.size()>0)
       UserUtils.updateAnnualRevenue(userIdRevenue);
   }

  public static Map<Id,List<Account>> getUserToAccounts(List<Account> accountsByUser)
   {
      Map<Id,List<Account>> userIdToAccounts=new Map<Id,List<Account>>();
     
       for(Account account : accountsByUsers)
         {
             if(account.AnnualRevenue !=null && account.AnnualRevenue !=0)
               {
                 if(userIdToAccounts.containskey(account.ownerId))
                     userIdToAccounts.get(account.Id).add(account);
                     else
                     userIdToAccounts.put(account.OwnerId,new List<Account>{account});
             
                }

         }
     return userIdToAccounts;
   }
 
 public static Map<Id,Decimal> getUserRevenue(Map<Id,List<Account>> userIdToAccounts)
  {
    Map<Id,Decimal> userIdToRevenue = new Map<Id, Decimal>();
     for(Id userId : userIdToAccounts.keySet())
      {
        // Iterate through Accounts to retrieve total
        Decimal totalRevenue =0;
         for(Account account : userIdToAccounts.get(userId))
          {
            totalRevenue += account.AnnualRevenue;
          }
         userIdToRevenue.put(userId,totalRevenue);

      }
     return userIdToRevenue;
  }

}


public without sharing class UserUtils
{

  public static Boolean testingException = false;

  public static final Map<String,Decimal> performanceTiers
    = new Map<String,Decimal>{'Low' => 0,'Medium' => 50000,'High' => 10000 };

  @future
  Public static void updateAnnualRevenue(Map<Id,Decimal> userIdsToAnnualRevenue)
  {
    // Retrieve users based on key values of the map : userIDsToAnnualRevenue
   List<User> users = getUsers(userIDsToAnnualRevenue.keySet());
   // Iterate through Users to determine if the performance tier
   // should be updated
   for(User user : users)
   {
     // Update Annual Revenue
      user.Annual_Revenue__c = userIDsToAnnualRevenue.get(user.Id);
       if(user.Annual_Revenue__c < performanceTiers.get('Medium'))
           user.Performance_Tier__c ='Low'; 
       if(user.Annual_Revenue__c >= performanceTiers.get('Medium')
          && user.Annual_Revenue__c < performanceTiers.get('High'))
           user.Performance_Tier__c='Medium';
       if(user.Annual_Revenue__c >= performanceTiers.get(High))
          user.performance_Tier__c='High';
    }

    update users;
  }

  public static List<User> getUsers(Set<Id> userIds)
  {
     List<User> users=[select Id,Name,Annual_Revenue__c,performance_Tier__c
                        from User where Id in : userIds];
      return users;

   }

}


Note :

Future methods can be quickly and easily built with benefits.

Drawbacks  of Future methods:
=============================
1.we can use only Primitive data types
2.No chaining
3.No monitoring

Testing future methods using Test.startTest() and Test.stopTest().

@isTest
private class AccountTest
{
  @testsetup
   static void setupData()
   {
     // Create multiple sales person users
      List<User> salesUsersToInsert = new List<User>();
     
      Profile adminProfile=[select Id, Name from Profile where Name ='System Administrator' LIMIT 1];
     // Low ,medium and high tier sales users
      User lowTierUser = new User(
           Performance_Tier__c='Low',
           FirstName ='Low',
           LastName ='User',
           Username='lowtieruser@example.com',
           Email ='lowtieruser@example.com',
           CompanyName ='Globomantics',
           Alias ='alias',
           Title ='Title',
           TimeZoneSidkey ='America/Chicago',
           EmailEncodingKey ='UTF-8',
           LanguageLocalKey='en_US',
           LocaleSidKey ='en_US',
           ProfileId=adminProfile.Id);
           
           User mediumTierUser = new User(
           Performance_Tier__c='Medium',
           FirstName ='Medium',
           LastName ='User',
           Username='mediumtieruser@example.com',
           Email ='mediumtieruser@example.com',
           CompanyName ='Globomantics',
           Alias ='alias',
           Title ='Title',
           TimeZoneSidkey ='America/Chicago',
           EmailEncodingKey ='UTF-8',
           LanguageLocalKey='en_US',
           LocaleSidKey ='en_US',
           ProfileId=adminProfile.Id);

           User highTierUser = new User(
           Performance_Tier__c='High',
           FirstName ='High',
           LastName ='User',
           Username='hightieruser@example.com',
           Email ='hightieruser@example.com',
           CompanyName ='Globomantics',
           Alias ='alias',
           Title ='Title',
           TimeZoneSidkey ='America/Chicago',
           EmailEncodingKey ='UTF-8',
           LanguageLocalKey='en_US',
           LocaleSidKey ='en_US',
           ProfileId=adminProfile.Id);

           // Add user records to list of users to insert
           salesUserToInsert.add(lowTierUser);
           salesUserToInsert.add(mediumTierUser);
           salesUserToInsert.add(highTierUser);
         
           insert salesUserToInsert;
       
           // Create test Accounts

            List<Account> accountsToInsert = new List<Account>();
             Account lowAccount = new Account (
                  Name = 'Low Account',
                  AnnualRevenue = 20000,
                  OwnerId= lowTierUser.Id);

             Account mediumAccount = new Account (
                  Name = 'Medium Account',
                  AnnualRevenue = 75000,
                  OwnerId= mediumTierUser.Id);

             Account highAccount = new Account (
                  Name = 'High Account',
                  AnnualRevenue = 100000,
                  OwnerId= highTierUser.Id);
         
             accountsToInsert.add(lowAccount);
             accountsToInsert.add(mediumAccount);
             accountsToInsert.add(highAccount);
           
             insert accountsToInsert;
 
   }

  @isTest
   static void sortOwnersSuccess()
   {
    // Retrieve users
    List<User> users = [ select Id, Performance_Tier__c from User
                          where LastName ='user'];
    // Retrieve Accounts
    List<Account> accounts = [select Id,Name,OwnerId,
                              AnnualRevenue from Account];
 
    // When
     List<Account> accountsToUpdate = new List<Account>();
     Test.startTest();
      Decimal updatedRevenue = UserUtils.performanceTiers.get('High') + 50000;
       Account accountUpdate = new Account(
            Id = accounts[0].Id,
            AnnualRevenue = updatedRevenue);
           
        accountsToUpdate.add(accountUpdate);
        update accountsToUpdate;
     Test.stopStart();

    // Then
   
    List<User> usersAfterUpdate = [ select Id,Performance_Tier__c
                                  from User where LastName = 'User' ];

   Interger numberofHighTierUsers =0;
 
    for ( User user : usersAfterUpdate)
      {
         if(user.Performance_Tier__c == 'High')
            numberofHighTierUsers + =1;
      }
   
    System.assertEquals (2,numberOfHighTierUsers, ' 2 High Tier Users Found After Update');


   }


}

Note : Do not make the mistake of a trigger that calls a Future Method that calls another trigger that calls a Future method and so on.


Monitoring ,Chaining processes with Queueable jobs :
=====================================

1.Beyond the Future Method

2.Chaining , monitoring , and non-primitive data types.

3. One Queueable job can call another queueable job , or future calls.
4. An Id means being able to retrieve data about other Queueable jobs.
5. Sobjects and declared objects can be passed in as input.

Note :
a. Possibility of chained parallel processes
b. Chanined parallelism means higher workloads.


Queueable Jobs :
=================
Queueable Jobs are derived from its interface, and as such : each Queueable class must have an execute method.

ex :

public class QueueableProcess implements Queueable
{
   // Every Queueable job has an 'execute' method
  public void execute (QueueableContext context)
  {
      doProcessing();
  }

}


To enqueue the job using System.enqueueJob.

System.enqueueJob(new QueueableProcess());

As an argument, we would pass an instance  of the queueable class itself as specifying which job to enqueue.

public without sharing class OpportunityUtils
{
  public static Map<Interger, List<sObject>> processProducts (List<Opportunity> oldOpps,List<Opportunity> newOpps)
   {
     // Collect all Opportunities moved to 'Closed Won'
     Map<Id,Opportunity> accountIdToOpportunity = new Map<Id,Opportunity>();
     Map<Id ,Opportunity> leadIdToOportunity = new Map<Id,Opportunity>();
     Map<Id,Opportunity> opportunityIdToOpportunity=new Map<Id,Opportunity>();
 
    for(Interger i=0; i< newOpps.size(); i++)
     {
       Opportunity opportunity = newOpps[i];
       Opportunity oldOpportunity = oldOpps == null ? null : oldOpps[i];
     
        if(oldOpportunity != null)

           if(opportunity.StageName != oldOpportunity.SatgeName && opportunity.SatgeName == 'Closed won')
            {
              accountIdToOpportunity.put(opportunity.AccountId,opportunity);
              leadIdToOpportunity.put(opportunity.Lead__c,opportunity);
              opportunityIdToOpportunity.put(opportunity.Id,opportunity);

            }
     }
 
   if(opportunityIdToOpportunity.keyset().size()==0)
    return new Map<Interger,List<sObject>>();
    // Retrieve object lists
 
    List<Account> accounts = getAccounts(accountIdToOpportunity.keySet());
 
    List<Case> cases = getCases(opportunityIdToOpportunity.keySet());
 
    List<Lead> leads = getLeads(leadIdToOpportunity.keySet());
   
    // Iterate through each object and make appropriate updates
    // For each Account,update its AnnualRevenue with the Amount
    // of the Opportunity record. For each Case, close it. For each
    // Lead,mark as 'Inactive'.

     for(Account account : accounts)
        account.AnnualRevenue = account.AnnualRevenue +
        accountIdToOpportunity.get(account.Id).Amount;

     for(Case caseRecord : cases)
       caseRecord.Status ='Closed';

     for(Lead lead : leads)
           lead.Status='Inactive';

   // Accounts can be updated synchronously
   
     update accounts;
   Map<Interger,List<sObject>> indexToObjects = new Map<Interger,List<SObject>>();
    indexToObjects.put(0,cases);
    indexToObjects.put(1,leads);
 
    Interger startingIndex =0;
    System.enqueueJob(new Worker(indexToObjects,startingIndex));
   
   return indexToObjects;

   }

  public static List<Case> getCases(Set<Id> opportunityIds)
  {
    return [select Id,OppOrtunity__c,Status
             From Case WHERE Opportunity__c in : opportunityIds];

  }
 
  public static List<Account> getAccounts(Set<Id> accountIds)
  {
    return [select Id,Name AnnualRevenue from Account
            where Account where Id in : accountIds];
  }

 public static List<Lead> getLeads(Set<Id> leads)
 {
   return [select Id, Name,Status from Lead
           where Id in : leadIds];
 }

}

public without sharing class Worker implements Queueable
{
  Map<Interger,List<sObject>> indexToObjectUpdates;
  Interger index;

  public Worker(Map<Interger,List<sObject>> indexToObjectUpdates,Interger index)
   {
     this.indexToObjectUpdates = indexToObjectUpdates;
     this.index = index;
   }

  public void execute (QueueableContext context)
   {
     if(!indexToObjectUpdates.containskey(index))
      return ;

    update indexToObjectUpdates.get(index);
   
    Interger nextIndex = index +1 ;
    if(indexToObjectUpdates.containskey(nextIndex))
      System.enqueueJob(new Worker(indexToObjectUpdates, nextIndex));

   }
}


@isTest
private class OpportunityTest
{
  public static final Decimal amount = 1000.00;

  @testsetup
  static void setupData()
  {
   
   Account account =new Account();
   account.Name ='Test Account';
   account.AnnualRevenue = 0;
   insert account;
 
   Lead  lead = new Lead();
    lead.Company ='A Company';
    lead.FirstName='Test';
    lead.LastName='Lead';
    lead.Status ='Working - Contacted';
  insert lead;
 
  Opportunity opportunity =new Opportunity();
   opportunity.AccountId = account.Id;
   opportunity.Amount = amount;
   opportunity.Lead__c = lead.Id;
   opportunity.CloseDate = Date.today();
   opportunity.Name= 'Test Opportunity';
   opportunity.StageName ='Negotiation/Review';
   insert opportunity;

  Case caseOnOpportunity =new Case();
  caseOnOpportunity.Opportunity__c=opportunity.Id;
  caseOnOpportunity.Status ='New';
  insert caseOnOpportunity;

  }

 @isTest
  static void cloasedOpportunityTest()
   {
    // Retrieve Opportunity
    List<Opportunity> opportunities = [select Id,AccountId,
      Amount,Lead__c,CloseDate,StageName from Opportunity];

    System.assertEquals(1,opportunities.size(),'Test Opportunity retrieved');
 
    List<Account> accounts = [select Id,Name,AnnualRevenue from account
                             where AnnualRevenue=0];

    System.assertEquals(1, accounts.size(),'1 Account Retrieved with 0 AnnualRevenue');
   
    List<Case> cases = [ select Id, Status from Case
                        where Status != 'Closed'];
    System.assertEquals (1, cases.size(),'1 non-closed Case found');

    List<Lead> leads =[select Id, Name,Status from Lead where Status !='Inactive'];
    System.assertEquals(1,leads.size().'1 active lead found');

   Test.startTest();
     List<Opportunity> oldOpportunities = opportunities;
     List<Opportunity> newOpportunities = new List<Opportunity>();
      //new opp

      Opportunity newOpportunity = new Opportunity();
      newOpportunity.AccountId = opportunities[0].AccountId;
      newOpportunity.Amount = opportunities[0].Amount;
      newOpportunity.Lead__c= opportunities[0].Lead__c;
      newOpportunity.CloseDate = opportunities[0].CloseDate;
      newOpportunity.Id = opportunities[0].Id;
      newOpportunity.StageName = 'Closed Won';

     newOpportunities.add(newOpportunity);
   
     Map<Interger,List<sObject>> workerMap = new Map<Interger,List<sObject>>();
     workerMap = OpportunityUtils.processProducts(oldOpportunities, newOpportunities);
     System.assertEquals(2,workerMap.keySet().size(),'2 Objects set for update');
   
     System.enqueueJob(new Worker(workerMap,1)); // o is enqueued in the function call
 
   Test.stopTest();

  // Then
   List<Account> accountsAfterUpdate = [ select Id,AnnualRevenue from Account
                                        where AnnualRevenue =: amount];
   System.assertEquals(1,accountsAfterUpdate.size(),'1 Account found with expected AnnualRevenue');

  List<Case> casesAfterUpdate = [select Id,Status from Case where Status ='closed'];
 
  System.assertEquals (1,casesAfterUpdate.size(),'1 Closed Case Found');

  List<Lead> leadAfterUpdate =[select Id,Name,Status From Lead where Status ='Inactive'];

  System.assertEquals(1,leadAfterUpdate.size(),'1 Inactive Lead Found');

   }

}


Notes :

1.Queueable Jobs allow use of chaining,  monitoring,and can take any object as input.

2.Jobs have to be defined as a class, unlike future methods, and take more effort.

3.testing Queueable jobs using Test.startTest() and Test.stopTest();

Running Apex on a Schedule :
==============================

we can schedule Apex at regular times or even singular times to execute and get certain tasks done.

Schedule Apex at regular intervals for periodic tasks.

Pair with batch processing for large scale nightly jobs.

UI includes options for Scheduling,or use corn expressions for tighter time increments.

Cron Expressions :
==================

// Seconds Minutes Hours Day_of_month  Day_of_week Optional_year is the default

 public static String cronExpression ='0 0 13 1 * ? '

// The above expression is an example of running on the

1st of the month, every month, at 1PM UTC , no matter the year.


Scheduled tasks are great for
1.Sending emails
2.Data snapshots
3.Accounting
4.Expensive calculations
5.Installed or third party packages.

Limitation of Scheduled Apex :
===============================
100 Scheduled Apex jobs at one time per org.

ex :

// Schedule the Error Checker

string cronExpression = ' 0 0 1 * * ? ';
System.schedule('Error Checker ', cronExpression, new ErrorCheckerScheduled() );


global class ErrorCheckerScheduled implements Schedulable
{
 
  global void execute(SchedulableContext sc)
  {
     checkForErrors();
  }

  public void checkForErrors()
   {
     // Find any errors created today
   
     List<Error__c> errors = getErrors();

    // If any errors exist , notify relevant users
 
    if(!errors.isEmpty())
       notify(errors);

   }

  Public List<Error__c> getErrors()
  {
     Datetime now = Datetime.now();

     Datetime onDayAgo = Datetime.newInstance(now.year(),now.month(),now.day()-1,now.hour(),now.minute(),now.seconds());

    return [ select Id,Name ,CreatedDate from Error__c
              where CreatedDate >= : oneDayAgo ];

  }

  public void notify(List<Error__c> errors)
  {
    // Find Contacts to notify
    List<Contact> contactsNotify = [ select Id, Name, Error_Contact__c from Contact where Error_Contact__c = true ];
   
    // Collect Contact ID

    Set<String> contactIDs = new Set<String>()
 
    for(Contact contact : contactsToNotify)
        contactIDs.add(contact.Id);

    // Construct error message
 
    String errorsInMessage = '';
    String newLine = '/n';

     for(Error__c error : errors)
       errorsInMessage += error.Message__c + newLine;

    // Notify users

    sendErrorEmail(errorsInMessage,contactIDs);

  }

  public void sendErrorEmail (String message, Set<String> contactIDs)
  {
    List<String> contactsToSend = new List<String>();
    contactsToSend.addAll(contactIDs);

   // Construct email message
 
   Messaging.SingleEmailMessage errorEmail = new Messaging.SingleEmailMessage();
   errorEmail.setToAddresses(contactsToSend);
   errorEmail.setPlainTextBody (message);
   errorEmail.setSubject('Errors found on :' + String.valueOf(Date.today()));
 
  // Add to list of emails to send
 
   List<Messaging.Email> emailsToSend = new List<Messaging.Email>();
    emailsToSend.add(errorEmail);

  // Send the email
  Messaging.sendEmail(emailsToSend);

  }

}


@isTest
public class ErrorCheckerTest
{

 @isTest
 static void sendEmailOnExceptionTest()
  {
 
     // Set UserUtils to reflect testing
     // throwing an exception
        UserUtils.testingException = true;
 
     // Get current user
     Id currentUserId = UserInfo.getUserId();
     List<User> currentUser = [select Id,Name,Annual_Revenue__c from User
                               where Id =: currentUserId ];

    Map<Id,Decimal> currentUserToNewAmount = new Map<Id,Decimal>();
    currentUserToNewAmount.put(currentUserId, currentUser[0].Annual_Revenue__c + 5000);
 
    // When :
   
     Test.startTest();
   
     UserUtils.updateAnnualRevenue(currentuserToNewAmount);

     Test.stopTest();

   // Then

   List<Error__c> errorsAfterFuture.size()>0,'Errors found After Future call');
  }

}


Massive Workloads in batch Apex :
=================================
Batch Apex is for high volume of records: from hundreds of thousands to millions.

Each batch job is broken of into three major areas :

a Start function,
an Execute function , and
a Finish function

Governor Limits and Flex Queue :
=================================
Managing governor limits is always important in salesforce and with batch jobs, a key limitation is that only five batch jobs are allowed per org simultaneously.

Note : keep in mind , we could use a  batch to spring off queueable jobs to minimize time spent running the batch job.

Note : five batch jobs per org, but with the Apex Flex Queue, we can expand this to up to 100 queued batch jobs.

Queued batch jobs will have a Holding status when they are added to the queue, and then when exiting the queue, the  indication is a status of Queued.

ex :

global class WelcomeEmailBatch implements Database.Batchable<sObject>
{
   String query = ' select Id,Name,Email,Welcome_Email__c ' +
                  'FROM Contact ' +
                  'WHERE Welcome_Email__c = null '+
                  'AND Email != null ';

  global Database.QueryLocator start(Database.BatchableContext BC)
  {
 
    return Database.getQueryLocator(query);
  }

  global void execute (Database.BatchableContext BC , List<Contact> contacts)
   {
     sendWelcomeEmails(contacts);
   }
  global void finish (Database.BatchableContext BC)
   {
      // Add error handling here for if anything
      // failed in job
   }
  public static void sendWelcomeEmails(List<Contact> contacts)
   {
      List<Contact> contactsToUpdate = new List<Contact>();
       List<Id> contactIds = new List<Id>();
       for(Contact contact : contacts)
        {
           contactIds.add(contact.Id);
         
           contact.Welcome_Email__c = Datetime.now();
            contactsToUpdate.add(contact); 
        }

      Boolean allorNone = true;
      Messaging.MassEmailMessage massEmail = new Messaging.MassEmailMessage();
      massEmail.setTargetObjectIds(contactIds);
      massEmail.setTemplateID(getWelcomeTemplateId());
      Messaging.sendEmail(new List<Messaging.MassEmailMessage>{massEmail},allOrNone);

      if(!contactsToUpdate.isEmpty())
         update contactsToUpdate;
   }

   public static Id getWelcomeTemplateId()
    {
      return [select Id,DeveloperName FROM EmailTemplate
                where DeveloperName ='Welcome'
                LIMIT 1].Id;
    }
}

@isTest
private class WelcomeEmailBatchTest
{
  @testsetup
   static void setupData()
    {
      // create a contact
       insert new Contact (FirstName ='Test',
         LastName ='Contact',
         Email = 'test@example.com',
         Welcome_Email__c=null);

    }

 @isTest
  static void welcomeEmailBatchTest()
   {
    // Given
    List<Contact> contacts = [ select Id,Name,Welcome_Email__c
                               from Contact where Welcome_Email__c = null];
    // when
    Test.startTest();
     Database.executeBatch(new WelcomeEmailBatch());
    Test.stopTest();
   
    // Then
 
   List<Contact> contactsAfterBatch = [ select Id,Name,Welcome_Email__c
                                        FROM Contact
                                        WHERE Welcome_Email__c!=null];
 
  System.assertEquals(1,contactsAfterBatch.size(),'1 Contact Found With Welcome_Email__c populated');
 
   }

}

Apex Flex Queue :
=================
With Flex Queues, any jobs that are submitted for execution but are not processed immediately
by the system go in holding status and are placed in a separate queue (Apex Flex queue).

Up to 100 batch jobs can be in the holding status . When system resources become available, the system picks up jobs from the Apex flex queue and moves them to the batch job queue. The status
of these moved jobs changes from Holding to Queued.Queued jobs get executed when the system is ready to process new jobs and they will then go to InProgress status.

This would give you an impression that a lot of jobs are in holding status and are stuck. They actually are placed in the Apex flex queue and waiting to be picked up when system resources are available.


Sunday, 6 January 2019

centralized trigger framework in Apex

AccountTrigger.Trigger :
========================

trigger AccountTrigger on Account (before insert, before update, before delete, after insert, after update, after delete, after undelete)
{

    // This is the only line of code that is required.

   TriggerFactory.createTriggerDispatcher(Account.sObjectType);

}


TriggerFactory.cls :
=====================

/**
 This factory creates the correct dispatcher and dispatches the trigger event(s) to the appropriate
event handler(s). The dispatchers are automatically created using the Type API, hence dispatcher
registration is not required for each dispatchers.
*/
public with sharing class TriggerFactory
{
    /**
Creates the appropriate dispatcher and dispatches the trigger event to the dispatcher's event handler method.
@param Schema.sObjectType Object type to process (SObject.sObjectType)
*/
    public static void createTriggerDispatcher(Schema.sObjectType soType)
    {
        ITriggerDispatcher dispatcher = getTriggerDispatcher(soType);
       if (dispatcher == null)
            throw new TriggerException('No Trigger dispatcher registered for Object Type: ' + soType);
        execute(dispatcher);
    }

    /**
Dispatches to the dispatcher's event handlers.
@param ITriggerDispatcher A Trigger dispatcher that dispatches to the appropriate handlers
*/
    private static void execute(ITriggerDispatcher dispatcher)
    {
    TriggerParameters tp = new TriggerParameters(Trigger.old, Trigger.new, Trigger.oldMap, Trigger.newMap,
Trigger.isBefore, Trigger.isAfter, Trigger.isDelete,
Trigger.isInsert, Trigger.isUpdate, Trigger.isUnDelete, Trigger.isExecuting);
        // Handle before trigger events
        if (Trigger.isBefore) {
            dispatcher.bulkBefore();
            if (Trigger.isDelete)
                dispatcher.beforeDelete(tp);
            else if (Trigger.isInsert)
                dispatcher.beforeInsert(tp);
            else if (Trigger.isUpdate)
                dispatcher.beforeUpdate(tp);       
        }
        else // Handle after trigger events
        {
            dispatcher.bulkAfter();
            if (Trigger.isDelete)
                dispatcher.afterDelete(tp);
            else if (Trigger.isInsert)
                dispatcher.afterInsert(tp);
            else if (Trigger.isUpdate)
                dispatcher.afterUpdate(tp);
        }
        dispatcher.andFinally();
    }

    /**
Gets the appropriate dispatcher based on the SObject. It constructs the instance of the dispatcher
dynamically using the Type API. The name of the dispatcher has to follow this format:
<ObjectName>TriggerDispatcher. For e.g. for the Feedback__c object, the dispatcher has to be named
as FeedbackTriggerDispatcher.
  @param Schema.sObjectType Object type to create the dispatcher
@return ITriggerDispatcher A trigger dispatcher  if one exists or null.
   */
    private static ITriggerDispatcher getTriggerDispatcher(Schema.sObjectType soType)
    {
    String originalTypeName = soType.getDescribe().getName();
    String dispatcherTypeName = null;
    if (originalTypeName.toLowerCase().endsWith('__c')) {
    Integer index = originalTypeName.toLowerCase().indexOf('__c');
    dispatcherTypeName = originalTypeName.substring(0, index) + 'TriggerDispatcher';
    }
    else
    dispatcherTypeName = originalTypeName + 'TriggerDispatcher';
Type obType = Type.forName(dispatcherTypeName);
ITriggerDispatcher dispatcher = (obType == null) ? null : (ITriggerDispatcher)obType.newInstance();
    return dispatcher;
    }
}


AccountTriggerDispatcher.cls :
===============================

/**
 This class extends the TriggerDispatcherBase to provide the dispatching functionality for the trigger actions
 on the Account object. The event handlers support allowing and preventing actions for reentrant scenarios.
 This is controlled by the flag isBeforeXxxxx and isAfterXxxxx member variables. These variables need to be set
 to true before invoking the handlers and set to false after the invocation of the handlers. Resetting is MUST
 as otherwise unit tests MAY fail. The actual actions should be placed in the handlers (in a separate class).
*/
public class AccountTriggerDispatcher extends TriggerDispatcherBase
{
/** Stores if before insert handler is processing */
private static Boolean isBeforeInsertProcessing = false;
/** Stores if before update handler is processing */
private static Boolean isBeforeUpdateProcessing = false;
/** Stores if before delete handler is processing */
private static Boolean isBeforeDeleteProcessing = false;
/** Stores if after insert handler is processing */
private static Boolean isAfterInsertProcessing = false;
/** Stores if after update handler is processing */
private static Boolean isAfterUpdateProcessing = false;
/** Stores if after delete handler is processing */
private static Boolean isAfterDeleteProcessing = false;
/** Stores if after undelete handler is processing */
private static Boolean isAfterUnDeleteProcessing = false;

/**
Called by the trigger framework to carry out the actions before the records are inserted. If there is an
existing call running on the same context, the rentrant call will utilize the handler that was created
in the original call.
TriggerParameters Contains the trigger parameters which includes the records that is getting inserted.
*/
public virtual override void beforeInsert(TriggerParameters tp) {
if(!isBeforeInsertProcessing) {
isBeforeInsertProcessing = true;
execute(new AccountBeforeInsertTriggerHandler(), tp, TriggerParameters.TriggerEvent.beforeInsert);
isBeforeInsertProcessing = false;
}
else execute(null, tp, TriggerParameters.TriggerEvent.beforeInsert);
}

/**
Called by the trigger framework to carry out the actions before the records are updated. If there is an
existing call running on the same context, the rentrant call will utilize the handler that was created
in the original call.
TriggerParameters Contains the trigger parameters which includes the records that is getting updated.
*/
public virtual override void beforeUpdate(TriggerParameters tp) {
if(!isBeforeUpdateProcessing) {
isBeforeUpdateProcessing = true;
execute(new AccountBeforeUpdateTriggerHandler(), tp, TriggerParameters.TriggerEvent.beforeUpdate);
isBeforeUpdateProcessing = false;
}
else execute(null, tp, TriggerParameters.TriggerEvent.beforeUpdate);
}

        public virtual override void beforeDelete(TriggerParameters tp) {
if(!isBeforeDeleteProcessing) {
isBeforeDeleteProcessing = true;
execute(new AccountBeforeDeleteTriggerHandler(), tp, TriggerParameters.TriggerEvent.beforeDelete);
isBeforeDeleteProcessing = false;
}
else execute(null, tp, TriggerParameters.TriggerEvent.beforeDelete);
}

/**
Called by the trigger framework to carry out the actions after the record(s) are created. If there is an
existing call running on the same context, the rentrant call will utilize the handler that was created
in the original call.
TriggerParameters Contains the trigger parameters which includes the record(s) that got created.
*/
public virtual override void afterInsert(TriggerParameters tp) {
if(!isAfterInsertProcessing) {
isAfterInsertProcessing = true;
execute(new AccountAfterInsertTriggerHandler(), tp, TriggerParameters.TriggerEvent.afterInsert);
isAfterInsertProcessing = false;
}
else execute(null, tp, TriggerParameters.TriggerEvent.afterInsert);
}

/**

Called by the trigger framework to carry out the actions after the records are updated. If there is an
existing call running on the same context, the rentrant call will utilize the handler that was created
in the original call.
TriggerParameters Contains the trigger parameters which includes the record(s) that got updated.
*/
public virtual override void afterUpdate(TriggerParameters tp) {
if(!isAfterUpdateProcessing) {
isAfterUpdateProcessing = true;
execute(new AccountAfterUpdateTriggerHandler(), tp, TriggerParameters.TriggerEvent.afterUpdate);
isAfterUpdateProcessing = false;
}
else execute(null, tp, TriggerParameters.TriggerEvent.afterUpdate);
}

       public virtual override void afterDelete(TriggerParameters tp) {
if(!isAfterDeleteProcessing) {
isAfterDeleteProcessing = true;
execute(new AccountAfterDeleteTriggerHandler(), tp, TriggerParameters.TriggerEvent.afterDelete);
isAfterDeleteProcessing = false;
}
else execute(null, tp, TriggerParameters.TriggerEvent.afterDelete);
}


     public virtual override void afterUnDelete(TriggerParameters tp) {
if(!isAfterUnDeleteProcessing) {
isAfterUnDeleteProcessing = true;
execute(new AccountAfterUnDeleteTriggerHandler(), tp, TriggerParameters.TriggerEvent.afterUndelete);
isAfterUnDeleteProcessing = false;
}
else execute(null, tp, TriggerParameters.TriggerEvent.afterUndelete);
}
}


TriggerDispatcherBase.cls :
==============================
/**
 This class implements the ITriggerDispatcher and acts as an adapter to avoid implementing all the
 ITriggerDispatcher methods.
 @return Feedback__c The newly created feedback object.
*/
public virtual class TriggerDispatcherBase implements ITriggerDispatcher {

private static ITriggerHandler beforeInserthandler;
private static ITriggerHandler beforeUpdatehandler;
private static ITriggerHandler beforeDeleteHandler;
private static ITriggerHandler afterInserthandler;
private static ITriggerHandler afterUpdatehandler;
private static ITriggerHandler afterDeleteHandler;
private static ITriggerHandler afterUndeleteHandler;


    /**
This method is called prior to execution of a before trigger event. If you want
to load any lookup data or cache the data, then this is the place that you need
to put your code.
*/
    public virtual void bulkBefore() {}

    /**
This method is called prior to execution of an after trigger event.
*/
    public virtual void bulkAfter() {}

    /**
This method is called for records to be inserted during a BEFORE trigger.
*/
    public virtual void beforeInsert(TriggerParameters tp) {}

    /**
This method is called for records to be updated during a BEFORE trigger.
*/
    public virtual void beforeUpdate(TriggerParameters tp) {}

    /**
This method is called for records to be deleted during a BEFORE trigger.
*/
    public virtual void beforeDelete(TriggerParameters tp) {}

    /**
This method is called for records inserted during an AFTER trigger. Always put field validation
in the 'After' methods in case another trigger has modified any values. The record is 'read only'
at this point.
*/
    public virtual void afterInsert(TriggerParameters tp) {}

    /**
This method is called iteratively for each record updated during an AFTER trigger.
*/
    public virtual void afterUpdate(TriggerParameters tp) {}

    /**
This method is called iteratively for each record deleted during an AFTER trigger.
*/
    public virtual void afterDelete(TriggerParameters tp) {}
   
    /**
This method is called prior to execution of a AFTER UNDELETE trigger.
*/
    public virtual void afterUnDelete(TriggerParameters tp) {}
   
    public virtual void andFinally() {}
   
    /**
Called by the event handlers. If this is the first call in the context, then this method will create a new
instance of the appropriate handler and execute the mainEntry method. If there is an existing call running
on the same context, then this method will use the existing handler instance created by the original call
and execute the inProgressEntry method.
ITriggerHandler The trigger handler instance. The dispatcher need to pass an instance of the trigger handler, such
  as AccountAfterInsertTriggerHandler if this is the first call in a given context. If it is retry,
  then the dispatcher will need to pass null.
TriggerParameters The trigger parameters passed by the framework.
TriggerParameters.TriggerEvent The trigger event.
*/
    protected void execute(ITriggerHandler handlerInstance, TriggerParameters tp, TriggerParameters.TriggerEvent tEvent) {
    if(handlerInstance != null) {
    if(tEvent == TriggerParameters.TriggerEvent.beforeInsert)
    beforeInsertHandler = handlerInstance;
    if(tEvent == TriggerParameters.TriggerEvent.beforeUpdate)
    beforeUpdateHandler = handlerInstance;
    if(tEvent == TriggerParameters.TriggerEvent.beforeDelete)
    beforeDeleteHandler = handlerInstance;
    if(tEvent == TriggerParameters.TriggerEvent.afterInsert)
    afterInsertHandler = handlerInstance;
    if(tEvent == TriggerParameters.TriggerEvent.afterUpdate)
    afterUpdateHandler = handlerInstance;
    if(tEvent == TriggerParameters.TriggerEvent.afterDelete)
    afterDeleteHandler = handlerInstance;
    if(tEvent == TriggerParameters.TriggerEvent.afterUnDelete)
    afterUndeleteHandler = handlerInstance;
    handlerInstance.mainEntry(tp);
    handlerInstance.updateObjects();
    }
    else {
    if(tEvent == TriggerParameters.TriggerEvent.beforeInsert)
    beforeInsertHandler.inProgressEntry(tp);
    if(tEvent == TriggerParameters.TriggerEvent.beforeUpdate)
    beforeUpdateHandler.inProgressEntry(tp);
    if(tEvent == TriggerParameters.TriggerEvent.beforeDelete)
    beforeDeleteHandler.inProgressEntry(tp);
    if(tEvent == TriggerParameters.TriggerEvent.afterInsert)
    afterInsertHandler.inProgressEntry(tp);
    if(tEvent == TriggerParameters.TriggerEvent.afterUpdate)
    afterUpdateHandler.inProgressEntry(tp);
    if(tEvent == TriggerParameters.TriggerEvent.afterDelete)
    afterDeleteHandler.inProgressEntry(tp);
    if(tEvent == TriggerParameters.TriggerEvent.afterUnDelete)
    afterUndeleteHandler.inProgressEntry(tp);
    }
    }
}

ITriggerDispatcher.cls :
==========================

/**
 Defines the interface for the trigger dispatching architecture.
*/
public interface ITriggerDispatcher {
/**
Called by the trigger framework to carry out the actions before the bulk operations.
*/
void bulkBefore();

/**
Called by the trigger framework to carry out the actions after the bulk operations.
*/
void bulkAfter();

/**
Called by the trigger framework to carry out the actions after completing the bulk operations.
*/
void andFinally();

/**
Called by the trigger framework to carry out the actions before the records are inserted.
TriggerParameters Contains the trigger parameters which includes the records that is getting inserted.
*/
void beforeInsert(TriggerParameters tp);

/**
Called by the trigger framework to carry out the actions before the records are updated.
TriggerParameters Contains the trigger parameters which includes the records that is getting updated.
*/
void beforeUpdate(TriggerParameters tp);

/**
Called by the trigger framework to carry out the actions before the records are deleted.
TriggerParameters Contains the trigger parameters which includes the records that is getting deleted.
*/
void beforeDelete(TriggerParameters tp);

/**
Called by the trigger framework to carry out the actions after the records are inserted.
TriggerParameters Contains the trigger parameters which includes the records that got inserted.
*/
void afterInsert(TriggerParameters tp);

/**
Called by the trigger framework to carry out the actions before the records are updated.
TriggerParameters Contains the trigger parameters which includes the records that got updated.
*/
void afterUpdate(TriggerParameters tp);

/**
Called by the trigger framework to carry out the actions after the records got deleted.
TriggerParameters Contains the trigger parameters which includes the records that got deleted.
*/
void afterDelete(TriggerParameters tp);

/**
Called by the trigger framework to carry out the actions after the records are undeleted.
TriggerParameters Contains the trigger parameters which includes the records that got undeleted.
*/
void afterUnDelete(TriggerParameters tp);
}


ITriggerHandler.cls :
========================

/**
 Defines the interface for trigger handlers. Logic for the first time events are placed under the mainEntry
 method and the logic for the subsequent events raised on the same transaction (reentrant) are placed under
 the inProgressEntry method.
*/
public interface ITriggerHandler {
/**
Called for the first time in the execution context.
TriggerParameters The trigger parameters such as the list of records before and after the update.
*/
void mainEntry(TriggerParameters tp);

/**
Called for the subsequent times in the same execution context.
TriggerParameters The trigger parameters such as the list of records before and after the update.
*/
void inProgressEntry(TriggerParameters tp);

/**
Updates the objects, if any.
*/
void updateObjects();
}

TriggerHandlerBase.cls :
============================
/**
  This class implements the ITriggerHandler to provide abstract/virtual methods for the interface methods
  and so that the trigger handlers need to implement only the method that they have to. The only exception
  is the mainEntry, which is mandatory for the trigger handlers to implement.
*/
public abstract class TriggerHandlerBase implements ITriggerHandler {
protected Map<Id, SObject> sObjectsToUpdate = new Map<Id, SObject>();

/**
Called for the first time in the execution context. The trigger handlers need to implement this method.
TriggerParameters The trigger parameters such as the list of records before and after the update.
*/
public abstract void mainEntry(TriggerParameters tp);

/**
Called for the subsequent times in the same execution context. The trigger handlers can chose to ignore if they don't need the reentrant feature.
TriggerParameters The trigger parameters such as the list of records before and after the update.
*/
public virtual void inProgressEntry(TriggerParameters tp) {

}

/**
Updates the objects, if any.
*/
public virtual void updateObjects() {
if(sObjectsToUpdate.size() > 0)
update sObjectsToUpdate.values();
}
}


UtilityClass.cls :
=======================
/**
 This class has helper methods.
*/

public with sharing class UtilityClass {

/**
Gets the type name of the SObject.
@param SObject The SObject for which the name to be obtained.
@return String - The type name.
*/
public static String getSObjectTypeName(SObject so) {
return so.getSObjectType().getDescribe().getName();
}
}

AccountHelper.cls :
=====================

public with sharing class AccountHelper {

   public static map<Id,SObject> updateWebsite(list<Account> listNewAccounts){
map<Id,SObject> resultMap = new map<Id,SObject>();
for(Account acct : listNewAccounts) {
Account newAccount = new Account();
newAccount.Id = acct.Id;
newAccount.Website = 'www.salesforce.com';
resultMap.put(newAccount.Id, newAccount);
}
return resultMap;
}

}


TriggerParameters.cls :
=======================
/**
 This class holds the trigger parameters.
*/
public class TriggerParameters {

/**
A enum that represents the trigger event.
*/
public Enum TriggerEvent { beforeInsert, beforeUpdate, beforeDelete, afterInsert, afterUpdate, afterDelete, afterUndelete }
public TriggerEvent tEvent;

public List<SObject> oldList { get; private set; }
public List<SObject> newList { get; private set; }
public Map<Id, SObject> oldMap { get; private set; }
public Map<Id, SObject> newMap { get; private set; }
public String triggerObject { get; private set; }
public Boolean isExecuting { get; private set; }

/**
Constructs the TriggerParameter object.
@param List<SObject> A list of records with the state of 'before' event.
@param List<SObject> A list of records with the state of 'after' event.
@param Map<Id, SObject> A map of records with the state of 'before' event.
@param Map<Id, SObject> A map of records with the state of 'after' event.
@param Boolean A flag to indicate 'isBefore' event.
@param Boolean A flag to indicate 'isAfter' event.
@param Boolean A flag to indicate 'isDelete' event.
@param Boolean A flag to indicate 'isInsert' event.
@param Boolean A flag to indicate 'isUpdate' event.
@param Boolean A flag to indicate 'isUnDelete' event.
@param Boolean A flag to indicate 'isExecuting'.
*/
public TriggerParameters(List<SObject> olist, List<SObject> nlist, Map<Id, SObject> omap, Map<Id, SObject> nmap,
Boolean ib, Boolean ia, Boolean id, Boolean ii, Boolean iu, Boolean iud, Boolean ie) {
this.oldList = olist;
this.newList = nlist;
this.oldMap = omap;
this.newMap = nmap;
this.triggerObject = UtilityClass.getSObjectTypeName((this.oldList != null && this.oldList.size() > 0) ? this.oldList[0] : this.newList[0]);
if (ib & ii) tEvent = TriggerEvent.beforeInsert;
else if (ib && iu) tEvent = TriggerEvent.beforeUpdate;
else if (ib && id) tEvent = TriggerEvent.beforeDelete;
else if (ia && ii) tEvent = TriggerEvent.afterInsert;
else if (ia && iu) tEvent = TriggerEvent.afterUpdate;
else if (ia && id) tEvent = TriggerEvent.afterDelete;
else if (ia && iud) tEvent = TriggerEvent.afterUndelete;
isExecuting = ie;
}
}

AccountBeforeInsertTriggerHandler.cls :
========================================

public class AccountBeforeInsertTriggerHandler extends TriggerHandlerBase
 {
public override void mainEntry(TriggerParameters tp)
        {

}
}

AccountBeforeUpdateTriggerHandler.cls :
=======================================

public class AccountBeforeUpdateTriggerHandler extends TriggerHandlerBase
{
public override void mainEntry(TriggerParameters tp)
        {

}
}
AccountAfterInsertTriggerHandler.cls :
========================================
public class AccountAfterInsertTriggerHandler extends TriggerHandlerBase
{
public override void mainEntry(TriggerParameters tp)
         {

}
}

AccountAfterUpdateTriggerHandler.cls :
=======================================

public class AccountAfterUpdateTriggerHandler extends TriggerHandlerBase
{

public override void mainEntry(TriggerParameters tp) {
process((List<Account>)tp.newList);
}

private void process(List<Account> listNewAccounts) {
sObjectsToUpdate.putAll(AccountHelper.updateWebsite(listNewAccounts));
}

public override void inProgressEntry(TriggerParameters tp) {
System.debug('This is an example for reentrant code...');
}

public override void updateObjects() {
// for demonstration purposes, don't do anything here...
}
}

TriggerException.cls :
======================

public class TriggerException extends Exception {

}