├── src ├── staticresources │ ├── Apx1Predeployment.resource │ └── Apx1Predeployment.resource-meta.xml ├── objects │ ├── User.object │ ├── Contact.object │ ├── Solution.object │ ├── Opportunity.object │ ├── AppConfig__c.object │ ├── DebugInfo__c.object │ └── AsyncRequest__c.object ├── triggers │ ├── OnOpportunity1.trigger │ ├── OnContact1.trigger │ ├── OnAccount1.trigger-meta.xml │ ├── OnContact1.trigger-meta.xml │ ├── OnOpportunity1.trigger-meta.xml │ ├── OnOpportunity2.trigger-meta.xml │ ├── OnOpportunity3.trigger-meta.xml │ ├── OnAsyncRequestInsert.trigger-meta.xml │ ├── SolutionTrigger1.trigger-meta.xml │ ├── SolutionTrigger2.trigger-meta.xml │ ├── OnAccount1.trigger │ ├── OnAsyncRequestInsert.trigger │ ├── SolutionTrigger1.trigger │ ├── SolutionTrigger2.trigger │ ├── OnOpportunity3.trigger │ └── OnOpportunity2.trigger ├── classes │ ├── Benchmarking.cls-meta.xml │ ├── Concurrency1.cls-meta.xml │ ├── GoingAsync1.cls-meta.xml │ ├── GoingAsync2.cls-meta.xml │ ├── GoingAsync3.cls-meta.xml │ ├── GoingAsync4.cls-meta.xml │ ├── GoingAsync5.cls-meta.xml │ ├── TestGoingAsync.cls-meta.xml │ ├── ThinkingInApex.cls-meta.xml │ ├── AppConfigSupport.cls-meta.xml │ ├── BulkPatternBatch.cls-meta.xml │ ├── DiagnosticsMain.cls-meta.xml │ ├── FunWithCollections.cls-meta.xml │ ├── SimulatedTranslator.cls-meta.xml │ ├── TestBulkPatterns.cls-meta.xml │ ├── TestDiagnostics1.cls-meta.xml │ ├── TestDiagnostics2.cls-meta.xml │ ├── TestForPackages.cls-meta.xml │ ├── TestHeapAndSOQL.cls-meta.xml │ ├── TestPersonAccount.cls-meta.xml │ ├── TestThinkingInApex.cls-meta.xml │ ├── TestTriggersExample.cls-meta.xml │ ├── TriggersExample.cls-meta.xml │ ├── DiagnosticsTriggers1.cls-meta.xml │ ├── DiagnosticsTriggers2.cls-meta.xml │ ├── PersonAccountSupport.cls-meta.xml │ ├── ScheduledDispatcher2.cls-meta.xml │ ├── SomeFutureOperations.cls-meta.xml │ ├── TestThinkingInApexLimits.cls-meta.xml │ ├── TriggerArchitectureMain1.cls-meta.xml │ ├── DiagnosticsInstrumentation.cls-meta.xml │ ├── ThinkingInApexBulkPatterns.cls-meta.xml │ ├── TriggerArchitectureClass1.cls-meta.xml │ ├── TriggerArchitectureClass2.cls-meta.xml │ ├── SomeFutureOperations.cls │ ├── ScheduledDispatcher2.cls │ ├── TestThinkingInApex.cls │ ├── BulkPatternBatch.cls │ ├── AppConfigSupport.cls │ ├── TestGoingAsync.cls │ ├── TestTriggersExample.cls │ ├── GoingAsync3.cls │ ├── GoingAsync2.cls │ ├── TriggersExample.cls │ ├── GoingAsync5.cls │ ├── TestPersonAccount.cls │ ├── TriggerArchitectureClass1.cls │ ├── TestHeapAndSOQL.cls │ ├── DiagnosticsTriggers2.cls │ ├── DiagnosticsInstrumentation.cls │ ├── DiagnosticsMain.cls │ ├── SimulatedTranslator.cls │ ├── Benchmarking.cls │ ├── TestForPackages.cls │ ├── TriggerArchitectureClass2.cls │ ├── TriggerArchitectureMain1.cls │ ├── PersonAccountSupport.cls │ ├── TestThinkingInApexLimits.cls │ ├── Concurrency1.cls │ ├── ThinkingInApex.cls │ ├── GoingAsync1.cls │ ├── TestDiagnostics1.cls │ ├── GoingAsync4.cls │ ├── TestBulkPatterns.cls │ ├── FunWithCollections.cls │ ├── DiagnosticsTriggers1.cls │ └── TestDiagnostics2.cls └── package.xml ├── RetrieveCurrentValue.cls ├── ControllingProgramFlow.cls ├── LeadRoundRobinQueueFor18Digit.tgr ├── Benchmarking └── Benchmarking.cls ├── RHX_Subscription.tgr ├── RHX_Customer_Feedback.tgr ├── RHX_Product_User.tgr ├── RHX_Property_to_Opportunity.tgr ├── SomeFutureOperations.cls ├── PropertyToOpportunityRH.tgr ├── CacheCurrentValue.cls ├── RHXZuoraSubscriptionProductCharge.tgr ├── CurrentLog.cls ├── SpringCMAccount.tgr ├── SpringCMOpportunity.tgr ├── LeadTrigger.tgr ├── ContactTrigger.tgr ├── ThinkInApex.cls ├── HandleAfterInsert_LeadConversion.tgr ├── activityCreatesOpp (1).tgr ├── SubscriptionProductUserLeadConvert.tgr ├── UpdateCommunityUserRoles.tgr ├── CommunityUserBeforeTriggerClonned.tgr ├── CommunityUserBeforeTriggerCreateLead (1).tgr ├── CommunityUserBeforeTriggerCreateLead (2).tgr └── README.md /src/staticresources/Apx1Predeployment.resource: -------------------------------------------------------------------------------- 1 | disable:1 2 | disable:2 3 | Opportunity:TrackingNumber__c=somevalue 4 | -------------------------------------------------------------------------------- /src/objects/User.object: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /RetrieveCurrentValue.cls: -------------------------------------------------------------------------------- 1 | User u = [SELECT UserIsSpecial__c FROM 2 | User WHERE ID = :UserInfo.getUserId()]; 3 | Boolean UserIsSpecial = u.UserIsSpecial__c; 4 | -------------------------------------------------------------------------------- /ControllingProgramFlow.cls: -------------------------------------------------------------------------------- 1 | /** Enforce that every opportunity is created with an OpportunityContactRole 2 | **/ 3 | 4 | trigger OnOpportunity on Opportunity (before insert) { 5 | Think 6 | -------------------------------------------------------------------------------- /src/triggers/OnOpportunity1.trigger: -------------------------------------------------------------------------------- 1 | trigger OnOpportunity1 on Opportunity (after insert) { 2 | /* 3 | ThinkingInApex.AfterInsertOpportunity( 4 | trigger.new, trigger.newMap); 5 | */ 6 | 7 | } -------------------------------------------------------------------------------- /src/triggers/OnContact1.trigger: -------------------------------------------------------------------------------- 1 | trigger OnContact1 on Contact (before insert, before update) { 2 | PersonAccountSupport.processContactTrigger1( 3 | trigger.isBefore, trigger.new, trigger.oldMap); 4 | } -------------------------------------------------------------------------------- /src/classes/Benchmarking.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/Concurrency1.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/GoingAsync1.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/GoingAsync2.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/GoingAsync3.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/GoingAsync4.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/GoingAsync5.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/TestGoingAsync.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/ThinkingInApex.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/AppConfigSupport.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 31.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/BulkPatternBatch.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/DiagnosticsMain.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/FunWithCollections.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/SimulatedTranslator.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/TestBulkPatterns.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/TestDiagnostics1.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/TestDiagnostics2.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/TestForPackages.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/TestHeapAndSOQL.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 31.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/TestPersonAccount.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/TestThinkingInApex.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/TestTriggersExample.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/TriggersExample.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/triggers/OnAccount1.trigger-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/triggers/OnContact1.trigger-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/DiagnosticsTriggers1.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/DiagnosticsTriggers2.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/PersonAccountSupport.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/ScheduledDispatcher2.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/SomeFutureOperations.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/TestThinkingInApexLimits.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/TriggerArchitectureMain1.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/triggers/OnOpportunity1.trigger-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/triggers/OnOpportunity2.trigger-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/triggers/OnOpportunity3.trigger-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 31.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/DiagnosticsInstrumentation.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/ThinkingInApexBulkPatterns.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 31.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/TriggerArchitectureClass1.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/TriggerArchitectureClass2.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/triggers/OnAsyncRequestInsert.trigger-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 31.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/triggers/SolutionTrigger1.trigger-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/triggers/SolutionTrigger2.trigger-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 31.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/triggers/OnAccount1.trigger: -------------------------------------------------------------------------------- 1 | trigger OnAccount1 on Account 2 | (after insert, after update, 3 | before insert, before update) { 4 | 5 | PersonAccountSupport.processAccountTrigger1( 6 | trigger.isBefore, trigger.new, trigger.oldMap); 7 | 8 | } -------------------------------------------------------------------------------- /src/triggers/OnAsyncRequestInsert.trigger: -------------------------------------------------------------------------------- 1 | trigger OnAsyncRequestInsert on AsyncRequest__c (after insert) { 2 | if(Limits.getLimitQueueableJobs() - Limits.getQueueableJobs() > 0) 3 | try 4 | { 5 | GoingAsync4.enqueueGoingAsync4(null); 6 | } catch(Exception ex) 7 | { 8 | // Ignore for now 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /src/staticresources/Apx1Predeployment.resource-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Private 4 | text/plain 5 | Predeployment resource 6 | 7 | -------------------------------------------------------------------------------- /LeadRoundRobinQueueFor18Digit.tgr: -------------------------------------------------------------------------------- 1 | trigger LeadRoundRobinQueueFor18Digit on LeadRoundRobinQueue__c (before insert, before update) 2 | { 3 | 4 | Id sID; 5 | 6 | for(LeadRoundRobinQueue__c lrr : trigger.new) { 7 | sID = lrr.Standard_Queue_Id__c; 8 | lrr.Standard_Queue_Id__c = string.valueOf(sID); 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /src/triggers/SolutionTrigger1.trigger: -------------------------------------------------------------------------------- 1 | trigger SolutionTrigger1 on Solution (after insert, after update) { 2 | 3 | /* 4 | GoingAsync1.handleTrigger1(trigger.new, trigger.newMap, 5 | trigger.oldMap, trigger.isInsert); 6 | */ 7 | 8 | /* 9 | GoingAsync1.handleTrigger5(trigger.new, trigger.newMap, 10 | trigger.oldMap, trigger.isInsert); 11 | */ 12 | } -------------------------------------------------------------------------------- /Benchmarking/Benchmarking.cls: -------------------------------------------------------------------------------- 1 | @isTest 2 | private class Benchmarking { 3 | 4 | @istest 5 | public static void TestNewAllocate() 6 | { 7 | for(Integer x = 0; x < 10000; x++) 8 | ReturnNewMap(); 9 | } 10 | 11 | private static Map ReturnNewMap() 12 | { 13 | Map result = new Map(); 14 | return result; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/triggers/SolutionTrigger2.trigger: -------------------------------------------------------------------------------- 1 | trigger SolutionTrigger2 on Solution (before insert, before update) { 2 | 3 | /* 4 | GoingAsync1.handleTrigger2(trigger.new, 5 | trigger.oldMap, trigger.isInsert); 6 | */ 7 | /* 8 | GoingAsync1.handleTrigger3(trigger.new, 9 | trigger.oldMap, trigger.isInsert); 10 | */ 11 | 12 | 13 | GoingAsync1.handleTrigger4(trigger.new, 14 | trigger.oldMap, trigger.isInsert); 15 | 16 | 17 | 18 | } -------------------------------------------------------------------------------- /src/triggers/OnOpportunity3.trigger: -------------------------------------------------------------------------------- 1 | trigger OnOpportunity3 on Opportunity (after delete, after insert, 2 | after undelete, after update, before delete, 3 | before insert, before update) { 4 | 5 | DiagnosticsMain.mainEntry('Opportunity', trigger.isBefore, 6 | trigger.isDelete, trigger.isAfter, trigger.isInsert, 7 | trigger.isUpdate, trigger.isExecuting, 8 | trigger.new, trigger.newMap, trigger.old, trigger.oldMap); 9 | 10 | 11 | } -------------------------------------------------------------------------------- /RHX_Subscription.tgr: -------------------------------------------------------------------------------- 1 | trigger RHX_Subscription on Subscription__c (after delete, after insert, after undelete, after update, before delete) { 2 | 3 | Type rollClass = System.Type.forName('rh2', 'ParentUtil'); 4 | 5 | if(rollClass != null) { 6 | rh2.ParentUtil pu = (rh2.ParentUtil) rollClass.newInstance(); 7 | if (trigger.isAfter) { 8 | pu.performTriggerRollups(trigger.oldMap, trigger.newMap, new String[]{'Subscription__c'}, null); 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /RHX_Customer_Feedback.tgr: -------------------------------------------------------------------------------- 1 | trigger RHX_Customer_Feedback on Customer_Feedback__c 2 | (after delete, after insert, after undelete, after update, before delete) { 3 | Type rollClass = System.Type.forName('rh2', 'ParentUtil'); 4 | if(rollClass != null) { 5 | rh2.ParentUtil pu = (rh2.ParentUtil) rollClass.newInstance(); 6 | if (trigger.isAfter) { 7 | pu.performTriggerRollups(trigger.oldMap, trigger.newMap, new String[]{'Customer_Feedback__c'}, null); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /RHX_Product_User.tgr: -------------------------------------------------------------------------------- 1 | trigger RHX_Product_User on Product_User__c (after delete, after insert, after undelete, after update, before delete) { 2 | 3 | Type rollClass = System.Type.forName('rh2', 'ParentUtil'); 4 | 5 | if(rollClass != null) { 6 | rh2.ParentUtil pu = (rh2.ParentUtil) rollClass.newInstance(); 7 | if (trigger.isAfter) { 8 | pu.performTriggerRollups(trigger.oldMap, trigger.newMap, new String[]{'Product_User__c'}, null); 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /RHX_Property_to_Opportunity.tgr: -------------------------------------------------------------------------------- 1 | trigger RHX_Property_to_Opportunity on Property_to_Opportunity__c 2 | (after delete, after insert, after undelete, after update, before delete) { 3 | Type rollClass = System.Type.forName('rh2', 'ParentUtil'); 4 | if(rollClass != null) { 5 | rh2.ParentUtil pu = (rh2.ParentUtil) rollClass.newInstance(); 6 | if (trigger.isAfter) { 7 | pu.performTriggerRollups(trigger.oldMap, trigger.newMap, new String[]{'Property_to_Opportunity__c'}, null); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /SomeFutureOperations.cls: -------------------------------------------------------------------------------- 1 | // indicates that the call has already been made 2 | public class SomeFutureOperations { 3 | private static Boolean FutureCallCalled = false; 4 | public static void DoFutureCall() 5 | { 6 | if(FutureCallCalled || 7 | System.isFuture()) return; 8 | FutureCallCalled = true; 9 | ActualFutureCall(); 10 | } 11 | 12 | @future 13 | private static void ActualFutureCall() 14 | { 15 | // Actual async code here 16 | system.debug('ActualFutureCall async operation'); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /PropertyToOpportunityRH.tgr: -------------------------------------------------------------------------------- 1 | trigger PropertyToOpportunityRH on Property_to_Opportunity__c (after delete, after insert, after undelete, after update, before delete) { 2 | 3 | Type rollClass = System.Type.forName('rh2', 'ParentUtil'); 4 | 5 | if(rollClass != null) { 6 | rh2.ParentUtil pu = (rh2.ParentUtil) rollClass.newInstance(); 7 | if (trigger.isAfter) { 8 | pu.performTriggerRollups(trigger.oldMap, trigger.newMap, new String[]{'Property_to_Opportunity__c'}, null); 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/classes/SomeFutureOperations.cls: -------------------------------------------------------------------------------- 1 | public class SomeFutureOperations { 2 | 3 | private static Boolean futureCallCalled = false; 4 | 5 | public static void doFutureCall() 6 | { 7 | if(futureCallCalled || System.isFuture()) return; 8 | futureCallCalled = true; 9 | actualFutureCall(); 10 | // Code to call async here 11 | } 12 | 13 | @future 14 | private static void actualFutureCall() 15 | { 16 | // Actual async code here 17 | system.debug('actualFutureCall async operation'); 18 | } 19 | 20 | 21 | 22 | } -------------------------------------------------------------------------------- /CacheCurrentValue.cls: -------------------------------------------------------------------------------- 1 | public class ThinkingInApex { 2 | 3 | private static Boolean IsUserSpecialChecked = false; 4 | 5 | private static Boolean UserIsSpecial = false; 6 | 7 | public static Boolean IsUserSpecial() 8 | { 9 | if(IsUserSpecialChecked) return UserIsSpecial; 10 | 11 | User u = [SELECT UserIsSpecial__c from User where ID 12 | = :UserInfo.getUserId()]; 13 | UserIsSpecial = u.UserIsSpecial__c; 14 | IsUserSpecialChecked = true; 15 | return UserIsSpecial; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /RHXZuoraSubscriptionProductCharge.tgr: -------------------------------------------------------------------------------- 1 | trigger RHXZuoraSubscriptionProductCharge on Zuora__SubscriptionProductCharge__c (after delete, after insert, after undelete, after update, before delete) { 2 | 3 | Type rollClass = System.Type.forName('rh2', 'ParentUtil'); 4 | 5 | if(rollClass != null) { 6 | rh2.ParentUtil pu = (rh2.ParentUtil) rollClass.newInstance(); 7 | if (trigger.isAfter) { 8 | pu.performTriggerRollups(trigger.oldMap, trigger.newMap, new String[]{'Zuora__SubscriptionProductCharge__c'}, null); 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/classes/ScheduledDispatcher2.cls: -------------------------------------------------------------------------------- 1 | global class ScheduledDispatcher2 Implements Schedulable { 2 | 3 | public Interface IScheduleDispatched 4 | { 5 | void execute(SchedulableContext sc); 6 | } 7 | 8 | global void execute(SchedulableContext sc) 9 | { 10 | Type targetType = Type.forName('GoingAsync5'); 11 | if(targetType!=null) { 12 | IScheduleDispatched obj = 13 | (IScheduleDispatched)targettype.newInstance(); 14 | obj.execute(sc); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /CurrentLog.cls: -------------------------------------------------------------------------------- 1 | /** 2 | Function to display the current diagnostic log 3 | **/ 4 | 5 | public static String CurrentLog() 6 | { 7 | if(DiagnosticLog == null) return null; 8 | String spaces = ' '; 9 | String result = ''; 10 | for(DiagnosticEntry de: DiagnosticLog) 11 | { 12 | Integer endindex = 3 * de.level; 13 | if(endindex >= spaces.length()) 14 | endindex = spaces.length()-1; 15 | result += spaces.substring(0,endindex) + 16 | de.description + '\n'; 17 | } 18 | return result; 19 | } 20 | 21 | -------------------------------------------------------------------------------- /SpringCMAccount.tgr: -------------------------------------------------------------------------------- 1 | trigger SpringCMAccount on Account (after insert) { 2 | Map rtMap = Schema.SObjectType.Account.getRecordTypeInfosById(); 3 | Map acc = new Map(); 4 | 5 | for(Account a : Trigger.new) { 6 | String recordType = null; 7 | if(rtMap.size() > 1) 8 | recordType = rtMap.get((ID)a.get('RecordTypeId')).getName(); 9 | acc.put(a.Id, recordType); 10 | } 11 | SpringCMRestWrap.BuildEOS(acc, 'Account', UserInfo.getSessionId(), 'CreateSubFolderStructure_CSV'); 12 | } -------------------------------------------------------------------------------- /SpringCMOpportunity.tgr: -------------------------------------------------------------------------------- 1 | trigger SpringCMOpportunity on Opportunity (after insert) { 2 | Map rtMap = Schema.SObjectType.Opportunity.getRecordTypeInfosById(); 3 | Map opp = new Map(); 4 | 5 | for(Opportunity o : Trigger.new) { 6 | String recordType = null; 7 | if(rtMap.size() > 1) 8 | recordType = rtMap.get((ID)o.get('RecordTypeId')).getName(); 9 | opp.put(o.Id, recordType); 10 | } 11 | SpringCMRestWrap.BuildEOS(opp, 'Opportunity', UserInfo.getSessionId(), 'CreateSubFolderStructure_CSV'); 12 | } -------------------------------------------------------------------------------- /LeadTrigger.tgr: -------------------------------------------------------------------------------- 1 | trigger LeadTrigger on Lead (before insert, before update) 2 | { 3 | List records = trigger.isDelete ? trigger.old : trigger.new; 4 | 5 | if(trigger.isBefore) 6 | { 7 | if(trigger.isInsert) 8 | { 9 | ULead.runRules(records, trigger.oldMap); 10 | } 11 | else if(trigger.isUpdate) 12 | { 13 | ULead.runRules(records, trigger.oldMap); 14 | } 15 | //else if(trigger.isDelete) 16 | //{ 17 | //} 18 | } 19 | 20 | //else 21 | /* 22 | if(trigger.isAfter) 23 | { 24 | if(trigger.isInsert) 25 | { 26 | } 27 | else if(trigger.isUpdate) 28 | { 29 | } 30 | else if(trigger.isDelete) 31 | { 32 | } 33 | else if(trigger.isUndelete) 34 | { 35 | } 36 | } 37 | */ 38 | } -------------------------------------------------------------------------------- /src/classes/TestThinkingInApex.cls: -------------------------------------------------------------------------------- 1 | @isTest 2 | private class TestThinkingInApex { 3 | 4 | // This test will fail when OnOpportunity1 trigger is enabled 5 | static testMethod void testOCRs1() 6 | { 7 | Opportunity op = new Opportunity( 8 | name='optest', StageName ='Prospecting', 9 | CloseDate = Date.Today() ); 10 | 11 | insert op; 12 | } 13 | 14 | static testMethod void testOCRs2() 15 | { 16 | Opportunity op = new Opportunity( 17 | name='optest', StageName ='Prospecting', 18 | CloseDate = Date.Today() ); 19 | 20 | Contact ct = new Contact(LastName = 'newct'); 21 | insert ct; 22 | ThinkingInApex.associateContacts = new List{ct}; 23 | 24 | insert op; 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /src/classes/BulkPatternBatch.cls: -------------------------------------------------------------------------------- 1 | global class BulkPatternBatch implements Database.Batchable { 2 | 3 | global final string query; 4 | global final Set opportunityIds; 5 | 6 | 7 | public bulkPatternBatch(Set opportunityIDsToUpdate) 8 | { 9 | opportunityids = opportunityIDsToUpdate; 10 | query = 'SELECT ID, OwnerID from Opportunity where ID in :opportunityids '; 11 | } 12 | 13 | global Database.QueryLocator start(Database.BatchableContext BC){ 14 | return Database.getQueryLocator(query); 15 | } 16 | 17 | global void execute(Database.BatchableContext BC, List scope){ 18 | List ops = (List)scope; 19 | Map newmap = new Map(ops); 20 | ThinkingInApexBulkPatterns.afterUpdateOpportunityBatchSupport(ops, newMap, null); 21 | return; 22 | } 23 | 24 | global void finish(Database.BatchableContext BC){ 25 | 26 | } 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/objects/Contact.object: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Level2__c 5 | false 6 | 7 | 8 | 9 | Primary 10 | false 11 | 12 | 13 | Secondary 14 | false 15 | 16 | 17 | Tertiary 18 | false 19 | 20 | false 21 | 22 | false 23 | Picklist 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/classes/AppConfigSupport.cls: -------------------------------------------------------------------------------- 1 | public with sharing class AppConfigSupport { 2 | 3 | private static AppConfig__c testConfig = null; 4 | 5 | public static AppConfig__c getAppConfig() 6 | { 7 | if(Test.isRunningTest() && testConfig!=null) return testConfig; 8 | 9 | AppConfig__c theobject = AppConfig__c.getInstance('default'); 10 | if(theObject==null || Test.isRunningTest()) 11 | { 12 | theObject = new AppConfig__c(); 13 | theObject.name = 'default'; 14 | theObject.EnableDiagnostics__c = (Test.isRunningTest())? true: false; 15 | theObject.AppEnabled__c = true; 16 | if(!Test.isRunningTest()) Database.Insert(theobject); 17 | else testconfig = theObject; 18 | } 19 | return theObject; 20 | } 21 | 22 | public static Boolean diagnosticsEnabled 23 | { 24 | get 25 | { 26 | return GetAppConfig().EnableDiagnostics__c; 27 | } 28 | } 29 | 30 | public static Boolean appEnabled 31 | { 32 | get 33 | { 34 | return GetAppConfig().AppEnabled__c; 35 | } 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /src/objects/Solution.object: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SolutionSpanish__c 5 | Solution auto-translated into Spanish 6 | false 7 | 8 | 32768 9 | false 10 | LongTextArea 11 | 3 12 | 13 | 14 | TranslationPending__c 15 | false 16 | Indicates this record has a translation pending 17 | false 18 | Indicates this record has a translation pending 19 | 20 | false 21 | Checkbox 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/objects/Opportunity.object: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TestCounterAmount__c 5 | Test Amount field used for concurrency testing 6 | false 7 | 8 | 18 9 | false 10 | 0 11 | false 12 | false 13 | Number 14 | false 15 | 16 | 17 | Enforce_Tracking_Number 18 | false 19 | ISBLANK(TrackingNumber__c ) 20 | TrackingNumber__c 21 | Tracking number must be set for opportunities 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/classes/TestGoingAsync.cls: -------------------------------------------------------------------------------- 1 | @isTest 2 | private class TestGoingAsync { 3 | 4 | private static Integer bulkTestSize = 101; 5 | 6 | static testMethod void testSolutionsInsert() { 7 | 8 | List sols = new List(); 9 | 10 | for(Integer x = 0; x solsmap = new Map(sols); 27 | 28 | List results = 29 | [Select ID, SolutionNote, SolutionSpanish__c 30 | from Solution where ID in :solsmap.keyset()]; 31 | for(Solution sol: results) 32 | System.AssertEquals(sol.SolutionNote + ' in Spanish', 33 | sol.SolutionSpanish__c); 34 | 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /src/classes/TestTriggersExample.cls: -------------------------------------------------------------------------------- 1 | @istest 2 | public class TestTriggersExample { 3 | 4 | private static final Integer numberOfOpportunities = 5; 5 | 6 | static testMethod void testTaskCount() { 7 | 8 | List ops = new List(); 9 | 10 | for(Integer x=0; x opMap = new Map(ops); 29 | 30 | List tasks = 31 | [Select ID, WhatID from Task 32 | where WhatID in :opmap.keyset() 33 | And Subject ='Opportunity stage update']; 34 | 35 | System.AssertEquals(numberOfOpportunities, tasks.size()); 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /ContactTrigger.tgr: -------------------------------------------------------------------------------- 1 | /* 2 | Name: ContactTrigger 3 | Created By: Raja Yeccherla(GearsCRM) 4 | Created Date: 06/22/2016 5 | Description: Trigger for Contact 6 | 7 | Modified By: 8 | Modified Date: 9 | Description: 10 | */ 11 | trigger ContactTrigger on contact (after insert, after update) { 12 | 13 | List records = trigger.isDelete ? trigger.old :trigger.new; 14 | 15 | /*if(trigger.isBefore) 16 | { 17 | if(trigger.isInsert) 18 | { 19 | } 20 | else if(trigger.isUpdate) 21 | { 22 | } 23 | else if(trigger.isDelete) 24 | { 25 | } 26 | } 27 | else if(trigger.isAfter)*/ 28 | 29 | if(trigger.isafter ) 30 | { 31 | if(trigger.isInsert) 32 | { 33 | UContact.setAccountTeamMember4(records, trigger.oldMap); 34 | } 35 | else if(trigger.isUpdate) 36 | { 37 | UContact.setAccountTeamMember4(records, trigger.oldMap); 38 | } 39 | /*else if(trigger.isDelete) 40 | { 41 | } 42 | else if(trigger.isUndelete) 43 | { 44 | } */ 45 | } 46 | } -------------------------------------------------------------------------------- /src/classes/GoingAsync3.cls: -------------------------------------------------------------------------------- 1 | public without sharing class GoingAsync3 2 | implements queueable, Database.AllowsCallouts { 3 | 4 | public void execute(QueueableContext context) 5 | { 6 | if(!AppConfigSupport.appEnabled) return; // On/off switch 7 | 8 | Integer allowedCallouts = 9 | Limits.getLimitCallouts() - Limits.getCallouts(); 10 | 11 | if(allowedCallouts<=0) return; 12 | List solutionsToUpdate = 13 | [SELECT ID, SolutionNote, SolutionSpanish__c 14 | from Solution 15 | where LastModifiedDate > :DateTime.Now().addHours(-24) 16 | And TranslationPending__c = true LIMIT :allowedCallouts]; 17 | for(Solution sl: solutionsToUpdate) 18 | { 19 | sl.SolutionSpanish__c = 20 | SimulatedTranslator.translate(sl.SolutionNote); 21 | sl.TranslationPending__c = false; 22 | } 23 | update solutionsToUpdate; 24 | 25 | 26 | if(solutionsToUpdate.size()== allowedCallouts && 27 | Limits.getLimitQueueableJobs() - Limits.getQueueableJobs() > 0) 28 | try 29 | { 30 | system.enqueueJob(new GoingAsync3()); 31 | } catch(Exception ex) 32 | { 33 | // Alternate chaining mechanism 34 | } 35 | 36 | } 37 | 38 | 39 | } -------------------------------------------------------------------------------- /src/objects/AppConfig__c.object: -------------------------------------------------------------------------------- 1 | 2 | 3 | List 4 | Protected 5 | Application configuration 6 | false 7 | 8 | AppEnabled__c 9 | false 10 | Application is enabled 11 | false 12 | Application is enabled 13 | 14 | false 15 | Checkbox 16 | 17 | 18 | EnableDiagnostics__c 19 | false 20 | True to enable diagnostics 21 | false 22 | 23 | false 24 | Checkbox 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ThinkInApex.cls: -------------------------------------------------------------------------------- 1 | /** With this approach, you can cache all necessary information from an object with only one SOQL call 2 | With this approach you can run into trouble if you try to cache large amount of data. 3 | **/ 4 | 5 | public class ThinkingInApex { 6 | 7 | private static Boolean UserCacheLoaded = false; 8 | private static Boolean UserIsSpecial = false; 9 | private static String UserTimeZone = null; 10 | 11 | public static Boolean IsUserSpecial() 12 | { 13 | if(UserCacheLoaded) return UserIsSpecial; 14 | CacheUserInfo(); 15 | return UserIsSpecial: 16 | } 17 | 18 | public static String UserTimeZone() 19 | { 20 | if(UserCacheLoaded) return UserTimeZone; 21 | CacheUserInfo(); 22 | return UserTimeZone; 23 | } 24 | 25 | private static void CacheUserInfo() 26 | { 27 | if(UserCacheLoaded) return UserTimeZone; 28 | CacheUserInfo(); 29 | return UserTimeZone; 30 | } 31 | 32 | private static void CacheUserInfo() 33 | { 34 | if(UserCahceLoaded) return; 35 | User u = [SELECT UserIsSpecial__c, 36 | TimeZoneSidKey from User where 37 | ID = :UserInfo.getUserId()]; 38 | UserIsSpecial = u.UserIsSpecial__c; 39 | UserTimeZone = u.TimeZoneSidKey; 40 | UserCacheLoaded = true; 41 | } 42 | 43 | } 44 | 45 | -------------------------------------------------------------------------------- /HandleAfterInsert_LeadConversion.tgr: -------------------------------------------------------------------------------- 1 | trigger HandleAfterInsert_LeadConversion on Contact (after insert) { 2 | 3 | if(LithiumRecursionController.HandleAfterInsert_LeadConversion == true) 4 | return; 5 | LithiumRecursionController.HandleAfterInsert_LeadConversion = true; 6 | 7 | Map mapLithmIdToCntcs = new Map(); 8 | List updLiCmmUsrs = new List(); 9 | 10 | for(Contact aContact : Trigger.new) { 11 | System.debug('___lithiumid___'+aContact.LithiumId__c); 12 | if(aContact.LithiumId__c != NULL) { 13 | mapLithmIdToCntcs.put(Integer.valueOf(aContact.LithiumId__c),aContact.Id); 14 | } 15 | } 16 | 17 | System.debug('___mapLithmIdToCntcs____'+mapLithmIdToCntcs); 18 | 19 | for(Li_Community_User1__c aCommunityUser : [SELECT Id,Contact__c,Lithium_User_Id__c from Li_Community_User1__c Where Lithium_User_Id__c != NULL AND Lithium_User_Id__c IN : mapLithmIdToCntcs.keySet()]) { 20 | if(mapLithmIdToCntcs.containsKey(Integer.valueOf(aCommunityUser.Lithium_User_Id__c))) { 21 | System.debug('____contact_id____'+mapLithmIdToCntcs.get(Integer.valueOf(aCommunityUser.Lithium_User_Id__c))); 22 | aCommunityUser.Contact__c = mapLithmIdToCntcs.get(Integer.valueOf(aCommunityUser.Lithium_User_Id__c)); 23 | updLiCmmUsrs.Add(aCommunityUser); 24 | } 25 | } 26 | System.debug('___updLiCmmUsrs____'+updLiCmmUsrs); 27 | update updLiCmmUsrs; 28 | } -------------------------------------------------------------------------------- /src/classes/GoingAsync2.cls: -------------------------------------------------------------------------------- 1 | global class GoingAsync2 implements 2 | Database.Batchable, 3 | Database.AllowsCallouts { 4 | 5 | global Database.Querylocator start(Database.BatchableContext bc) 6 | { 7 | return Database.getQueryLocator('SELECT ID, SolutionNote, SolutionSpanish__c From Solution Where TranslationPending__c = true'); 8 | } 9 | 10 | global void execute(Database.BatchableContext BC, List scope) 11 | { 12 | 13 | for(Solution sl: scope) 14 | { 15 | sl.SolutionSpanish__c = SimulatedTranslator.translate(sl.SolutionNote); 16 | sl.TranslationPending__c = false; 17 | } 18 | update scope; 19 | 20 | } 21 | 22 | global void finish(Database.BatchableContext BC) 23 | { 24 | List stillPending = 25 | [SELECT ID From Solution 26 | Where TranslationPending__c = true Limit 1]; 27 | if(stillPending.size()>0) StartBatch(true); 28 | } 29 | 30 | public static Boolean isBatchActive(String classname) 31 | { 32 | List activeStatuses = 33 | new List{'Completed','Aborted','Failed'}; 34 | AsyncApexJob[] activeJobs = 35 | [select id, CompletedDate, Status, ExtendedStatus, ApexClassID 36 | from AsyncApexJob where ApexClass.Name = :classname 37 | and JobType='BatchApex' 38 | And Status Not in :activeStatuses 39 | Order By CreatedDate Desc Limit 1]; 40 | return activeJobs.size() >0; 41 | } 42 | 43 | private static Boolean batchRequested = false; 44 | 45 | public static void startBatch(Boolean forceStart) 46 | { 47 | if(!forceStart && 48 | (batchRequested || isBatchActive('GoingAsync2'))) return; 49 | 50 | GoingAsync2 ga = new GoingAsync2(); 51 | Integer batchSize = Limits.getLimitCallouts(); 52 | if(batchSize>200) batchSize = 200; 53 | try 54 | { 55 | Database.executeBatch(ga, batchSize); 56 | } catch(Exception ex) 57 | { 58 | return; 59 | } 60 | 61 | batchRequested = true; 62 | } 63 | 64 | 65 | 66 | } -------------------------------------------------------------------------------- /src/classes/TriggersExample.cls: -------------------------------------------------------------------------------- 1 | public class TriggersExample { 2 | 3 | public static void afterUpdateOpportunityCreateTasks1( 4 | List newList, Map newMap, 5 | Map oldMap) 6 | { 7 | List newTasks = new List(); 8 | 9 | for(Opportunity op: newList) 10 | { 11 | system.debug('old probability ' + 12 | oldMap.get(op.id).Probability + 13 | ' new probability ' + op.Probability); 14 | if(oldMap.get(op.id).Probability == 10 && op.Probability > 10) 15 | { 16 | newTasks.add( 17 | new Task(ownerId = op.OwnerID, 18 | WhatID = op.id, 19 | ActivityDate = Date.Today().addDays(2), 20 | Subject='Opportunity stage update', 21 | Type='Other')); 22 | } 23 | } 24 | insert newTasks; 25 | } 26 | 27 | private static Map probabilityOverrides = null; 28 | 29 | public static void afterUpdateOpportunityCreateTasks2( 30 | List newList, Map newMap, 31 | Map oldMap) 32 | { 33 | List newTasks = new List(); 34 | if(probabilityOverrides==null) probabilityOverrides = new Map(); 35 | 36 | for(Opportunity op: newList) 37 | { 38 | Double oldProbability = (probabilityOverrides.containskey(op.id))? 39 | probabilityOverrides.get(op.id) : 40 | oldMap.get(op.id).Probability; 41 | system.debug('old probability ' + oldMap.get(op.id).Probability + 42 | ' new probability ' + op.Probability); 43 | if(oldProbability == 10 && op.Probability > 10) 44 | { 45 | newTasks.add( 46 | new Task(ownerId = op.OwnerID, WhatID = op.id, 47 | ActivityDate = Date.Today().addDays(2), 48 | Subject='Opportunity stage update', 49 | Type='Other')); 50 | } 51 | if(oldProbability != op.Probability) 52 | probabilityOverrides.put(op.id, op.Probability); 53 | 54 | } 55 | insert newTasks; 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /activityCreatesOpp (1).tgr: -------------------------------------------------------------------------------- 1 | trigger activityCreatesOpp on Opportunity (after insert) { 2 | 3 | // create set of Activity Ids that are Events; create map of Activity Id to Event 4 | set eventIds = new set(); 5 | map eventMap = new map(); 6 | // create set of Activity Ids that are Tasks; create map of Activity Id to Task 7 | set taskIds = new set(); 8 | map taskMap = new map(); 9 | 10 | // If the Activity Id on the opp is an Event or Task, add it to the appropriate set 11 | for(Opportunity opp : Trigger.new) { 12 | String actId = opp.ActivityId__c; 13 | if(String.isNotBlank(actId) && actId.startsWith('00U')) { 14 | eventIds.add(opp.ActivityId__c); 15 | } 16 | if(String.isNotBlank(actId) && actId.startsWith('00T')) { 17 | taskIds.add(opp.ActivityId__c); 18 | } 19 | } 20 | 21 | // If there are any Events that led to opp creation, create a mapping pair 22 | if(eventIds.size() > 0) { 23 | for(Event e : [SELECT Id from Event WHERE Id IN :eventIds]) { 24 | eventMap.put(e.Id, e); 25 | } 26 | } 27 | 28 | // If there are any Tasks that led to opp creation, create a mapping pair 29 | if(taskIds.size() > 0) { 30 | for(Task t : [SELECT Id from Task WHERE Id IN :taskIds]) { 31 | taskMap.put(t.Id, t); 32 | } 33 | } 34 | 35 | // changes the WhatId to the Opportunity Id on any Events or Tasks that are referenced on the Opportunity 36 | for(Opportunity opp : Trigger.new) { 37 | if(eventMap.containsKey(opp.ActivityId__c)) { 38 | eventMap.get(opp.ActivityId__c).WhatId = opp.Id; 39 | } 40 | if(taskMap.containsKey(opp.ActivityId__c)) { 41 | taskMap.get(opp.ActivityId__c).WhatId = opp.Id; 42 | } 43 | } 44 | 45 | // update the Events or Tasks that created an Opportunity 46 | if(eventMap.size() > 0) { 47 | update eventMap.values(); 48 | } 49 | if(taskMap.size() > 0) { 50 | update taskMap.values(); 51 | } 52 | 53 | 54 | } -------------------------------------------------------------------------------- /SubscriptionProductUserLeadConvert.tgr: -------------------------------------------------------------------------------- 1 | trigger SubscriptionProductUserLeadConvert on Lead (before update) { 2 | 3 | // create mapping of Lead Id to Contact Id 4 | Map mapLeadIdContactId = new Map(); 5 | // create mapping of Lead Id to Account Id 6 | Map mapLeadIdAccountId = new Map(); 7 | 8 | // add the Lead Id:Contact Id pair to the mapping above for each Lead converted into a Contact 9 | for (Lead l : Trigger.new) { 10 | if (l.isConverted) { 11 | mapLeadIdContactId.put(l.Id, l.convertedContactId); 12 | mapLeadIdAccountId.put(l.Id, l.convertedAccountId); 13 | } 14 | /*if (l.isConverted && l.convertedAccountId != null) { 15 | mapLeadIdAccountId.put(l.Id, l.convertedAccountId); 16 | }*/ 17 | } 18 | 19 | // store a list of Product User records, including the Subscription Account Id, looking up to a Lead stored in the Lead:Contact mapping 20 | List listProductUser = [SELECT Lead__c, Contact__c, Subscription__c, Subscription__r.Account__c, External_Id__c 21 | FROM Product_User__c 22 | WHERE Lead__c IN :mapLeadIdContactId.keySet()]; 23 | 24 | Map subscriptionMap = new Map(); 25 | // iterate through all Product User records that lookup to a Lead 26 | for (Product_User__c p : listProductUser) { 27 | p.Contact__c = mapLeadIdContactId.get(p.Lead__c); 28 | if (String.isBlank(p.Subscription__r.Account__c)) { 29 | Subscription__c subscription = new Subscription__c( 30 | Id = p.Subscription__c, 31 | Account__c = mapLeadIdAccountId.get(p.Lead__c)); 32 | subscriptionMap.put(subscription.Id, subscription); 33 | } 34 | 35 | } 36 | 37 | List listSubscriptions = subscriptionMap.values(); 38 | try { 39 | update listSubscriptions; 40 | } 41 | catch (DmlException dmx) 42 | { 43 | // always handle DML Exceptions on cross object updates from a trigger 44 | } 45 | 46 | update listProductUser; 47 | 48 | } -------------------------------------------------------------------------------- /src/classes/GoingAsync5.cls: -------------------------------------------------------------------------------- 1 | public class GoingAsync5 2 | implements ScheduledDispatcher2.IScheduleDispatched { 3 | 4 | public void execute(SchedulableContext sc) 5 | { 6 | // When used as a backup to start the asnyc framework 7 | system.enqueueJob(new GoingAsync4()); 8 | // Always abort the job on completion 9 | system.abortJob(sc.getTriggerID()); 10 | } 11 | 12 | public static String getSchedulerExpression(Datetime dt) { 13 | // Don't try to schedule Apex before current time + buffer 14 | if(dt < DateTime.Now().AddMinutes(1)) 15 | dt = DateTime.Now().AddMinutes(1); 16 | return ('' + dt.second() + ' ' + dt.minute() + ' ' + 17 | dt.hour() + ' ' + dt.day() + ' ' + 18 | dt.month() + ' ? ' + dt.year()); 19 | } 20 | 21 | public static void startScheduler(DateTime scheduledTime, String jobName) 22 | { 23 | 24 | // Is the job already running? 25 | List jobs = 26 | [SELECT Id, CronJobDetail.Name, State, NextFireTime 27 | FROM CronTrigger 28 | WHERE CronJobDetail.Name= :jobName]; 29 | if(jobs.size()>0 && jobs[0].state!='COMPLETED' && 30 | jobs[0].state!='ERROR' && jobs[0].state!='DELETED') 31 | { 32 | // It's already running/scheduled 33 | 34 | // Depending on your design you might want to exit, 35 | // or abort and reschedule if the requested start time 36 | // is earlier 37 | return; 38 | } 39 | 40 | // If the job exists, it needs to be deleted 41 | if(jobs.size()>0) system.abortJob(jobs[0].id); 42 | 43 | 44 | try 45 | { 46 | System.schedule(jobName, 47 | getSchedulerExpression(scheduledTime), 48 | new ScheduledDispatcher2()); 49 | } catch(Exception ex) 50 | { 51 | system.Debug(ex.getMessage()); 52 | // Log the error? 53 | // Or throw the error to the caller? 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/classes/TestPersonAccount.cls: -------------------------------------------------------------------------------- 1 | @istest 2 | public class TestPersonAccount { 3 | 4 | static testMethod void testWithContacts() { 5 | List contacts = TestDiagnostics2.createContacts('patst', 3); 6 | contacts[0].LeadSource='Web'; 7 | contacts[1].LeadSource='Phone Inquiry'; 8 | contacts[2].LeadSource='Other'; 9 | Test.StartTest(); 10 | insert contacts; 11 | Test.StopTest(); 12 | // Seealldata is false, so we'll get the same 3 contacts 13 | Map contactMap = 14 | new Map([Select ID, Level2__c from Contact Limit 3]); 15 | system.assertEquals(contactMap.get(contacts[0].id).Level2__c,'Primary'); 16 | system.assertEquals(contactMap.get(contacts[1].id).Level2__c,'Primary'); 17 | system.assertEquals(contactMap.get(contacts[2].id).Level2__c,'Secondary'); 18 | } 19 | 20 | static testMethod void testWithAccounts() { 21 | List contacts = 22 | TestDiagnostics2.createContacts('patst', 3); 23 | List accounts = 24 | TestDiagnostics2.createAccounts('patest', 3); 25 | contacts[0].LeadSource='Web'; 26 | contacts[1].LeadSource='Phone Inquiry'; 27 | contacts[2].LeadSource='Other'; 28 | PersonAccountSupport.fakePersonContactIDs = new List(); 29 | PersonAccountSupport.fakePersonAccountDuringTest = true; 30 | insert contacts; 31 | for(Contact ct: contacts) 32 | PersonAccountSupport.fakePersonContactIDs.add(ct.id); 33 | Test.StartTest(); 34 | insert accounts; 35 | Test.StopTest(); 36 | // Seealldata is false, so we'll get the same 3 contacts 37 | Map contactMap = 38 | new Map( 39 | [Select ID, Level2__c 40 | from Contact Limit 3]); 41 | system.assertEquals(contactMap.get(contacts[0].id).Level2__c,'Primary'); 42 | system.assertEquals(contactMap.get(contacts[1].id).Level2__c,'Primary'); 43 | system.assertEquals(contactMap.get(contacts[2].id).Level2__c,'Secondary'); 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /src/triggers/OnOpportunity2.trigger: -------------------------------------------------------------------------------- 1 | trigger OnOpportunity2 on Opportunity (after update) { 2 | 3 | // Samples for use with ThinkingInApexBulkPatterns.cls 4 | //ThinkingInApexBulkPatterns.afterUpdateOpportunityAwful(trigger.new, trigger.oldmap); 5 | //ThinkingInApexBulkPatterns.afterUpdateOpportunityCommon(trigger.new, trigger.newmap, trigger.oldmap); 6 | //ThinkingInApexBulkPatterns.afterUpdateOpportunitySets(trigger.new, trigger.newmap, trigger.oldmap); 7 | //ThinkingInApexBulkPatterns.afterUpdateOpportunityBetterQueries(trigger.new, trigger.newmap, trigger.oldmap); 8 | //ThinkingInApexBulkPatterns.afterUpdateOpportunityBetterQueries2(trigger.new, trigger.newmap, trigger.oldmap); 9 | //ThinkingInApexBulkPatterns.afterUpdateOpportunityFutureSupport(trigger.new, trigger.newmap, trigger.oldmap); 10 | //ThinkingInApexBulkPatterns.afterUpdateOpportunityBatchSupport(trigger.new, trigger.newmap, trigger.oldmap); 11 | 12 | 13 | // Trigger arhitecture examples 14 | // Example using parameters to track update objects 15 | /*Map objectsToUpdate = new Map(); 16 | TriggerArchitectureClass1.Entry1(trigger.new, trigger.newMap, trigger.old, trigger.oldMap, objectsToUpdate); 17 | TriggerArchitectureClass2.Entry1(trigger.new, trigger.newMap, trigger.old, trigger.oldMap, objectsToUpdate); 18 | if(objectstoupdate.size()>0) update objectsToUpdate.values(); 19 | */ 20 | 21 | // Examples using a dispatcher class 22 | 23 | //TriggerArchitectureMain1.Entry2(trigger.new, trigger.newMap, trigger.old, trigger.oldMap); 24 | //TriggerArchitectureMain1.Entry3(trigger.new, trigger.newMap, trigger.old, trigger.oldMap); 25 | 26 | // Compelte centralized dispatching 27 | /* TriggerArchitectureMain1.entry4('Opportunity', trigger.isBefore, trigger.isDelete, trigger.isAfter, 28 | trigger.isInsert, trigger.isUpdate, trigger.isExecuting, 29 | trigger.new, trigger.newMap, trigger.old, trigger.oldMap); 30 | */ 31 | 32 | 33 | // Samples for use with TriggerExample.cls 34 | //TriggersExample.afterUpdateOpportunityCreateTasks1(trigger.new, trigger.newmap, trigger.oldmap); 35 | TriggersExample.afterUpdateOpportunityCreateTasks2(trigger.new, trigger.newmap, trigger.oldmap); 36 | 37 | } -------------------------------------------------------------------------------- /src/classes/TriggerArchitectureClass1.cls: -------------------------------------------------------------------------------- 1 | public class TriggerArchitectureClass1 implements TriggerArchitectureMain1.ITriggerEntry { 2 | 3 | public static void entry1(List newlist, 4 | Map newMap, List oldList, 5 | Map oldMap, Map objectsToUpdate) 6 | { 7 | // Do some processing here 8 | // Add entries to the objectstoupdate map if they need to be updated 9 | } 10 | 11 | 12 | public static void entry2(List newList, 13 | Map newMap, List oldList, 14 | Map oldMap) 15 | { 16 | // Do some processing here 17 | // Add entries to the dispatcher static variable 18 | // if they need to be updated 19 | } 20 | 21 | 22 | public static void entry3(List newList, 23 | Map newMap, List oldList, 24 | Map oldMap) 25 | { 26 | // Do some processing here 27 | // Add entries to the dispatcher static variable if they need to be updated 28 | } 29 | 30 | public void mainEntry(String triggerObject, Boolean isBefore, 31 | Boolean isDelete, Boolean isAfter, Boolean isInsert, 32 | Boolean isUpdate, Boolean isExecuting, 33 | List newList, Map newMap, 34 | List oldList, Map oldMap) 35 | { 36 | List opNewList = (List)newList; 37 | List opOldList = (List)oldList; 38 | Map opNewMap = (Map)newMap; 39 | Map opOldMap = (Map)oldMap; 40 | 41 | // Do some processing here 42 | // Add entries to the dispatcher static variable 43 | //if they need to be updated or do direct DML 44 | 45 | } 46 | 47 | public void inProgressEntry(String triggerObject, Boolean isBefore, 48 | Boolean isDelete, Boolean isAfter, Boolean isInsert, 49 | Boolean isUpdate, Boolean isExecuting, 50 | List newList, Map newMap, 51 | List oldList, Map oldMap) 52 | { 53 | // Be sure to detect for the objects you actually want to handle. 54 | // Can dispatch to other classes is necessary 55 | } 56 | 57 | 58 | } -------------------------------------------------------------------------------- /src/classes/TestHeapAndSOQL.cls: -------------------------------------------------------------------------------- 1 | @isTest 2 | private class TestHeapAndSOQL { 3 | 4 | @testsetup static void setup() 5 | { 6 | // Build a 10K long string for description 7 | String longString = ''; 8 | for(Integer x = 0; x<500; x++) longstring += '0123456789'; 9 | 10 | List newLeads = new List(); 11 | for(Integer x = 0; x<8000; x++) 12 | newLeads.add(new Lead( 13 | Company = 'comp' + string.ValueOf(x), 14 | LastName = 'joe'+string.ValueOf(x), 15 | Description = longString)); 16 | insert newLeads; 17 | } 18 | 19 | 20 | static testMethod void testAllRecords() { 21 | 22 | Test.startTest(); 23 | 24 | List leadNames = new List(); 25 | 26 | List leads = [Select ID, Company, LastName, Description from Lead]; 27 | system.debug(logginglevel.info, 'lead count ' + leads.size()); 28 | for(Lead ld: leads) leadNames.add(ld.LastName); 29 | 30 | ShowLimits(); 31 | Test.stopTest(); 32 | 33 | } 34 | 35 | static testMethod void testSoqlLoop1() { 36 | 37 | Test.startTest(); 38 | 39 | List leadNames = new List(); 40 | 41 | for(List leads: [Select ID, Company, LastName, Description from Lead]) 42 | { 43 | system.debug(logginglevel.info, 'lead count ' + leads.size()); 44 | for(Lead ld: leads) leadNames.add(ld.LastName); 45 | } 46 | ShowLimits(); 47 | Test.stopTest(); 48 | 49 | } 50 | 51 | static testMethod void testSoqlLoop2() { 52 | 53 | Test.startTest(); 54 | 55 | List leadNames = new List(); 56 | 57 | for(Lead ld: [Select ID, Company, LastName, Description from Lead]) 58 | leadNames.add(ld.LastName); 59 | 60 | ShowLimits(); 61 | Test.stopTest(); 62 | 63 | } 64 | 65 | private static void ShowLimits() 66 | { 67 | system.debug(LoggingLevel.info, 'SOQL count ' + limits.getQueries()); 68 | system.debug(LoggingLevel.info, 'SOQL rows ' + limits.getQueryRows()); 69 | system.debug(LoggingLevel.info, 'Heap size ' + limits.getHeapSize()); 70 | 71 | } 72 | 73 | 74 | } -------------------------------------------------------------------------------- /src/classes/DiagnosticsTriggers2.cls: -------------------------------------------------------------------------------- 1 | public class DiagnosticsTriggers2 Implements DiagnosticsMain.ITriggerEntry { 2 | 3 | private static Map probabilityOverrides = null; 4 | 5 | public void mainEntry(String triggerObject, Boolean isBefore, 6 | Boolean isDelete, Boolean isAfter, Boolean isInsert, 7 | Boolean isUpdate, Boolean isExecuting, 8 | List newList, Map newMap, 9 | List oldList, Map oldMap) 10 | { 11 | DiagnosticsInstrumentation.push('DiagnosticsTriggers2.MainEntry'); 12 | afterUpdateOpportunityCreateTasks2((List)newList, (Map) newMap, (Map) oldMap); 13 | DiagnosticsInstrumentation.pop(); 14 | 15 | } 16 | public void inProgressEntry(String triggerObject, Boolean isBefore, Boolean isDelete, Boolean isAfter, Boolean isInsert, Boolean isUpdate, Boolean IsExecuting, List newlist, Map newmap, List oldlist, Map oldmap) 17 | { 18 | // Ignore triggers within triggers 19 | } 20 | 21 | 22 | private static void afterUpdateOpportunityCreateTasks2( 23 | List newList, Map newMap, 24 | Map oldMap) 25 | { 26 | DiagnosticsInstrumentation.push( 27 | 'DiagnosticsTriggers2.AfterUpdateOpportunityCreateTasks2'); 28 | 29 | List newTasks; 30 | // Comment out the following line to fake a runtime error 31 | // newtasks = new List(); 32 | if(probabilityOverrides==null) 33 | probabilityOverrides = new Map(); 34 | 35 | for(Opportunity op: newList) 36 | { 37 | Double oldProbability = (probabilityOverrides.containsKey(op.id))? 38 | probabilityOverrides.get(op.id) : oldMap.get(op.id).Probability; 39 | system.debug('old probability ' + oldMap.get(op.id).Probability + 40 | ' new probability ' + op.Probability); 41 | if(oldProbability == 10 && op.Probability > 10) 42 | { 43 | newTasks.add(new Task(OwnerId = op.OwnerID, WhatID = op.id, 44 | ActivityDate = Date.Today().addDays(2), 45 | Subject='Opportunity stage update', Type='Other')); 46 | } 47 | if(oldProbability != op.Probability) 48 | probabilityOverrides.put(op.id, op.Probability); 49 | 50 | } 51 | insert newTasks; 52 | DiagnosticsInstrumentation.pop(); 53 | 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /UpdateCommunityUserRoles.tgr: -------------------------------------------------------------------------------- 1 | trigger UpdateCommunityUserRoles on Account (after Update) { 2 | 3 | Map qualifiedAccounts = new Map(); 4 | 5 | if(Trigger.isUpdate && Trigger.isAfter){ 6 | 7 | for(Account acc : Trigger.new){ 8 | system.debug('__old___'+Lithium_Roles_With_IDs__c.getAll().containskey(Trigger.oldMap.get(acc.id).Market_Segment__c)); 9 | system.debug('__new___'+Lithium_Roles_With_IDs__c.getAll().containskey(acc.Market_Segment__c)); 10 | 11 | if(!qualifiedAccounts.containsKey(String.valueOf(acc.id).substring(0,15)) && acc.Market_Segment__c != null && (Trigger.oldMap.get(acc.id).Market_Segment__c == null || 12 | !Lithium_Roles_With_IDs__c.getAll().containskey(Trigger.oldMap.get(acc.id).Market_Segment__c)) && (Lithium_Roles_With_IDs__c.getAll().containskey(acc.Market_Segment__c))){ 13 | qualifiedAccounts.put(String.valueOf(acc.id).substring(0,15),acc); 14 | } 15 | } 16 | 17 | Map allRoles = Lithium_Roles_With_IDs__c.getAll(); 18 | Map rolesIDs = new Map(); 19 | 20 | for(Lithium_Roles_With_IDs__c role : allRoles.values()) { 21 | rolesIDs.put(role.name, role); 22 | } 23 | 24 | System.debug('__qualifiedAccounts___'+qualifiedAccounts); 25 | 26 | if(!qualifiedAccounts.isEmpty()){ 27 | 28 | List commUsersWithRelatedAccount = [SELECT id,Lithium_User_Id__c,Account_ID__c FROM Li_Community_User1__c WHERE Contact__r.AccountId =: qualifiedAccounts.keyset()]; 29 | for(Li_Community_User1__c cm : commUsersWithRelatedAccount){ 30 | if(cm.Account_ID__c != null){ 31 | if(qualifiedAccounts.get(cm.Account_ID__c) !=null){ 32 | System.debug('__userid___'+cm.Lithium_User_Id__c); 33 | System.debug('___market_segment___'+rolesIDs.get(qualifiedAccounts.get(cm.Account_ID__c).market_segment__c).role_id__c); 34 | Lithium_Revised_ConnectorUtils.sendAPIRequestV1UpdateUser(cm.Lithium_User_Id__c,rolesIDs.get(qualifiedAccounts.get(cm.Account_ID__c).market_segment__c).role_id__c); 35 | } 36 | } 37 | } 38 | 39 | System.debug('___commUsersWithRelatedAccount___'+commUsersWithRelatedAccount); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/classes/DiagnosticsInstrumentation.cls: -------------------------------------------------------------------------------- 1 | public without sharing class DiagnosticsInstrumentation { 2 | 3 | //public static Boolean diagnosticsEnabled = true; // Chapter 9 4 | public static Boolean diagnosticsEnabled = AppConfigSupport.diagnosticsEnabled; 5 | 6 | private static List diagnosticLog; 7 | private static Integer currentLevel = 0; 8 | 9 | private static List stackTrace = new List(); 10 | public static string exceptionTrace = ''; 11 | 12 | private class DiagnosticEntry 13 | { 14 | Integer level; 15 | String description; 16 | 17 | public diagnosticEntry(string entryDescription) 18 | { 19 | level = currentLevel; 20 | description = entryDescription; 21 | } 22 | } 23 | 24 | public static void push(String functionName) 25 | { 26 | debug('Entering: ' + functionName); 27 | currentLevel+=1; 28 | stacktrace.add(functionName); 29 | } 30 | 31 | public static void debug(String debugString) 32 | { 33 | if(!diagnosticsEnabled) return; 34 | if(diagnosticLog==null) diagnosticLog = new List(); 35 | diagnosticLog.add(new DiagnosticEntry(debugString)); 36 | } 37 | 38 | public static void debugException(Exception ex) 39 | { 40 | String exceptionInfo = 'Exception occurred line ' + 41 | ex.getLineNumber() + ' - ' + ex.getMessage() + 42 | ' stack: ' + ex.getStackTraceString(); 43 | debug(exceptionInfo); 44 | DebugInfo__c dbg = new DebugInfo__c(DebugData__c = currentLog()); 45 | exceptionTrace = ' Exception occurred line ' + 46 | ex.getLineNumber() + ' - ' + ex.getMessage(); 47 | for(String st: stackTrace) exceptionTrace += ' | ' + st; 48 | exceptionTrace += ' |\n '; 49 | if(diagnosticsEnabled) insert dbg; 50 | } 51 | 52 | public static void pop() 53 | { 54 | if(currentLevel>0) currentLevel-=1; 55 | if(currentLevel==0) System.Debug(LoggingLevel.Info, 56 | 'Diagnostic Log\n' + currentLog()); 57 | if(stackTrace.size()>0) 58 | stackTrace.remove(stackTrace.size()-1); 59 | } 60 | 61 | public static void popAll() 62 | { 63 | currentLevel=0; 64 | pop(); 65 | } 66 | 67 | public static String currentLog() 68 | { 69 | if(diagnosticLog == null) return null; 70 | String spaces = ' '; 71 | String result = ''; 72 | for(DiagnosticEntry de: diagnosticLog) 73 | { 74 | Integer endIndex = 3 * de.level; 75 | if(endIndex >= spaces.length()) endIndex = spaces.length()-1; 76 | result += spaces.substring(0,endIndex) + de.description + '\n'; 77 | } 78 | return result; 79 | } 80 | 81 | 82 | } -------------------------------------------------------------------------------- /src/classes/DiagnosticsMain.cls: -------------------------------------------------------------------------------- 1 | public class DiagnosticsMain { 2 | 3 | public interface ITriggerEntry 4 | { 5 | void mainEntry(String triggerObject, Boolean isBefore, 6 | Boolean isDelete, Boolean isAfter, Boolean isInsert, 7 | Boolean isUpdate, Boolean isExecuting, 8 | List newList, Map newMap, 9 | List oldList, Map oldMap); 10 | 11 | void inProgressEntry(String triggerObject, Boolean isBefore, 12 | Boolean isDelete, Boolean isAfter, Boolean isInsert, 13 | Boolean isUpdate, Boolean isExecuting, 14 | List newList, Map newMap, 15 | List oldList, Map oldMap); 16 | } 17 | 18 | public static ITriggerEntry activeFunction = null; 19 | 20 | public static Boolean fakeException = false; 21 | 22 | public static void mainEntry(String triggerObject, Boolean isBefore, 23 | Boolean isDelete, Boolean isAfter, Boolean isInsert, 24 | Boolean isUpdate, Boolean isExecuting, 25 | List newList, Map newMap, 26 | List oldList, Map oldMap) 27 | { 28 | DiagnosticsInstrumentation.push('MainEntry TriggerObject: ' + 29 | triggerObject + ' IsBefore: ' + isBefore + ' IsInsert: ' + 30 | isInsert + ' IsUpdate: ' + isUpdate); 31 | try 32 | { 33 | if(fakeException && activeFunction==null ) 34 | activefunction.inProgressEntry(triggerObject, isBefore, isDelete, 35 | isAfter, isInsert, isUpdate, isExecuting, 36 | newList, newMap, oldList, oldMap); 37 | 38 | if(activeFunction != null) 39 | { 40 | activefunction.inProgressEntry( 41 | triggerObject, isBefore, isDelete, 42 | isAfter, isInsert, isUpdate, isExecuting, 43 | newList, newMap, oldList, oldMap); 44 | return; 45 | } 46 | 47 | if(triggerObject == 'Opportunity' && isAfter && isUpdate) 48 | { 49 | activeFunction = new DiagnosticsTriggers1(); 50 | activeFunction.mainEntry(triggerObject, isBefore, isDelete, 51 | isAfter, isInsert, isUpdate, isExecuting, 52 | newList, newMap, oldList, oldMap); 53 | } 54 | 55 | if(TriggerObject == 'Opportunity' && isAfter && isUpdate) 56 | { 57 | activeFunction = new DiagnosticsTriggers2(); 58 | activefunction.mainEntry(triggerObject, isBefore, isDelete, 59 | isAfter, isInsert, isUpdate, isExecuting, 60 | newList, newMap, oldList, oldMap); 61 | } 62 | diagnosticsInstrumentation.Pop(); 63 | 64 | } 65 | catch(Exception ex) 66 | { 67 | diagnosticsInstrumentation.debugException(ex); 68 | diagnosticsInstrumentation.popAll(); 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/classes/SimulatedTranslator.cls: -------------------------------------------------------------------------------- 1 | global class SimulatedTranslator { 2 | 3 | 4 | global class MockTranslator implements HttpCalloutMock 5 | { 6 | global HTTPResponse respond(HTTPRequest req) 7 | { 8 | String requestBody = req.getBody(); 9 | HttpResponse res = new HttpResponse(); 10 | res.setHeader('Content-Type', 'text'); 11 | res.setBody(requestBody + ' in Spanish'); 12 | res.setStatusCode(200); 13 | return res; 14 | } 15 | } 16 | 17 | public static HTTPResponse translate(HTTPRequest req) 18 | { 19 | // Replace following with call to actual translation service 20 | //req.setEndpoint('http://api.salesforce.com/foo/bar'); 21 | 22 | // Just simulate the call for now - remove this code if you've implemented 23 | // real translation 24 | if(!Test.isRunningTest()) 25 | { 26 | // Call the mock translator 27 | //MockTranslator mock = new MockTranslator(); 28 | //return mock.respond(req); 29 | 30 | // or, if you enable this endpoint in your security settings, you 31 | // can uncomment these lines and have some fun :-) 32 | String endpoint ='http://isithackday.com/arrpi.php?format=text&text=' + EncodingUtil.urlEncode( req.getBody(),'UTF-8'); 33 | req.setBody(''); 34 | req.setEndpoint(endpoint); 35 | } 36 | req.setMethod('GET'); 37 | Http h = new Http(); 38 | HttpResponse res = h.send(req); 39 | return res; 40 | } 41 | 42 | // Note - there is an existing platform bug in which mock callouts fails in 43 | // queueable Apex. We are temporarily bypassing this issue by detecting the 44 | // failure and calling code that simulates the callout directly. 45 | public static String translate(String sourcetext) 46 | { 47 | HttpRequest req = new HttpRequest(); 48 | req.setBody(sourcetext); 49 | req.setHeader('Content-Type','text'); 50 | HttpResponse result; 51 | try 52 | { 53 | result = translate(req); 54 | } catch(System.CalloutException ex) 55 | { 56 | if (System.isBatch() && Test.isRunningTest() && ex.getMessage()=='Callout loop not allowed') 57 | return translate2(sourceText); 58 | else throw ex; 59 | } 60 | return result.getBody(); 61 | } 62 | 63 | 64 | private static Integer callCounter = 0; 65 | 66 | // Workaround for mock Apex issue in queueable 67 | public static String translate2(String sourceText) 68 | { 69 | if(!Test.isRunningTest()) 70 | { 71 | return translate(sourcetext); 72 | } 73 | callCounter++; 74 | if(callCounter > Limits.getLimitCallouts()) 75 | throw new SimulatedException('Too many simulated callouts'); 76 | return sourceText + ' in Spanish'; 77 | } 78 | 79 | public class SimulatedException extends Exception{} 80 | 81 | } -------------------------------------------------------------------------------- /src/classes/Benchmarking.cls: -------------------------------------------------------------------------------- 1 | @isTest 2 | private class Benchmarking { 3 | 4 | @istest 5 | public static void testNewAllocate() 6 | { 7 | for(Integer x = 0; x<10000; x++) 8 | returnNewMap(); 9 | } 10 | 11 | private static Map returnNewMap() 12 | { 13 | Map result = new Map(); 14 | return result; 15 | } 16 | 17 | @istest 18 | public static void testSorts() 19 | { 20 | List unsortedData = new List(); 21 | for(Integer x = 0; x<500; x++) 22 | { 23 | unsortedData.add(x); unsortedData.add(x+500); 24 | } 25 | for(Integer x = 0; x< 10000; x++) 26 | { 27 | returnSorted(unsortedData); 28 | } 29 | 30 | } 31 | 32 | private static List returnSorted(List inputList) 33 | { 34 | List sortedList1 = inputList.clone(); 35 | sortedList1.sort(); 36 | return sortedList1; 37 | } 38 | 39 | 40 | @istest 41 | public static void testProperties() 42 | { 43 | Lead ld = new Lead(LastName ='test', Email = 'someone@myemail.com'); 44 | for(Integer x = 0; x< 10000; x++) 45 | { 46 | checkForSpam(ld); 47 | } 48 | for(Integer x = 0; x< 10000; x++) 49 | { 50 | checkForSpam2(ld); 51 | } 52 | for(Integer x = 0; x< 10000; x++) 53 | { 54 | checkForSpam3(ld); 55 | } 56 | 57 | } 58 | 59 | private static Boolean checkForSpam(Lead ld) 60 | { 61 | if(ld.Email == null) return false; 62 | if(ld.Email.endsWithIgnoreCase('@yahoo.com')) return true; 63 | if(ld.Email.endsWithIgnoreCase('@gmail.com')) return true; 64 | if(ld.Email.endsWithIgnoreCase('@hotmail.com')) return true; 65 | return false; 66 | } 67 | 68 | private static Boolean checkForSpam2(Lead ld) 69 | { 70 | String testEmail = ld.Email; 71 | if(testEmail == null) return false; 72 | if(testEmail.endsWithIgnoreCase('@yahoo.com')) return true; 73 | if(testEmail.endsWithIgnoreCase('@gmail.com')) return true; 74 | if(testEmail.endsWithIgnoreCase('@hotmail.com')) return true; 75 | return false; 76 | } 77 | 78 | private static Boolean checkForSpam3(Lead ld) 79 | { 80 | String testEmail = ld.Email; 81 | if(testEmail == null) return false; 82 | testEmail = testEmail.toLowerCase(); 83 | if(testEmail.endsWith('@yahoo.com')) return true; 84 | if(testEmail.endsWith('@gmail.com')) return true; 85 | if(testEmail.endsWith('@hotmail.com')) return true; 86 | return false; 87 | } 88 | 89 | } -------------------------------------------------------------------------------- /src/classes/TestForPackages.cls: -------------------------------------------------------------------------------- 1 | @istest(seealldata = true) 2 | public class TestForPackages { 3 | 4 | static testMethod void recordTypeTest() { 5 | // This will fail to work on any org that doesn't have record types defined on leads 6 | // List rtypes = [Select ID, RecordTypeID from Lead Limit 1]; 7 | 8 | Boolean leadHasRecordType = 9 | Schema.Sobjecttype.Lead.Fields.getMap().containskey('recordtypeid'); 10 | 11 | String fieldString = 'ID '; 12 | if(leadHasRecordType) fieldString += ', RecordTypeID '; 13 | List rtypes = 14 | Database.Query('Select ' + fieldstring + ' from Lead Limit 1'); 15 | if(rtypes.size()>0 && leadHasRecordType) 16 | { 17 | system.debug(rtypes[0].get('RecordTypeID')); 18 | } 19 | system.debug(fieldString + ' ' + rtypes); 20 | } 21 | 22 | static testMethod void addProduct() 23 | { 24 | // Adding this line adds a Product2 dependency to the package 25 | //List aproduct = [Select ID from Product2 Limit 1]; 26 | } 27 | 28 | static testmethod void testPersonAccount() 29 | { 30 | PersonAccountSupport.isPersonAccountOrg(); 31 | } 32 | 33 | static testmethod void testCorporateCurrency() 34 | { 35 | String corporate = corporateCurrency; 36 | } 37 | 38 | private static Map 39 | cachedCurrencyConversionMap = null; 40 | 41 | private static string m_CorporateCurrency = null; 42 | 43 | public static string corporateCurrency { 44 | get { 45 | getCurrencyConversionMap(); 46 | return corporateCurrency; 47 | } 48 | } 49 | 50 | public static Map getCurrencyConversionMap() 51 | { 52 | Boolean currencyTestMode = false; 53 | 54 | if(cachedCurrencyConversionMap!=null) 55 | return cachedCurrencyConversionMap; 56 | 57 | if(Test.isRunningTest() && 58 | !userinfo.isMultiCurrencyOrganization()) 59 | currencyTestMode = true; 60 | if(!userinfo.isMultiCurrencyOrganization() && 61 | !currencyTestMode) return null; 62 | 63 | List ctypes = null; 64 | if(!currencyTestMode) ctypes = 65 | database.query('Select conversionrate, isocode, iscorporate from currencytype'); 66 | 67 | Map isoMap = new Map(); 68 | if(!currencyTestMode) 69 | { 70 | for(SObject ct: ctypes) 71 | { 72 | string ctCode = string.ValueOf(ct.get('isocode')); 73 | if(Boolean.valueOf(ct.get('iscorporate'))) 74 | { 75 | m_CorporateCurrency = ctCode; 76 | } 77 | double conversionRate = double.valueOf(ct.get('conversionrate')); 78 | if(conversionRate!=0) isoMap.put(ctcode, 1/conversionRate); 79 | } 80 | } 81 | cachedCurrencyConversionMap = (currencyTestMode)? null: isoMap; 82 | return cachedCurrencyConversionMap; 83 | } 84 | } -------------------------------------------------------------------------------- /src/classes/TriggerArchitectureClass2.cls: -------------------------------------------------------------------------------- 1 | public class TriggerArchitectureClass2 implements TriggerArchitectureMain1.ITriggerEntry { 2 | 3 | public static void entry1(List newlist, 4 | Map newMap, List oldList, 5 | Map oldMap, Map objectsToUpdate) 6 | { 7 | // Do some processing here 8 | // Add entries to the objectstoupdate map if they need to be updated 9 | } 10 | 11 | 12 | public static void entry2(List newList, 13 | Map newMap, List oldList, 14 | Map oldMap) 15 | { 16 | // Do some processing here 17 | // Add entries to the dispatcher static variable 18 | // if they need to be updated 19 | } 20 | 21 | public static void entry3(List newList, 22 | Map newMap, List oldList, 23 | Map oldMap) 24 | { 25 | // Do some processing here 26 | // Add entries to the dispatcher static variable if they need to be updated 27 | // Can examine the TriggerArchitectureMain1.inClass2 static variable to 28 | // modify behavior based on the knowledge of reentrancy 29 | } 30 | 31 | public void mainEntry(String triggerObject, Boolean isBefore, 32 | Boolean isDelete, Boolean isAfter, Boolean isInsert, 33 | Boolean isUpdate, Boolean isExecuting, 34 | List newList, Map newMap, 35 | List oldList, Map oldMap) 36 | { 37 | List opNewList = (List)newList; 38 | List opOldList = (List)oldList; 39 | Map opNewMap = (Map)newMap; 40 | Map opOldMap = (Map)oldMap; 41 | 42 | // Do some processing here 43 | // Add entries to the dispatcher static variable 44 | //if they need to be updated or do direct DML 45 | 46 | } 47 | 48 | public void inProgressEntry(String triggerObject, Boolean isBefore, 49 | Boolean isDelete, Boolean isAfter, Boolean isInsert, 50 | Boolean isUpdate, Boolean isExecuting, 51 | List newList, Map newMap, 52 | List oldList, Map oldMap) 53 | { 54 | // Be sure to detect for the objects you actually want to handle. 55 | // Can dispatch to other classes is necessary 56 | 57 | // Here's an example: 58 | 59 | if(TriggerObject == 'Opportunity' && IsAfter) 60 | { 61 | TriggerArchitectureMain1.activeFunction = new TriggerArchitectureClass1(); 62 | TriggerArchitectureMain1.activeFunction.mainEntry( 63 | triggerObject, isBefore, isDelete, isAfter, isInsert, isUpdate, isExecuting, 64 | newList, newMap, oldList, oldMap); 65 | TriggerArchitectureMain1.activeFunction = this; 66 | } 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /src/objects/DebugInfo__c.object: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Accept 5 | Default 6 | 7 | 8 | CancelEdit 9 | Default 10 | 11 | 12 | Clone 13 | Default 14 | 15 | 16 | Delete 17 | Default 18 | 19 | 20 | Edit 21 | Default 22 | 23 | 24 | Follow 25 | Default 26 | 27 | 28 | List 29 | Default 30 | 31 | 32 | New 33 | Default 34 | 35 | 36 | SaveEdit 37 | Default 38 | 39 | 40 | Tab 41 | Default 42 | 43 | 44 | View 45 | Default 46 | 47 | SYSTEM 48 | Deployed 49 | Debug Logs 50 | true 51 | true 52 | false 53 | false 54 | true 55 | true 56 | true 57 | 58 | DebugData__c 59 | Debug data entry 60 | false 61 | 62 | 131072 63 | false 64 | LongTextArea 65 | 3 66 | 67 | 68 | 69 | All 70 | Everything 71 | 72 | 73 | 74 | DBG-{0000} 75 | 76 | AutoNumber 77 | 78 | DebugInfos 79 | 80 | ReadWrite 81 | 82 | -------------------------------------------------------------------------------- /src/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AppConfigSupport 5 | Benchmarking 6 | BulkPatternBatch 7 | Concurrency1 8 | DiagnosticsInstrumentation 9 | DiagnosticsMain 10 | DiagnosticsTriggers1 11 | DiagnosticsTriggers2 12 | FunWithCollections 13 | GoingAsync1 14 | GoingAsync2 15 | GoingAsync3 16 | GoingAsync4 17 | GoingAsync5 18 | PersonAccountSupport 19 | ScheduledDispatcher2 20 | SimulatedTranslator 21 | SomeFutureOperations 22 | TestBulkPatterns 23 | TestDiagnostics1 24 | TestDiagnostics2 25 | TestForPackages 26 | TestGoingAsync 27 | TestHeapAndSOQL 28 | TestPersonAccount 29 | TestThinkingInApex 30 | TestThinkingInApexLimits 31 | TestTriggersExample 32 | ThinkingInApex 33 | ThinkingInApexBulkPatterns 34 | TriggerArchitectureClass1 35 | TriggerArchitectureClass2 36 | TriggerArchitectureMain1 37 | TriggersExample 38 | ApexClass 39 | 40 | 41 | OnAccount1 42 | OnAsyncRequestInsert 43 | OnContact1 44 | OnOpportunity1 45 | OnOpportunity2 46 | OnOpportunity3 47 | SolutionTrigger1 48 | SolutionTrigger2 49 | ApexTrigger 50 | 51 | 52 | Contact.Level2__c 53 | Opportunity.TestCounterAmount__c 54 | Solution.SolutionSpanish__c 55 | Solution.TranslationPending__c 56 | CustomField 57 | 58 | 59 | * 60 | CustomObject 61 | 62 | 63 | * 64 | StaticResource 65 | 66 | 67 | Opportunity.Enforce_Tracking_Number 68 | ValidationRule 69 | 70 | 33.0 71 | 72 | -------------------------------------------------------------------------------- /src/classes/TriggerArchitectureMain1.cls: -------------------------------------------------------------------------------- 1 | public class TriggerArchitectureMain1 { 2 | 3 | public static Map opsToUpdate = new Map(); 4 | 5 | public static void entry2(List newList, 6 | Map newMap, List oldList, 7 | Map oldMap) 8 | { 9 | TriggerArchitectureClass1.entry2(newList, newMap, oldList, oldMap); 10 | TriggerArchitectureClass2.entry2(newList, newMap, oldList, oldMap); 11 | if(opsToUpdate.size()>0) update opsToUpdate.values(); 12 | } 13 | 14 | public static Boolean inClass1 = false; 15 | public static Boolean inClass2 = false; 16 | 17 | public static void entry3(List newList, 18 | Map newMap, List oldList, 19 | Map oldMap) 20 | { 21 | if(!inClass1) 22 | { 23 | inClass1= true; 24 | TriggerArchitectureClass1.entry3(newList, newMap, oldList, oldMap); 25 | inClass1 = false; 26 | } 27 | inClass2 = true; 28 | TriggerArchitectureClass2.entry3(newList, newMap, oldList, oldMap); 29 | inClass2 = false; 30 | if(opsToUpdate.size()>0) update opsToUpdate.values(); 31 | } 32 | 33 | 34 | public interface ITriggerEntry 35 | { 36 | void mainEntry(String triggerObject, Boolean isBefore, 37 | Boolean isDelete, Boolean isAfter, 38 | Boolean isInsert, Boolean isUpdate, 39 | Boolean isExecuting, 40 | List newList, Map newMap, 41 | List oldList, Map oldMap); 42 | void inProgressEntry(String triggerObject, Boolean isBefore, 43 | Boolean isDelete, Boolean isAfter, 44 | Boolean isInsert, Boolean isUpdate, 45 | Boolean isExecuting, List newList, 46 | Map newMap, 47 | List oldList, Map oldMap); 48 | } 49 | 50 | public static ITriggerEntry activeFunction = null; 51 | 52 | public static void entry4(String triggerObject, Boolean isBefore, 53 | Boolean isDelete, Boolean isAfter, 54 | Boolean isInsert, Boolean isUpdate, 55 | Boolean isExecuting, List newList, 56 | Map newMap, List oldList, 57 | Map oldMap) 58 | { 59 | if(activeFunction != null) 60 | { 61 | activeFunction.inProgressEntry(triggerObject, isBefore, 62 | isDelete, isAfter, isInsert, isUpdate, isExecuting, 63 | newList, newMap, oldList, oldMap); 64 | return; 65 | } 66 | 67 | if(triggerObject == 'Opportunity' && isAfter && isUpdate) 68 | { 69 | activeFunction = new TriggerArchitectureClass1(); 70 | activeFunction.mainEntry(triggerObject, 71 | isBefore, isDelete, isAfter, isInsert, isUpdate, isExecuting, 72 | newList, newMap, oldList, oldMap); 73 | 74 | activeFunction = new TriggerArchitectureClass2(); 75 | activeFunction.mainEntry(triggerObject, 76 | isBefore, isDelete, isAfter, isInsert, isUpdate, isExecuting, 77 | newList, newMap, oldList, oldMap); 78 | 79 | if(opstoupdate.size()>0) update opsToUpdate.values(); 80 | activeFunction = null; 81 | } 82 | } 83 | 84 | 85 | 86 | } -------------------------------------------------------------------------------- /src/classes/PersonAccountSupport.cls: -------------------------------------------------------------------------------- 1 | public class PersonAccountSupport { 2 | 3 | @TestVisible public static Boolean fakePersonAccountDuringTest = false; 4 | 5 | private static Set accountFields = null; 6 | 7 | public static Boolean isPersonAccountOrg() 8 | { 9 | if(accountFields==null) accountFields = 10 | Schema.Sobjecttype.Account.fields.getMap().keyset(); 11 | return AccountFields.contains('personcontactid'); 12 | } 13 | 14 | // Map from contact field to account field 15 | public static String getPersonAccountAlias(String fieldName) 16 | { 17 | fieldName = fieldname.toLowerCase(); // Case insensitive 18 | 19 | // Unchanged - FirstName, LastName, etc. 20 | if(accountFields.contains(fieldName)) return fieldName; 21 | 22 | // Replace aliased __c with __pc 23 | fieldName = fieldName.replace('__c', '__pc'); 24 | if(accountFields.contains(fieldName)) return fieldname; 25 | 26 | if(accountFields.contains('person' + fieldName)) 27 | return ('person' + fieldName); 28 | 29 | return null; 30 | } 31 | 32 | public static void processContactTrigger1( 33 | Boolean isBefore, List newList, Map oldMap) 34 | { 35 | for(Contact ct: newList) 36 | { 37 | if(ct.LeadSource=='Web' || ct.LeadSource=='Phone Inquiry') 38 | ct.Level2__c = 'Primary'; 39 | else ct.Level2__c = 'Secondary'; 40 | } 41 | } 42 | 43 | public static List fakePersonContactIDs = null; 44 | 45 | private static Boolean updatingPersonContact = false; 46 | 47 | public static void processAccountTrigger1( 48 | Boolean isBefore, List newList, Map oldMap) 49 | { 50 | if(!isPersonAccountOrg() && !fakePersonAccountDuringTest || 51 | updatingPersonContact) return; 52 | 53 | if(isBefore) 54 | { 55 | // Using before approach 56 | String leadSourceAlias = getPersonAccountAlias('LeadSource'); 57 | String levelAlias = getPersonAccountAlias('Level2__c'); 58 | for(Account act: newList) 59 | { 60 | if(leadSourceAlias!=null && levelAlias!=null && 61 | (!fakePersonAccountDuringTest && act.get('PersonContactID')!=null)) 62 | { // Will only be valid on person accounts 63 | if(act.get(leadSourceAlias)=='Web' || 64 | act.get(leadSourceAlias)=='Phone Inquiry') 65 | act.put(levelAlias,'Primary'); 66 | else act.put(levelAlias,'Secondary'); 67 | } 68 | } 69 | } 70 | else 71 | { // Better approach can work on after trigger 72 | Set personContactIds = new Set(); 73 | for(Integer x = 0; x personContacts = 84 | new Map( 85 | [Select ID, LeadSource, Level2__c 86 | from Contact where ID in :personContactIds]); 87 | processContactTrigger1(true, 88 | personcontacts.values(), personcontacts); 89 | updatingPersonContact = true; 90 | update personcontacts.values(); 91 | updatingPersonContact = false; 92 | } 93 | 94 | 95 | } 96 | 97 | } -------------------------------------------------------------------------------- /src/classes/TestThinkingInApexLimits.cls: -------------------------------------------------------------------------------- 1 | @istest 2 | public class TestThinkingInApexLimits { 3 | 4 | // Demonstration of using a single query for an object and related object to reduce SOQL calls 5 | // First examples shows a straightforward approach: 6 | 7 | static testMethod void testContactsWithAccountRevenue1() { 8 | // Set up a dummy account and contact 9 | Account newact = new Account(Name = 'testaccount'); 10 | insert newact; 11 | Contact newct = new Contact(LastName = 'testcontactln'); 12 | newct.AccountId = newact.id; 13 | insert newct; 14 | 15 | Test.startTest(); 16 | 17 | // Query for contact info 18 | List cts = [SELECT ID, AccountID from Contact 19 | where Name = 'testcontactln']; 20 | 21 | // Some code that operates on the contacts here.... 22 | 23 | // Get list of account IDs. 24 | Set accountIds = new Set(); 25 | for(Contact ct: cts) 26 | if(ct.AccountID!=null) accountIds.add(ct.AccountID); 27 | 28 | if(accountIds.size()>0) 29 | { 30 | List accounts = [Select ID, AnnualRevenue from Account 31 | where ID in :accountids]; 32 | for(Account accountfound: accounts) 33 | if(accountfound.AnnualRevenue == null) 34 | accountfound.AnnualRevenue = 500; 35 | update accounts; 36 | } 37 | 38 | Test.stopTest(); 39 | 40 | // Make sure it worked 41 | if(accountIds.size()>0) 42 | { 43 | List accounts2 = [Select ID, AnnualRevenue from Account where ID in :accountids]; 44 | for(Account accountFound: accounts2) system.assertEquals(500, accountFound.AnnualRevenue); 45 | } 46 | 47 | 48 | } 49 | 50 | // Second example shows how you can use a related query 51 | static testMethod void testContactsWithAccountRevenue2() { 52 | // Set up a dummy account and contact 53 | Account newact = new Account(Name = 'testaccount'); 54 | insert newact; 55 | Contact newct = new Contact(LastName = 'testcontactln'); 56 | newct.AccountId = newact.id; 57 | insert newct; 58 | 59 | Test.startTest(); 60 | 61 | // Query for contact info and annual revenue on 62 | // account in a single query 63 | List cts = [SELECT ID, AccountID, Account.ID, 64 | Account.AnnualRevenue from Contact 65 | where Name = 'testcontactln']; 66 | 67 | // Some code that operates on the contacts here.... 68 | 69 | Map accountsToUpdate = new Map(); 70 | 71 | for(Contact ct: cts) 72 | { 73 | if (ct.Account.AnnualRevenue == null) 74 | { 75 | ct.Account.AnnualRevenue = 500; 76 | accountsToUpdate.put(ct.AccountID, ct.Account); 77 | } 78 | } 79 | 80 | if(accountsToUpdate.size()>0) 81 | update accountsToUpdate.values(); 82 | 83 | Test.stopTest(); 84 | 85 | // Make sure it worked 86 | if(accountsToUpdate.size()>0) 87 | { 88 | List accounts2 = [Select ID, AnnualRevenue from Account where ID in :accountstoupdate.keyset()]; 89 | for(Account accountFound: accounts2) system.assertEquals(500, accountFound.AnnualRevenue); 90 | } 91 | 92 | 93 | } 94 | 95 | } -------------------------------------------------------------------------------- /src/classes/Concurrency1.cls: -------------------------------------------------------------------------------- 1 | public class Concurrency1 { 2 | 3 | public static void delay(Integer seconds) 4 | { 5 | List largeArray = new List(); 6 | for(Integer x =0; x<10000; x++) largeArray.add(x); 7 | for(Integer counter = 0; counter0) delay(delayBefore); 22 | List ops = 23 | [Select ID, Amount From Opportunity 24 | where Name = :opportunityName]; 25 | for(Opportunity op: ops) 26 | op.Amount = (op.Amount==null)? 27 | amount: op.Amount + Amount; 28 | if(delayFromQuery>0) delay(delayFromQuery); 29 | update ops; 30 | if(delayAfter>0) delay(delayAfter); 31 | } 32 | 33 | @future 34 | public static void incrementPessimistic( 35 | double amount, Integer delayBefore, 36 | Integer delayFromQuery, Integer delayAfter) 37 | { 38 | if(DelayBefore>0) delay(delayBefore); 39 | List ops = 40 | [Select ID, Amount From Opportunity 41 | where Name = :opportunityName For Update]; 42 | for(Opportunity op: ops) 43 | op.Amount = (op.Amount==null)? 44 | amount: op.Amount + Amount; 45 | if(delayFromQuery>0) delay(delayFromQuery); 46 | update ops; 47 | if(delayAfter>0) delay(delayAfter); 48 | } 49 | 50 | 51 | 52 | @future 53 | public static void incrementOptimisticWithCapture( 54 | double amount, Integer delayBefore, 55 | Integer delayFromQuery, Integer delayAfter) 56 | { 57 | if(delayBefore>0) delay(delayBefore); 58 | List ops = 59 | [Select ID, Amount From Opportunity 60 | where Name = :opportunityName]; 61 | for(Opportunity op: ops) 62 | op.Amount = (op.Amount==null)? 63 | amount: op.Amount + Amount; 64 | if(delayFromQuery>0) delay(delayFromQuery); 65 | List 66 | dmlResults = Database.Update(ops, false); 67 | List failedUpdates = new List(); 68 | for(Integer x = 0; x< ops.size(); x++) 69 | { 70 | Database.SaveResult sr = dmlResults[x]; 71 | if(!sr.isSuccess()) 72 | { 73 | for(Database.Error err: sr.getErrors()) 74 | { 75 | if(err.getStatusCode() == StatusCode.UNABLE_TO_LOCK_ROW) 76 | { 77 | failedUpdates.add(ops[x]); 78 | break; 79 | } 80 | } 81 | } 82 | 83 | } 84 | 85 | if(failedUpdates.size()>0) 86 | { 87 | // Do a logging or recovery operation here 88 | recordRecoveryInformation(failedUpdates, amount); 89 | } 90 | 91 | if(delayAfter>0) delay(delayAfter); 92 | } 93 | 94 | @testvisible 95 | private static void recordRecoveryInformation( 96 | List failedOps, double amount) 97 | { 98 | List requests = new List(); 99 | for(Opportunity op: failedOps) 100 | { 101 | requests.add(new AsyncRequest__c(AsyncType__c = 'Amount Update', 102 | NewAmount__c = op.Amount, 103 | OriginalAmount__c = op.Amount - amount, 104 | TargetOpportunity__c = op.id )); 105 | } 106 | insert requests; 107 | } 108 | 109 | } -------------------------------------------------------------------------------- /src/classes/ThinkingInApex.cls: -------------------------------------------------------------------------------- 1 | public class ThinkingInApex { 2 | 3 | public class SimpleFieldAccess { 4 | User u = [Select UserIsSpecial__c from User 5 | where ID = :UserInfo.getUserId()]; 6 | Boolean userIsSpecial = u.UserIsSpecial__c; 7 | } 8 | 9 | /* First example 10 | 11 | private static Boolean isUserSpecialChecked = false; 12 | private static Boolean userIsSpecial = false; 13 | 14 | public static Boolean isUserSpecial() 15 | { 16 | if(isUserSpecialChecked) return userIsSpecial; 17 | 18 | User u = [Select UserIsSpecial__c from User 19 | where ID = :UserInfo.getUserId()]; 20 | userIsSpecial = u.UserIsSpecial__c; 21 | isUserSpecialChecked = true; 22 | return userIsSpecial; 23 | 24 | } 25 | 26 | */ 27 | 28 | private static Boolean userCacheLoaded = false; 29 | private static Boolean userIsSpecial = false; 30 | private static String userTimeZone = null; 31 | 32 | public static Boolean isUserSpecial() 33 | { 34 | if(userCacheLoaded) return userIsSpecial; 35 | cacheUserInfo(); 36 | return userIsSpecial; 37 | } 38 | 39 | public static String userTimeZone() 40 | { 41 | if(userCacheLoaded) return userTimeZone; 42 | cacheUserInfo(); 43 | return userTimeZone; 44 | } 45 | 46 | 47 | private static void cacheUserInfo() 48 | { 49 | if(userCacheLoaded) return; 50 | User u = [Select UserIsSpecial__c, TimeZoneSidKey 51 | from User where ID = :UserInfo.getUserId()]; 52 | userIsSpecial = u.UserIsSpecial__c; 53 | userTimeZone = u.TimeZoneSidKey; 54 | userCacheLoaded = true; 55 | } 56 | 57 | 58 | public static List associateContacts = null; 59 | 60 | /* First version of afterInsertOpportunity - does not insert opportunity contact roles 61 | 62 | public static void afterInsertOpportunity( 63 | List newList, Map newMap) 64 | { 65 | 66 | List ocrs = 67 | [SELECT ID, ContactID, IsPrimary, OpportunityID 68 | from OpportunityContactRole 69 | where OpportunityID in :newMap.keyset()]; 70 | 71 | Set ocrOpportunities = new Set(); 72 | 73 | for(OpportunityContactRole ocr: ocrs) 74 | ocrOpportunities.add(ocr.OpportunityID); 75 | 76 | for(Opportunity op: newList) 77 | { 78 | if(! ocrOpportunities.contains(op.id)) 79 | op.addError('Opportunity Contact Role is required to create an opportunity'); 80 | } 81 | 82 | // Other functionality 83 | 84 | } 85 | */ 86 | 87 | public static void afterInsertOpportunity( 88 | List newList, Map newMap) 89 | { 90 | 91 | if(AssociateContacts!=null) 92 | createSomeContactRoles(newList, associateContacts); 93 | 94 | List ocrs = 95 | [SELECT ID, ContactID, IsPrimary, OpportunityID 96 | from OpportunityContactRole 97 | where OpportunityID in :newMap.keyset()]; 98 | 99 | Set ocrOpportunities = new Set(); 100 | 101 | for(OpportunityContactRole ocr: ocrs) 102 | ocrOpportunities.add(ocr.OpportunityID); 103 | 104 | for(Opportunity op: newList) 105 | { 106 | if(! ocrOpportunities.contains(op.id)) 107 | op.addError('Opportunity Contact Role is required to create an opportunity'); 108 | } 109 | 110 | // Other functionality 111 | 112 | } 113 | 114 | 115 | private static void createSomeContactRoles( 116 | List ops, List cts) 117 | { 118 | List newOcrs = 119 | new List(); 120 | 121 | for(Integer x = 0; x< ops.size(); x++) 122 | { 123 | newOcrs.add( 124 | new OpportunityContactRole( 125 | OpportunityID = ops[x].id, 126 | ContactID = cts[x].id, 127 | IsPrimary = true)); 128 | } 129 | insert newOcrs; 130 | } 131 | 132 | 133 | 134 | 135 | } -------------------------------------------------------------------------------- /src/classes/GoingAsync1.cls: -------------------------------------------------------------------------------- 1 | public class GoingAsync1 { 2 | 3 | // Simple protection from workflows and triggers 4 | private static Boolean alreadyProcessed = false; 5 | 6 | public static void handleTrigger1(List solutionList, 7 | Map newMap, Map oldMap, 8 | Boolean isInsert) 9 | { 10 | if(alreadyProcessed) return; 11 | alreadyProcessed = true; 12 | if(isInsert) firstAttempt(newMap.keyset()); 13 | else 14 | { 15 | Set textChangedIds = new Set(); 16 | for(Solution sl: solutionList) 17 | { 18 | if(sl.SolutionNote!= oldMap.get(sl.id).SolutionNote) 19 | textChangedIds.add(sl.id); 20 | } 21 | if(textChangedIds.size()>0) firstAttempt(textChangedIds); 22 | } 23 | 24 | } 25 | 26 | 27 | @future(callout=true) 28 | public static void firstAttempt(Set solutionIds) 29 | { 30 | List solutionsToUpdate = 31 | [SELECT ID, SolutionNote, SolutionSpanish__c 32 | from Solution where ID in :solutionids]; 33 | for(Solution sl: solutionsToUpdate) 34 | sl.SolutionSpanish__c = SimulatedTranslator.translate(sl.SolutionNote); 35 | update solutionsToUpdate; 36 | } 37 | 38 | 39 | public static void handleTrigger2(List solutionlist, 40 | Map oldmap, Boolean isInsert) 41 | { 42 | if(alreadyProcessed) return; 43 | alreadyProcessed = true; 44 | for(Solution sl:solutionlist) 45 | { 46 | if(isInsert || sl.SolutionNote!= oldMap.get(sl.id).SolutionNote) 47 | sl.TranslationPending__c = true; 48 | } 49 | secondAttemptRequestAsync(); 50 | } 51 | 52 | public static void secondAttemptRequestAsync() 53 | { 54 | if(system.isFuture() || system.isBatch()) secondAttemptSync(); 55 | else 56 | { 57 | if(Limits.getFutureCalls()< Limits.getLimitFutureCalls()-3) 58 | secondAttemptAsync(); 59 | } 60 | } 61 | 62 | @future(callout=true) 63 | private static void secondAttemptAsync() 64 | { 65 | secondAttemptSync(); 66 | } 67 | 68 | public static void secondAttemptSync() 69 | { 70 | Integer allowedCallouts = Limits.getLimitCallouts() - Limits.getCallouts(); 71 | if(allowedCallouts<=0) return; 72 | List solutionsToUpdate = 73 | [SELECT ID, SolutionNote, SolutionSpanish__c 74 | from Solution 75 | where LastModifiedDate > :DateTime.Now().addHours(-24) 76 | And TranslationPending__c = true LIMIT :allowedCallouts]; 77 | if(solutionsToUpdate.size()==0) return; 78 | for(Solution sl: solutionsToUpdate) 79 | { 80 | sl.SolutionSpanish__c = SimulatedTranslator.translate(sl.SolutionNote); 81 | sl.TranslationPending__c = false; 82 | } 83 | update solutionsToUpdate; 84 | 85 | } 86 | 87 | public static void handleTrigger3(List solutionlist, 88 | Map oldmap, Boolean isInsert) 89 | { 90 | if(alreadyProcessed) return; 91 | alreadyProcessed = true; 92 | Boolean foundOne = false; 93 | for(Solution sl:solutionlist) 94 | { 95 | if(isInsert || sl.SolutionNote!= oldMap.get(sl.id).SolutionNote) 96 | { 97 | sl.TranslationPending__c = true; 98 | foundOne = true; 99 | } 100 | } 101 | if(foundOne) GoingAsync2.StartBatch(false); 102 | } 103 | 104 | public static void handleTrigger4(List solutionlist, 105 | Map oldmap, Boolean isInsert) 106 | { 107 | if(alreadyProcessed) return; 108 | alreadyProcessed = true; 109 | Boolean foundOne = false; 110 | for(Solution sl:solutionlist) 111 | { 112 | if(isInsert || sl.SolutionNote!= oldMap.get(sl.id).SolutionNote) 113 | { 114 | sl.TranslationPending__c = true; 115 | foundOne = true; 116 | } 117 | } 118 | if(foundOne && (Limits.GetLimitQueueableJobs() - Limits.GetQueueableJobs() > 0)) 119 | system.EnqueueJob(new GoingAsync3()); 120 | } 121 | 122 | public static void handleTrigger5(List solutionList, 123 | Map newMap, Map oldMap, 124 | Boolean isInsert) 125 | { 126 | if(alreadyProcessed) return; 127 | alreadyProcessed = true; 128 | List newAsyncRequests = new List(); 129 | 130 | List textChangedIds = new List(); 131 | for(Solution sl: solutionList) 132 | { 133 | if(isInsert || sl.SolutionNote!= oldMap.get(sl.id).SolutionNote) 134 | textChangedIds.add(sl.id); 135 | if(textChangedIds.size()>=100) 136 | { 137 | newAsyncRequests.add( 138 | new AsyncRequest__c(AsyncType__c = 'Translate Solution', 139 | Params__c = string.Join(textChangedIds,','))); 140 | textChangedIds.clear(); 141 | } 142 | } 143 | 144 | if(textChangedIds.size()>0) 145 | newAsyncRequests.add( 146 | new AsyncRequest__c(AsyncType__c = 'Translate Solution', 147 | Params__c = string.Join(textChangedIds,','))); 148 | 149 | insert newAsyncRequests; 150 | } 151 | 152 | } -------------------------------------------------------------------------------- /src/classes/TestDiagnostics1.cls: -------------------------------------------------------------------------------- 1 | @isTest 2 | private class TestDiagnostics1 { 3 | 4 | static testMethod void updateOpportunityTest() { 5 | List ops = new List(); 6 | initTestObjects(ops, 100, 100, 40, 80); 7 | 8 | DiagnosticsInstrumentation.Debug( 9 | 'Starting testing: UpdateOpportunityTest'); 10 | Test.StartTest(); 11 | for(Opportunity op: ops) 12 | op.StageName = 'Qualification'; 13 | update ops; 14 | Test.stopTest(); 15 | 16 | validateOCRs(ops); 17 | 18 | } 19 | 20 | static testMethod void createTaskTest() 21 | { 22 | Integer numberOfOpportunities = 100; 23 | List ops = new List(); 24 | for(Integer x=0; x tasks = 41 | [SELECT ID, OwnerID, WhatID, Status, Subject, Type 42 | from Task 43 | where OwnerID = :UserInfo.getUserID() 44 | And Type='Other' And IsClosed = False 45 | And Subject = 'Assign Primary Contact' ]; 46 | system.assertEquals(NumberOfOpportunities, tasks.size()); 47 | 48 | } 49 | 50 | private static final Integer numberOfStageUpdateOpportunities = 5; 51 | 52 | static testMethod void testTaskCount() { 53 | 54 | List ops = new List(); 55 | 56 | for(Integer x=0; x opMap = new Map(ops); 77 | 78 | List tasks = 79 | [Select ID, WhatID from Task 80 | where WhatID in :opmap.keyset() 81 | And Subject ='Opportunity stage update']; 82 | 83 | System.AssertEquals(numberOfStageUpdateOpportunities, tasks.size()); 84 | } 85 | 86 | // Prepare the specified number of opportunities, with contact roles on each. 87 | // The contact roles are distributed evenly among the number of contacts specified. 88 | public static void initTestObjects(List newOpportunities, 89 | Integer numberOfOpportunities, Integer numberOfOtherOpportunities, 90 | Integer contactRolesPerOp, Integer numberOfContacts) 91 | { 92 | if(numberOfContacts < contactRolesPerOp) 93 | numberOfContacts = contactRolesPerOp; 94 | 95 | Listcts = new List(); 96 | for(Integer x=0;x otherOpportunities = new List(); 116 | for(Integer x=0; x ocrList = new List(); 130 | Integer contactNumber = 0; 131 | for(Opportunity op: otherOpportunities) 132 | { 133 | for(Integer ocrNumber = 0; ocrNumber < contactRolesPerOp; ocrNumber++) 134 | { 135 | ocrList.add( 136 | new OpportunityContactRole(OpportunityID = op.id, 137 | ContactID = cts[contactNumber].id)); 138 | contactNumber++; 139 | if(contactNumber >= numberOfContacts) contactNumber = 0; 140 | } 141 | 142 | } 143 | insert ocrList; 144 | } 145 | 146 | public static void validateOCRs(List ops) 147 | { 148 | // Get map for IDs 149 | Map opMap = new Map(ops); 150 | 151 | List opresults = 152 | [SELECT ID, (SELECT ID from OpportunityContactRoles 153 | where IsPrimary = true) 154 | from opportunity where ID in :opmap.keyset() ]; 155 | 156 | for(Opportunity op: opResults) 157 | System.Assert(op.OpportunityContactRoles.size()==1); 158 | } 159 | 160 | } -------------------------------------------------------------------------------- /src/objects/AsyncRequest__c.object: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Accept 5 | Default 6 | 7 | 8 | CancelEdit 9 | Default 10 | 11 | 12 | Clone 13 | Default 14 | 15 | 16 | Delete 17 | Default 18 | 19 | 20 | Edit 21 | Default 22 | 23 | 24 | Follow 25 | Default 26 | 27 | 28 | List 29 | Default 30 | 31 | 32 | New 33 | Default 34 | 35 | 36 | SaveEdit 37 | Default 38 | 39 | 40 | Tab 41 | Default 42 | 43 | 44 | View 45 | Default 46 | 47 | SYSTEM 48 | Deployed 49 | Stores asynchronous requests 50 | false 51 | true 52 | false 53 | false 54 | true 55 | true 56 | true 57 | 58 | AsyncType__c 59 | Type of async operation 60 | false 61 | 62 | 63 | 64 | Translate Solution 65 | false 66 | 67 | 68 | Amount Update 69 | false 70 | 71 | false 72 | 73 | false 74 | Picklist 75 | 76 | 77 | Error_Message__c 78 | Error message 79 | false 80 | 81 | 32768 82 | false 83 | LongTextArea 84 | 3 85 | 86 | 87 | Error__c 88 | false 89 | Error occurred 90 | false 91 | 92 | false 93 | Checkbox 94 | 95 | 96 | NewAmount__c 97 | New value of amount field in Concurrency1 example 98 | false 99 | 100 | 18 101 | false 102 | 2 103 | false 104 | Currency 105 | 106 | 107 | OriginalAmount__c 108 | Original amount value for Concurrency1 example 109 | false 110 | 111 | 18 112 | false 113 | 2 114 | false 115 | Currency 116 | 117 | 118 | Params__c 119 | Parameters 120 | false 121 | 122 | 131072 123 | false 124 | LongTextArea 125 | 3 126 | 127 | 128 | TargetOpportunity__c 129 | SetNull 130 | Opportunity lookup used in Concurrency1 example 131 | false 132 | 133 | Opportunity 134 | AsyncRequests 135 | AsyncRequests 136 | false 137 | false 138 | Lookup 139 | 140 | 141 | 142 | All 143 | Everything 144 | 145 | 146 | 147 | ar-{0000} 148 | 149 | AutoNumber 150 | 151 | AsyncRequests 152 | 153 | ReadWrite 154 | Vowel 155 | 156 | -------------------------------------------------------------------------------- /CommunityUserBeforeTriggerClonned.tgr: -------------------------------------------------------------------------------- 1 | trigger CommunityUserBeforeTriggerClonned on Li_Community_User1__c (before insert, before update) { 2 | Map emailToCommunityUserMap = new Map(); 3 | Map emailToContactMap = new Map(); 4 | List listOfCommunityUsers = new List(); 5 | List contactsToInsert = new List(); 6 | LithiumSettings1__c liSetting = LithiumSettings1__c.getValues('DefaultSetting'); 7 | 8 | if(liSetting == NULL) { 9 | liSetting = new LithiumSettings1__c(); 10 | liSetting.Name = 'DefaultSetting'; 11 | liSetting.Sync_With_Contact__c = true; 12 | liSetting.Create_New_Contact_record__c = true; 13 | 14 | Database.insert(liSetting); 15 | } 16 | 17 | for(Li_Community_User1__c aCommunityUser : trigger.new) { 18 | if(aCommunityUser.Email_Address__c != NULL) 19 | emailToCommunityUserMap.put((aCommunityUser.Email_Address__c).trim().tolowercase(), aCommunityUser); // build a map of email>community user 20 | } 21 | //change comments after first run 22 | List cntcts = new List(); 23 | set stEmail = new set(); 24 | for(contact c: [SELECT Id,account.ownerid, account.owner.isactive, FirstName, LastName, Name,LithiumId__c, Email FROM Contact WHERE Email IN :emailToCommunityUserMap.keySet() and LithiumId__c != null]) 25 | { 26 | cntcts.add(c); 27 | stEmail.add(String.valueOf(c.Email).trim().tolowercase()); 28 | } 29 | for(contact c: [SELECT Id,account.ownerid, account.owner.isactive, FirstName, LastName, Name,LithiumId__c, Email FROM Contact WHERE Email IN :emailToCommunityUserMap.keySet() and LithiumId__c = null]) 30 | { 31 | if(!stEmail.contains(String.valueOf(c.Email).trim().tolowercase())) 32 | { 33 | cntcts.add(c); 34 | stEmail.add(String.valueOf(c.Email).trim().tolowercase()); 35 | } 36 | } 37 | //List cntcts = [SELECT Id, FirstName, LastName, LithiumId__c , Name, Email FROM Contact WHERE Email IN :emailToCommunityUserMap.keySet()]; 38 | //first run ends 39 | 40 | for(Contact aContact1 : cntcts) { 41 | if(!emailToContactMap.containsKey(aContact1.Email)){ 42 | emailToContactMap.put(String.valueOf(aContact1.Email).trim().tolowercase(), aContact1); // build a map of email>contact (Contacts which have same email as community users) 43 | } 44 | } 45 | List lstCon = new List(); 46 | for(String usr_email : emailToCommunityUserMap.keySet()) { // loop through all community users with email address 47 | usr_email = usr_email.trim().tolowercase(); 48 | Contact aContact = emailToContactMap.get(usr_email); 49 | 50 | Li_Community_User1__c aCommunityUser = emailToCommunityUserMap.get(usr_email); 51 | if(aContact != NULL && liSetting.Sync_With_Contact__c){ // attach Contact record or not 52 | aCommunityUser.Contact__c = aContact.Id; // use existing contact 53 | if(aContact.account != null && aContact.account.owner.isactive == true && aContact.account.ownerId != null){ 54 | aCommunityUser.ownerid = aContact.account.ownerId; 55 | } 56 | //Comment after first run 57 | //if(aContact.LithiumId__c == null) 58 | //lstCon.add(new contact(id=aContact.Id,LithiumId__c = aCommunityUser.Lithium_User_Id__c)); 59 | //Comment ends 60 | } 61 | if(aContact == NULL && liSetting.Create_New_Contact_record__c) { // contact doesn't exist and lithium setting asks to create one 62 | if(aCommunityUser != null && aCommunityUser.name != null && !aCommunityUser.name.Startswith('a4m')){ 63 | aContact = new Contact(); // No contact exists; create new record 64 | aContact.ownerid = '005C0000006hBOnIAM'; 65 | aContact.FirstName = aCommunityUser.First_Name__c; 66 | aContact.LastName = (aCommunityUser.Last_Name__c == NULL)?aCommunityUser.Name:aCommunityUser.Last_Name__c; // LastName is required for Contact; making sure it is not empty 67 | aContact.Email = aCommunityUser.Email_Address__c; 68 | aContact.LithiumId__c = aCommunityUser.Lithium_User_Id__c; 69 | contactsToInsert.add(aContact); // list of contacts to update/insert 70 | listOfCommunityUsers.add(aCommunityUser); // maintain a list of community users also; same order as contactsToInsert 71 | } 72 | } 73 | } 74 | //Comment after first run 75 | /*if(lstCon != null && lstCon.size() > 0) 76 | { 77 | update lstCon; 78 | }*/ 79 | //Comment ends 80 | if(!contactsToInsert.isEmpty()) { 81 | try{ 82 | Database.SaveResult[] iResults = Database.insert(contactsToInsert, false); // insert/update contacts; continue DML operations if a failure occurs 83 | Integer i = 0; 84 | for(Database.SaveResult result : iResults) { 85 | // Database.SaveResult[] in same order as contactsToInsert 86 | // Safe to assume same index for community users list 87 | if(!result.isSuccess()) { // error occured in contact update/insert 88 | system.debug('error updating ' + result.getErrors()[0].getMessage()); 89 | listOfCommunityUsers[i].addError('Error creating/updating Contact'); // add error to community user object; 90 | } 91 | else if(liSetting.Sync_With_Contact__c){ // attach new Contact record or not 92 | listOfCommunityUsers[i].Contact__c = result.getId(); // assign contact id; this is not available before contact is inserted, hence done here 93 | } 94 | ++i; 95 | } 96 | }catch(Exception e){ 97 | system.debug('Catched the exception while inserting/updating contact'); 98 | } 99 | 100 | } 101 | } -------------------------------------------------------------------------------- /src/classes/GoingAsync4.cls: -------------------------------------------------------------------------------- 1 | public without sharing class GoingAsync4 2 | implements queueable, Database.AllowsCallouts { 3 | 4 | // Version of execute at the end of chapter 7 5 | /* 6 | public void execute(QueueableContext context) 7 | { 8 | if(!AppConfigSupport.appEnabled) return; // On/off switch 9 | List requests; 10 | try 11 | { 12 | requests = [Select ID, AsyncType__c, Params__c 13 | from AsyncRequest__c 14 | where Error__c = false And 15 | CreatedById = :UserInfo.getUserId() 16 | Limit 1 for update]; 17 | } 18 | catch(Exception ex) { return; } 19 | if(requests.size()==0 ) return; 20 | 21 | AsyncRequest__c currentRequest = requests[0]; 22 | 23 | try 24 | { 25 | if(currentRequest.AsyncType__c=='Translate Solution') 26 | translate(currentRequest); 27 | 28 | // Add more here 29 | 30 | delete currentRequest; 31 | // Optional 32 | database.emptyRecycleBin(new List{currentRequest.id}); 33 | 34 | } 35 | catch(Exception ex) 36 | { 37 | currentRequest.Error__c = true; 38 | currentRequest.Error_Message__c = ex.getMessage(); 39 | update currentRequest; 40 | } 41 | 42 | List moreRequests = [Select ID, AsyncType__c, Params__c 43 | from AsyncRequest__c 44 | where Error__c = false 45 | and ID <> :currentRequest.id 46 | and CreatedById = :UserInfo.getUserId() 47 | Limit 1 ]; 48 | 49 | if(moreRequests.size()==0) return; 50 | 51 | try 52 | { 53 | enqueGoingAsync4(context.getJobId()); 54 | } 55 | catch(Exception ex) 56 | { 57 | tryToQueue(); 58 | } 59 | 60 | } 61 | */ 62 | 63 | // Version of execute at the end of chapter 8 64 | public void execute(QueueableContext context) 65 | { 66 | if(!AppConfigSupport.appEnabled) return; // On/off switch 67 | List requests; 68 | try 69 | { 70 | requests = [Select ID, AsyncType__c, Params__c, 71 | NewAmount__c, OriginalAmount__c, TargetOpportunity__c 72 | from AsyncRequest__c 73 | where Error__c = false And 74 | CreatedById = :UserInfo.getUserId() 75 | Limit 1 for update]; 76 | } 77 | catch(Exception ex) { return; } 78 | if(requests.size()==0 ) return; 79 | 80 | AsyncRequest__c currentRequest = requests[0]; 81 | 82 | try 83 | { 84 | if(currentRequest.AsyncType__c=='Translate Solution') 85 | translate(currentRequest); 86 | 87 | if(currentRequest.AsyncType__c=='Amount Update') 88 | updateAmounts(currentRequest); 89 | 90 | // Add more here 91 | 92 | delete currentRequest; 93 | // Optional 94 | database.emptyRecycleBin(new List{currentRequest.id}); 95 | 96 | } 97 | catch(Exception ex) 98 | { 99 | currentRequest.Error__c = true; 100 | currentRequest.Error_Message__c = ex.getMessage(); 101 | update currentRequest; 102 | } 103 | 104 | List moreRequests = [Select ID, AsyncType__c, Params__c 105 | from AsyncRequest__c 106 | where Error__c = false 107 | and ID <> :currentRequest.id 108 | and CreatedById = :UserInfo.getUserId() 109 | Limit 1 ]; 110 | 111 | if(moreRequests.size()==0) return; 112 | 113 | try 114 | { 115 | enqueueGoingAsync4(context.getJobId()); 116 | } 117 | catch(Exception ex) 118 | { 119 | tryToQueue(); 120 | } 121 | 122 | } 123 | 124 | public static void enqueueGoingAsync4(ID currentJobId) 125 | { 126 | List jobs = [Select ID, Status, ExtendedStatus from AsyncApexJob 127 | where JobType = 'Queueable' And (status='Queued' Or Status='Holding') 128 | and CreatedById = :userinfo.getUserID() and 129 | ApexClass.Name='GoingAsync4' and ID!= :currentJobId Limit 1 ]; 130 | if(jobs.size()==1) return; // Already have one queued that isn't this one. 131 | 132 | system.enqueueJob(new GoingAsync4()); 133 | } 134 | 135 | 136 | @future 137 | private static void tryToQueue() 138 | { 139 | if(!AppConfigSupport.appEnabled) return; // On/off switch 140 | try 141 | { 142 | if(Limits.getLimitQueueableJobs() - Limits.getQueueableJobs() > 0) 143 | enqueueGoingAsync4(null); 144 | } 145 | catch(Exception ex) 146 | { 147 | // Wait for someone else to make a request... 148 | // Or maybe use scheduled Apex? 149 | } 150 | } 151 | 152 | public void translate(AsyncRequest__c request) 153 | { 154 | Integer allowedCallouts = 155 | Limits.getLimitCallouts() - Limits.getCallouts(); 156 | if(allowedCallouts<=0) return; 157 | 158 | List idsAfterSplit = request.Params__c.split(','); 159 | 160 | List solutionsToUpdate = 161 | [SELECT ID, SolutionNote, SolutionSpanish__c 162 | from Solution 163 | where ID in :idsAfterSplit 164 | LIMIT :allowedCallouts]; 165 | for(Solution sl: solutionsToUpdate) 166 | { 167 | sl.SolutionSpanish__c = 168 | SimulatedTranslator.translate(sl.SolutionNote); 169 | sl.TranslationPending__c = false; 170 | } 171 | update solutionsToUpdate; 172 | } 173 | 174 | public void updateAmounts(AsyncRequest__c request) 175 | { 176 | List ops = 177 | [Select ID, Amount from Opportunity 178 | where ID = :request.TargetOpportunity__c for update]; 179 | if(ops.size()==0) return; // The op may have been deleted 180 | Opportunity op = ops[0]; 181 | 182 | // Implement update scneario here 183 | 184 | // Option #1 185 | //op.Amount = request.NewAmount__c; 186 | 187 | // Option #2 188 | op.Amount += (request.NewAmount__c - 189 | request.OriginalAmount__c); 190 | 191 | // Option #3 192 | if(op.Amount!= request.OriginalAmount__c) 193 | { 194 | // Concurrency error - throw an exception here 195 | throw new AsyncUpdateException( 196 | 'Amount on opportunity update has changed'); 197 | } 198 | 199 | try 200 | { 201 | update op; 202 | } 203 | catch(DmlException dex) 204 | { 205 | if(dex.getDmlType(0) == StatusCode.UNABLE_TO_LOCK_ROW) 206 | { 207 | insert request.clone(); 208 | return; 209 | } 210 | throw dex; 211 | } 212 | // Any other exception will not be caught 213 | 214 | } 215 | 216 | public class AsyncUpdateException extends Exception {} 217 | 218 | } -------------------------------------------------------------------------------- /src/classes/TestBulkPatterns.cls: -------------------------------------------------------------------------------- 1 | @istest 2 | public class TestBulkPatterns { 3 | 4 | static testMethod void bulkOpportunityTest() { 5 | List ops = new List(); 6 | initTestObjects(ops, 100, 20, 20, 40); 7 | 8 | Test.StartTest(); 9 | for(Opportunity op: ops) op.StageName = 'Qualification'; 10 | update ops; 11 | Test.StopTest(); 12 | 13 | validateOCRs(ops); 14 | 15 | } 16 | 17 | static testMethod void createTaskTest() 18 | { 19 | Integer numberOfOpportunities = 100; 20 | List ops = new List(); 21 | for(Integer x=0; x tasks = 37 | [SELECT ID, OwnerID, WhatID, Status, Subject, Type 38 | from Task 39 | where OwnerID = :UserInfo.getUserID() 40 | And Type='Other' And IsClosed = False 41 | And Subject = 'Assign Primary Contact' ]; 42 | system.assertEquals(NumberOfOpportunities, tasks.size()); 43 | 44 | } 45 | 46 | 47 | 48 | // Prepare the specified number of opportunities, with contact roles on each. 49 | // The contact roles are distributed evenly among the number of contacts specified. 50 | public static void initTestObjects(List newOpportunities, 51 | Integer numberOfOpportunities, Integer numberOfOtherOpportunities, 52 | Integer contactRolesPerOp, Integer numberOfContacts) 53 | { 54 | if(numberOfContacts < contactRolesPerOp) 55 | numberOfContacts = contactRolesPerOp; 56 | 57 | Listcts = new List(); 58 | for(Integer x=0;x otherOpportunities = new List(); 78 | for(Integer x=0; x ocrList = new List(); 92 | Integer contactNumber = 0; 93 | for(Opportunity op: otherOpportunities) 94 | { 95 | for(Integer ocrNumber = 0; ocrNumber < contactRolesPerOp; ocrNumber++) 96 | { 97 | ocrList.add( 98 | new OpportunityContactRole(OpportunityID = op.id, 99 | ContactID = cts[contactNumber].id)); 100 | contactNumber++; 101 | if(contactNumber >= numberOfContacts) contactNumber = 0; 102 | } 103 | 104 | } 105 | insert ocrList; 106 | } 107 | 108 | public static void validateOCRs(List ops) 109 | { 110 | // Get map for IDs 111 | Map opMap = new Map(ops); 112 | 113 | // Query for primary Contacts 114 | List ocrs = 115 | [SELECT ID, OpportunityID from OpportunityContactRole 116 | where OpportunityID in :opMap.keyset() 117 | And IsPrimary= true]; 118 | 119 | // Create set of opportunity IDs with primary contacts 120 | Set opportunitiesWithPrimaryContact = new Set(); 121 | for(OpportunityContactRole ocr: ocrs) 122 | opportunitiesWithPrimaryContact.add(ocr.OpportunityID); 123 | 124 | // Now make sure every opportunity has a primary contact role 125 | for(Opportunity op: ops) 126 | System.Assert(opportunitiesWithPrimaryContact.contains(op.id)); 127 | 128 | List opResults = 129 | [SELECT ID, 130 | (SELECT ID from OpportunityContactRoles 131 | where IsPrimary = true) 132 | from opportunity where ID in :opmap.keyset() ]; 133 | 134 | for(Opportunity op: opResults) 135 | System.Assert(op.OpportunityContactRoles.size()==1); 136 | } 137 | 138 | /* 139 | These are a couple of tests used while writing the book to identify the 140 | time cost of iterating over a subquery collection 141 | static testMethod void benchMarkTest1() { 142 | List ops = new List(); 143 | initTestObjects(ops, 100, 20, 20, 40); 144 | 145 | List allOCRS = [Select ID, OpportunityID, ContactID from OpportunityContactRole]; 146 | 147 | Test.StartTest(); 148 | List opportunities = 149 | [Select ID ,(Select ID, ContactID, IsPrimary 150 | from OpportunityContactRoles) from Opportunity]; 151 | for(Opportunity op: opportunities) 152 | { 153 | for(OpportunityContactRole ocr: op.OpportunityContactRoles) 154 | { 155 | ID ctid = ocr.ContactID; 156 | } 157 | } 158 | 159 | Test.StopTest(); 160 | 161 | } 162 | 163 | static testMethod void benchMarkTest2() { 164 | List ops = new List(); 165 | initTestObjects(ops, 100, 20, 20, 40); 166 | List allOCRS = [Select ID, OpportunityID, ContactID from OpportunityContactRole]; 167 | 168 | Test.StartTest(); 169 | 170 | Map> opportunitiesMap = 171 | new Map>(); 172 | 173 | for(OpportunityContactRole ocr: allOCRS) 174 | { 175 | ID opid = ocr.OpportunityID; // Use temp variable for speed 176 | if(!opportunitiesMap.containsKey(opid)) 177 | opportunitiesMap.put(opid, new List()); 178 | opportunitiesMap.get(opid).add(ocr); 179 | } 180 | 181 | 182 | for(ID opid: opportunitiesMap.keyset()) 183 | { 184 | for(OpportunityContactRole ocr: opportunitiesMap.get(opid)) 185 | { 186 | ID ctid = ocr.ContactID; 187 | } 188 | } 189 | 190 | Test.StopTest(); 191 | 192 | } 193 | */ 194 | 195 | } -------------------------------------------------------------------------------- /src/classes/FunWithCollections.cls: -------------------------------------------------------------------------------- 1 | @istest 2 | public class FunWithCollections { 3 | 4 | static testMethod void quickSet() 5 | { 6 | Listcts = new List(); 7 | for(Integer x=0;x<5;x++) 8 | { 9 | cts.add(new Contact(LastName = 'cttest_' + String.valueOf(x))); 10 | } 11 | 12 | insert cts; 13 | 14 | Set contactIds = new Set(); 15 | for(Contact ct: cts) contactIds.add(ct.id); 16 | 17 | List tasks = 18 | [Select ID from Task 19 | where Whoid in :contactIds Limit 500]; 20 | 21 | Map contactMap = new Map(cts); 22 | 23 | List tasks2 = 24 | [Select ID from Task 25 | where Whoid in :contactMap.keyset() Limit 500]; 26 | 27 | } 28 | 29 | 30 | 31 | static testMethod void collectionGroup() 32 | { 33 | Listcts = new List(); 34 | for(Integer x=0;x<50;x++) 35 | { 36 | cts.add(new Contact(LastName = 'cttest_' + String.valueOf(x))); 37 | } 38 | 39 | insert cts; 40 | 41 | List newTasks = new List(); 42 | for(Integer x=0; x<50; x++) 43 | { 44 | newTasks.add( 45 | new Task(WhoID = cts[x].id, 46 | ActivityDate = Date.Today().addDays(x), 47 | Description='some task', Type='Call')); 48 | } 49 | insert newTasks; 50 | 51 | // cts is the list of input contacts 52 | Map contactMap = new Map(cts); 53 | 54 | List tasks = 55 | [Select ID, ActivityDate, Description 56 | from Task where Whoid in :contactMap.keyset() Order 57 | By ActivityDate Desc Limit 500]; 58 | 59 | Map> tasksByWeek = new Map>(); 60 | 61 | for(Task t: tasks) 62 | { 63 | // Perform global task operation here 64 | 65 | 66 | // Group by week 67 | Date weekStart = t.ActivityDate.toStartOfWeek(); 68 | if(tasksByWeek.get(weekStart)==null) 69 | tasksByWeek.put(weekStart, new List()); 70 | tasksByWeek.get(weekStart).add(t); 71 | 72 | // Perform week related operation here 73 | } 74 | 75 | system.debug(tasksByWeek); 76 | 77 | 78 | } 79 | 80 | 81 | 82 | static testMethod void caseSensitivity() 83 | { 84 | Map intMap = new Map{'A'=>0, 'b'=>1, 'C'=>2}; 85 | system.assert(!intMap.containskey('a')); 86 | system.assert(!intMap.containskey('B')); 87 | 88 | } 89 | 90 | static testMethod void caseOnDescribe() { 91 | // Get global describe 92 | Map gd = Schema.getGlobalDescribe(); 93 | 94 | System.Assert(gd.ContainsKey('CampaignMember')); 95 | System.Assert(gd.ContainsKey('campaignmember')); 96 | System.Assert(gd.ContainsKey('CAMPAIGNMEMBER')); 97 | system.debug(gd); 98 | 99 | System.Assert(gd.keyset().Contains('campaignmember')); 100 | System.Assert(!gd.keyset().Contains('CampaignMember')); 101 | System.Assert(!gd.keyset().Contains('CAMPAIGNMEMBER')); 102 | 103 | } 104 | 105 | static testMethod void objectKeys() 106 | { 107 | Listcts = new List(); 108 | for(Integer x=0;x<5;x++) 109 | { 110 | cts.add(new Contact(LastName = 'cttest_' + String.valueOf(x))); 111 | } 112 | 113 | insert cts; 114 | 115 | // Create a map keyed on contacts 116 | Map contactMap = new Map(); 117 | 118 | for(Integer x = 0; x< 5; x++) 119 | { 120 | contactMap.put(cts[x], x); 121 | } 122 | 123 | system.assertEquals(contactMap.size(),5); 124 | 125 | // Create another list to reference these 126 | List sameContacts = new List(cts); 127 | 128 | for(Integer x = 0; x< 5; x++) 129 | { 130 | samecontacts[x].AssistantName = 'person' + string.ValueOf(x); 131 | system.assertEquals(cts[x].AssistantName ,sameContacts[x].AssistantName); 132 | system.assertNotEquals(contactMap.get(cts[x]), x); 133 | contactMap.put(sameContacts[x], x); 134 | } 135 | system.assertNotEquals(contactMap.size(),5); 136 | 137 | 138 | } 139 | 140 | static testMethod void objectSets() 141 | { 142 | Listcts = new List(); 143 | for(Integer x=0;x<5;x++) 144 | { 145 | cts.add(new Contact(LastName = 'cttest_' + String.valueOf(x))); 146 | } 147 | 148 | insert cts; 149 | 150 | // Create a map keyed on contacts 151 | Set contactSet = new Set(); 152 | 153 | for(Integer x = 0; x< 5; x++) 154 | { 155 | contactSet.add(cts[x]); 156 | } 157 | 158 | system.assertEquals(contactSet.size(),5); 159 | 160 | // Create another list to reference these 161 | List sameContacts = new List(cts); 162 | 163 | for(Integer x = 0; x< 5; x++) 164 | { 165 | sameContacts[x].AssistantName = 'person' + string.ValueOf(x); 166 | system.assertEquals(cts[x].AssistantName ,sameContacts[x].AssistantName); 167 | system.assert(!contactSet.contains(cts[x])); 168 | contactSet.add(sameContacts[x]); 169 | } 170 | system.assertNotEquals(contactSet.size(),5); 171 | 172 | 173 | } 174 | 175 | static testMethod void objectKeysCorrect() 176 | { 177 | Listcts = new List(); 178 | for(Integer x=0;x<5;x++) 179 | { 180 | cts.add(new Contact(LastName = 'cttest_' + String.valueOf(x))); 181 | } 182 | 183 | insert cts; 184 | 185 | // Create a map keyed on contacts 186 | Map contactMap = new Map(); 187 | 188 | for(Integer x = 0; x< 5; x++) 189 | { 190 | contactMap.put(cts[x].id, x); 191 | } 192 | 193 | system.assertEquals(contactMap.size(),5); 194 | 195 | // Create another list to reference these 196 | List sameContacts = new List(cts); 197 | 198 | for(Integer x = 0; x< 5; x++) 199 | { 200 | sameContacts[x].AssistantName = 'person' + string.ValueOf(x); 201 | system.assertEquals(cts[x].AssistantName ,sameContacts[x].AssistantName); 202 | system.assertEquals(contactMap.get(cts[x].id), x); 203 | contactMap.put(sameContacts[x].id, x); 204 | } 205 | system.assertEquals(contactMap.size(),5); 206 | 207 | 208 | } 209 | 210 | static testMethod void LimitUpdates() 211 | { 212 | Listcts = new List(); 213 | for(Integer x=0;x<50;x++) 214 | { 215 | cts.add(new Contact(LastName = 'cttest_' + String.valueOf(x))); 216 | } 217 | 218 | insert cts; 219 | 220 | Map contactsToUpdate = new Map(); 221 | 222 | // First set of operations 223 | for(Contact ct: cts) 224 | { 225 | // Do various operations 226 | // If an update is needed: 227 | contactsToUpdate.put(ct.id, ct); 228 | 229 | } 230 | 231 | // Second set of operations 232 | for(Contact ct: cts) 233 | { 234 | // Do various operations 235 | // If an update is needed: 236 | contactsToUpdate.put(ct.id, ct); 237 | 238 | } 239 | 240 | if(contactsToUpdate.size()>0) update contactsToUpdate.values(); 241 | 242 | 243 | } 244 | 245 | } -------------------------------------------------------------------------------- /src/classes/DiagnosticsTriggers1.cls: -------------------------------------------------------------------------------- 1 | public class DiagnosticsTriggers1 Implements DiagnosticsMain.ITriggerEntry { 2 | 3 | public void mainEntry(String triggerObject, Boolean isBefore, 4 | Boolean isDelete, Boolean isAfter, Boolean isInsert, 5 | Boolean isUpdate, Boolean isExecuting, 6 | List newList, Map newMap, 7 | List oldList, Map oldMap) 8 | { 9 | DiagnosticsInstrumentation.push('DiagnosticsTriggers1.MainEntry'); 10 | afterUpdateOpportunityBetterQueries2((List)newList, 11 | (Map) newMap, (Map) oldMap, 12 | IsInsert, IsUpdate); 13 | DiagnosticsInstrumentation.pop(); 14 | } 15 | public void inProgressEntry(String triggerObject, Boolean isBefore, 16 | Boolean isDelete, Boolean isAfter, Boolean isInsert, 17 | Boolean isUpdate, Boolean isExecuting, 18 | List newList, Map newMap, 19 | List oldList, Map oldMap) 20 | { 21 | // Ignore triggers within triggers 22 | } 23 | 24 | 25 | public static void afterUpdateOpportunityBetterQueries2( 26 | List newList, Map newMap, 27 | Map oldMap, Boolean isInsert, Boolean isUpdate) 28 | { 29 | // Pattern 5 - Implementation with SOQL aggregates #2 30 | 31 | DiagnosticsInstrumentation.Push( 32 | 'DiagnosticsTriggers1.AfterUpdateOpportunityBetterQueries2'); 33 | 34 | 35 | Set opportunityIDsWithStagenameChanges = new Set(); 36 | 37 | // Get OpportunityContactRoles 38 | for(Opportunity op: newList) 39 | { 40 | if(op.StageName != oldMap.get(op.id).StageName) 41 | opportunityIDsWithStagenameChanges.add(op.id); 42 | } 43 | 44 | // Quick exit if no processing required 45 | if(opportunityIDsWithStagenameChanges.size()==0) 46 | { 47 | DiagnosticsInstrumentation.Pop(); 48 | return; 49 | } 50 | 51 | 52 | // Query for all related OpportunityContactRole 53 | List ocrs = 54 | [Select ID, ContactID, IsPrimary, OpportunityID 55 | from OpportunityContactRole 56 | where OpportunityID in :opportunityIDsWithStagenameChanges]; 57 | 58 | // Look for primary, or for no OCR on opportunities 59 | Set opsWithNoPrimaryWithContactRoles = opportunityIDsWithStagenameChanges.Clone(); 60 | Set opsWithNoContactRoles = opportunityIDsWithStagenameChanges.Clone(); 61 | 62 | 63 | for(OpportunityContactRole ocr: ocrs) 64 | { 65 | if(ocr.IsPrimary) opsWithNoPrimaryWithContactRoles.remove(ocr.OpportunityID); 66 | opsWithNoContactRoles.remove(ocr.OpportunityID); 67 | } 68 | opsWithNoPrimaryWithContactRoles.RemoveAll(opsWithNoContactRoles); 69 | 70 | // First deal with any opportunities without contact roles 71 | if(opsWithNoContactRoles.size()>0) 72 | { 73 | // Find out which ones have existing tasks 74 | List tasks = 75 | [SELECT ID, OwnerID, WhatID, Status, Subject, Type 76 | from Task where Type='Other' 77 | And WhatID in :opsWithNoContactRoles And IsClosed = False 78 | And Subject = 'Assign Primary Contact' ]; 79 | 80 | // Don't loop through opportunities - waste of script lines. Loop through tasks to build set of IDs with tasks 81 | Set opsWithoutTasks = opsWithNoContactRoles.clone(); 82 | for(Task t: tasks) 83 | { 84 | Opportunity op = newMap.get(t.WhatID); // Get the opportunity 85 | // Make sure it's assigned to the right person 86 | if(t.OwnerID == op.OwnerID) opsWithoutTasks.remove(op.ID); 87 | } 88 | // Now create new tasks 89 | List newTasks = new List(); 90 | for(ID opid: opsWithoutTasks) 91 | { 92 | Opportunity op = newMap.get(opid); 93 | newTasks.add(new Task(OwnerID = op.OwnerID, Type='Other', 94 | WhatID = op.ID, Subject = 'Assign Primary Contact', 95 | ActivityDate = Date.Today().AddDays(3) )); 96 | } 97 | if(newTasks.size()>0) insert newTasks; 98 | 99 | } 100 | if(opsWithNoPrimaryWithContactRoles.size()>0) 101 | { 102 | // Get a list of the contacts 103 | List contactIdsForOps = new List(); 104 | for(OpportunityContactRole ocr: ocrs) 105 | { 106 | if(opsWithNoPrimaryWithContactRoles.contains(ocr.OpportunityID)) 107 | contactIdsForOps.add(ocr.ContactID); 108 | } 109 | 110 | // Now get the totals count and primary count for each contact by 111 | // using aggregate functions and grouping by contact 112 | List ocrsByContact = 113 | [Select ContactID, Count(ID) total 114 | from OpportunityContactRole 115 | where ContactID in :contactIdsForOps 116 | Group By ContactID]; 117 | List primaryOcrsByContact = 118 | [Select ContactID, Count(ID) total 119 | from OpportunityContactRole where IsPrimary=true 120 | and ContactID in :contactIdsForOps Group By ContactID]; 121 | 122 | // Let's get the totals by contact for faster loop 123 | Map totalsByContact = new Map(); 124 | Map primaryByContact = new Map(); 125 | for(AggregateResult ar: ocrsByContact) 126 | totalsByContact.put((ID)ar.get('ContactID'), 127 | Integer.ValueOf(ar.get('total'))); 128 | for(AggregateResult ar: primaryOcrsByContact) 129 | primaryByContact.put((ID)ar.get('ContactID'), 130 | Integer.ValueOf(ar.get('total'))); 131 | 132 | // Instead of requerying opportunties with a subquery of contact roles 133 | // Build a map from opportunity ID to related contact roles 134 | // for opportunties without primary contact roles 135 | Map> opportunitiesWithoutPrimary = 136 | new Map>(); 137 | for(OpportunityContactRole ocr: ocrs) 138 | { 139 | ID opid = ocr.OpportunityID; // Use temp variable for speed 140 | if(opsWithNoPrimaryWithContactRoles.contains(opid)) 141 | { 142 | if(!opportunitiesWithoutPrimary.containsKey(opid)) 143 | opportunitiesWithoutPrimary.put(opid, new List()); 144 | opportunitiesWithoutPrimary.get(opid).add(ocr); 145 | } 146 | } 147 | 148 | 149 | List ocrsToUpdate = 150 | new List(); 151 | 152 | for(ID opid: opportunitiesWithoutPrimary.keyset()) 153 | { 154 | OpportunityContactRole bestOcr = null; 155 | Integer primaryCount = 0; 156 | Integer totalCount = 0; 157 | for(OpportunityContactRole opOcrs: opportunitiesWithoutPrimary.get(opid)) 158 | { 159 | if(bestOcr==null || 160 | primaryByContact.get(opOcrs.contactid) > primaryCount || 161 | (primaryByContact.get(opOcrs.contactid) == totalCount && 162 | totalsByContact.get(opocrs.contactId) > totalCount )) 163 | primaryCount = primaryByContact.get(opocrs.Contactid); 164 | totalCount = totalsByContact.get(opocrs.Contactid); 165 | bestOcr = opOcrs; 166 | } 167 | bestOcr.IsPrimary = true; 168 | ocrsToUpdate.add(bestOcr); 169 | } 170 | update ocrsToUpdate; 171 | } 172 | DiagnosticsInstrumentation.pop(); 173 | } 174 | 175 | } -------------------------------------------------------------------------------- /src/classes/TestDiagnostics2.cls: -------------------------------------------------------------------------------- 1 | @istest 2 | public class TestDiagnostics2 { 3 | 4 | static testMethod void updateOpportunityTest() { 5 | List ops = new List(); 6 | initTestObjects(ops, 100, 20, 20, 40); 7 | 8 | DiagnosticsInstrumentation.Debug('Starting testing: bulkOpportunityTest'); 9 | Test.StartTest(); 10 | for(Opportunity op: ops) op.StageName = 'Qualification'; 11 | update ops; 12 | Test.StopTest(); 13 | 14 | validateOCRs(ops); 15 | 16 | } 17 | 18 | @istest(oninstall=true seealldata=false) 19 | static void createTaskTestSingle() 20 | { 21 | createTaskTest(1); 22 | } 23 | 24 | @istest(oninstall=false seealldata=false) 25 | static void createTaskTestBulk() 26 | { 27 | createTaskTest(100); 28 | } 29 | 30 | static void createTaskTest(Integer numberOfOpportunities) 31 | { 32 | List ops = 33 | createOpportunities('optest_', numberOfOpportunities); 34 | for(Opportunity op: ops) 35 | { 36 | op.CloseDate = Date.Today().addDays(5); 37 | op.StageName = 'Prospecting'; 38 | } 39 | insert ops; 40 | 41 | diagnosticsInstrumentation.Debug( 42 | 'Starting testing: CreateTastTest'); 43 | Test.StartTest(); 44 | for(Opportunity op: ops) op.StageName = 'Qualification'; 45 | update ops; 46 | Test.StopTest(); 47 | 48 | List tasks = 49 | [SELECT ID, OwnerID, WhatID, Status, Subject, Type 50 | from Task where OwnerID = :UserInfo.getUserID() 51 | And Type='Other' And IsClosed = False 52 | And Subject = 'Assign Primary Contact' ]; 53 | 54 | system.assertEquals(numberOfOpportunities, tasks.size()); 55 | 56 | } 57 | 58 | private static final Integer numberOfStageUpdateOpportunities = 5; 59 | 60 | static testMethod void testTaskCount() { 61 | 62 | if(isTestDisabled(1)) return; 63 | 64 | List ops = createOpportunities( 65 | 'optest_', numberOfStageUpdateOpportunities); 66 | 67 | for(Opportunity op: ops) 68 | { 69 | op.CloseDate = Date.Today().addDays(5); 70 | op.StageName = 'Prospecting' ; 71 | } 72 | insert ops; 73 | 74 | for(Opportunity op: ops) 75 | { 76 | op.StageName = 'Negotiation/Review'; 77 | } 78 | 79 | DiagnosticsInstrumentation.Debug('Starting testing: testTaskCount'); 80 | Test.StartTest(); 81 | update ops; 82 | Test.StopTest(); 83 | 84 | Map opMap = new Map(ops); 85 | 86 | List tasks = 87 | [Select ID, WhatID from Task 88 | where WhatID in :opmap.keyset() 89 | And Subject ='Opportunity stage update']; 90 | 91 | //System.AssertEquals(numberOfStageUpdateOpportunities, tasks.size()); 92 | System.AssertEquals(numberOfStageUpdateOpportunities, tasks.size(), 93 | 'Error in TestTaskCount. Stacktrace: ' + 94 | DiagnosticsInstrumentation.exceptionTrace); 95 | 96 | } 97 | 98 | static testmethod void testFakeException() 99 | { 100 | DiagnosticsMain.fakeException = true; 101 | DiagnosticsInstrumentation.diagnosticsEnabled = true; 102 | List ops = 103 | createOpportunities('optest_', numberOfStageUpdateOpportunities); 104 | 105 | for(Opportunity op: ops) 106 | { 107 | op.CloseDate = Date.Today().addDays(5); 108 | op.StageName = 'Prospecting' ; 109 | } 110 | Test.StartTest(); 111 | insert ops; 112 | Test.StopTest(); 113 | 114 | List dbg = [Select ID from DebugInfo__c]; 115 | system.assert(dbg.size()>0); 116 | 117 | } 118 | 119 | // Prepare the specified number of opportunities, with contact roles on each. 120 | // The contact roles are distributed evenly among the number of contacts specified. 121 | public static void initTestObjects(List newOpportunities, 122 | Integer numberOfOpportunities, Integer numberOfOtherOpportunities, 123 | Integer contactRolesPerOp, Integer numberOfContacts) 124 | { 125 | if(numberOfContacts < contactRolesPerOp) 126 | numberOfContacts = contactRolesPerOp; 127 | 128 | Listcts = createContacts('cttest_', numberOfContacts); 129 | 130 | insert cts; 131 | 132 | newopportunities = createOpportunities( 133 | 'optest_', numberOfOpportunities); 134 | for(Opportunity op: newOpportunities) 135 | { 136 | op.CloseDate = Date.Today().addDays(5); 137 | op.StageName = 'Prospecting'; 138 | } 139 | 140 | // Insert the test opportunities 141 | insert newOpportunities; 142 | 143 | List otherOpportunities = 144 | createOpportunities('optest2_', numberOfOtherOpportunities); 145 | for(Opportunity op:otherOpportunities) 146 | { 147 | op.CloseDate = Date.Today().addDays(5); 148 | op.StageName = 'Prospecting'; 149 | } 150 | 151 | insert otherOpportunities; 152 | // Combine the two for creating OpportunityContactRoles 153 | otherOpportunities.addall(newOpportunities); 154 | 155 | // Now insert contact roles 156 | List ocrList = new List(); 157 | Integer contactNumber = 0; 158 | for(Opportunity op: otherOpportunities) 159 | { 160 | for(Integer ocrNumber = 0; ocrNumber < contactRolesPerOp; ocrNumber++) 161 | { 162 | ocrList.add( 163 | new OpportunityContactRole(OpportunityID = op.id, 164 | ContactID = cts[contactNumber].id)); 165 | contactNumber++; 166 | if(contactNumber >= numberOfContacts) contactNumber = 0; 167 | } 168 | 169 | } 170 | insert ocrList; 171 | } 172 | 173 | public static void validateOCRs(List ops) 174 | { 175 | // Get map for IDs 176 | Map opMap = new Map(ops); 177 | 178 | List opresults = 179 | [SELECT ID, (SELECT ID from OpportunityContactRoles 180 | where IsPrimary = true) from opportunity 181 | where ID in :opmap.keyset() ]; 182 | for(Opportunity op: opresults) 183 | System.Assert(op.OpportunityContactRoles.size()==1); 184 | } 185 | 186 | public static List createOpportunities 187 | (String baseName, Integer count) 188 | { 189 | List results = new List(); 190 | for(Integer x = 0; x< count; x++) 191 | { 192 | //results.add(new Opportunity(Name = baseName + String.valueOf(x) )); 193 | Opportunity op = (Opportunity)Opportunity.sObjectType.newSObject(null, true); 194 | op.Name = baseName + String.valueOf(x); 195 | results.add(op); 196 | } 197 | setDefaultFields('Opportunity', results); 198 | return results; 199 | 200 | } 201 | 202 | public static List createContacts(String baseName, Integer count) 203 | { 204 | List results = new List(); 205 | 206 | for(Integer x = 0; x< count; x++) 207 | { 208 | //results.add(new Contact(LastName = baseName + String.valueOf(x) )); 209 | Contact ct = (Contact)Contact.sObjectType.newSObject(null, true); 210 | ct.LastName = baseName + String.valueOf(x); 211 | results.add(ct); 212 | } 213 | 214 | return results; 215 | 216 | } 217 | 218 | public static List createAccounts(String baseName, Integer count) 219 | { 220 | List results = new List(); 221 | 222 | for(Integer x = 0; x< count; x++) 223 | { 224 | //results.add(new Account(Name = baseName + String.valueOf(x) )); 225 | Account act = (Account)Account.sObjectType.newSObject(null, true); 226 | act.Name = baseName + String.valueOf(x); 227 | results.add(act); 228 | } 229 | 230 | return results; 231 | 232 | } 233 | 234 | 235 | public static Boolean isTestDisabled(Integer testNumber) 236 | { 237 | List resources = 238 | [Select Body from StaticResource 239 | where Name = 'Apx1Predeployment' ]; 240 | if(resources.size()==0) return false; 241 | String contents = resources[0].Body.ToString(); 242 | if(contents==null) return false; 243 | List lines = contents.split('\\n'); 244 | for(String line:lines) 245 | { 246 | List entries = line.split(':'); 247 | system.debug('entries ' + entries); 248 | try 249 | { 250 | if(entries[0]=='disable' && 251 | Integer.valueOf(entries[1].trim())== testnumber) 252 | return true; 253 | } 254 | catch(Exception ex){} 255 | } 256 | return false; 257 | } 258 | 259 | public static Boolean setDefaultFields(String objectType, List theObjects) 260 | { 261 | List resources = 262 | [Select Body from StaticResource 263 | where Name = 'Apx1Predeployment' ]; 264 | if(resources.size()==0) return false; 265 | String contents = resources[0].Body.ToString(); 266 | if(contents==null) return false; 267 | List lines = contents.split('\\n'); 268 | for(String line:lines) 269 | { 270 | List entries = line.split(':'); 271 | try 272 | { 273 | if(entries[0]==objectType) 274 | { 275 | List fieldinfo = entries[1].split('='); 276 | for(SObject obj: theObjects) 277 | { 278 | // Implemented only for strings 279 | obj.put(fieldinfo[0], fieldinfo[1]); 280 | } 281 | } 282 | } 283 | catch(Exception ex){} 284 | } 285 | return false; 286 | } 287 | 288 | } -------------------------------------------------------------------------------- /CommunityUserBeforeTriggerCreateLead (1).tgr: -------------------------------------------------------------------------------- 1 | trigger CommunityUserBeforeTriggerCreateLead on Li_Community_User1__c (before insert, before update) { 2 | 3 | Map emailToCommunityUserMap = new Map(); 4 | Map emailToLeadMap = new Map(); 5 | Map emailToContactMap = new Map(); 6 | List listOfCommunityUsers = new List(); 7 | List leadsToInsert = new List(); 8 | LithiumSettings1__c liSetting = LithiumSettings1__c.getValues('DefaultSetting'); 9 | 10 | if(liSetting == NULL) { 11 | liSetting = new LithiumSettings1__c(); 12 | liSetting.Name = 'DefaultSetting'; 13 | liSetting.Sync_With_Contact__c = true; 14 | liSetting.Create_New_Contact_record__c = true; 15 | Database.insert(liSetting); 16 | } 17 | 18 | System.debug('__liSetting___'+liSetting); 19 | 20 | for(Li_Community_User1__c aCommunityUser : trigger.new) { 21 | if(aCommunityUser.Email_Address__c != NULL) 22 | emailToCommunityUserMap.put((aCommunityUser.Email_Address__c).trim().tolowercase(), aCommunityUser); // build a map of email>community user 23 | } 24 | 25 | System.debug('__emailToCommunityUserMap___'+emailToCommunityUserMap); 26 | 27 | //change comments after first run 28 | /*List cntLds = new List(); 29 | set stEmail = new set(); 30 | 31 | for(Lead c: [SELECT Id,FirstName, LastName, Name, LithiumId__c, Email, OwnerId, Owner.isActive FROM Lead WHERE IsConverted = FALSE AND Email IN :emailToCommunityUserMap.keySet() and LithiumId__c != null]) 32 | { 33 | cntLds.add(c); 34 | stEmail.add(String.valueOf(c.Email).trim().tolowercase()); 35 | } 36 | 37 | for(Lead c: [SELECT Id,FirstName, LastName, Name,LithiumId__c, Email, OwnerId, Owner.isActive FROM Lead WHERE IsConverted = FALSE AND Email IN : emailToCommunityUserMap.keySet() and LithiumId__c = null]) 38 | { 39 | if(!stEmail.contains(String.valueOf(c.Email).trim().tolowercase())) 40 | { 41 | cntLds.add(c); 42 | stEmail.add(String.valueOf(c.Email).trim().tolowercase()); 43 | } 44 | } 45 | //List cntcts = [SELECT Id, FirstName, LastName, LithiumId__c , Name, Email FROM Contact WHERE Email IN :emailToCommunityUserMap.keySet()]; 46 | //first run ends 47 | 48 | for(Lead aLead1 : cntLds) { 49 | if(!emailToLeadMap.containsKey(aLead1.Email)){ 50 | emailToLeadMap.put(String.valueOf(aLead1.Email).trim().tolowercase(), aLead1); // build a map of email>contact (Contacts which have same email as community users) 51 | } 52 | } */ 53 | 54 | /*************************************EMAIL TO LEAD MAP************************************/ 55 | for(Lead c : [SELECT Id,owner.profileid,FirstName,isconverted, LastName, Name, LithiumId__c, Email, OwnerId, Owner.isActive FROM Lead WHERE IsConverted = FALSE AND Email IN :emailToCommunityUserMap.keySet()]){ 56 | emailToLeadMap.put(String.valueOf(c.Email).trim().tolowercase(), c); 57 | } 58 | 59 | /*******************************EMAIL TO CONTACT MAP**************************************/ 60 | for(Contact c : [SELECT Id,owner.profileid,FirstName, LastName, Name, LithiumId__c, Email, OwnerId, Owner.isActive FROM Contact WHERE Email IN :emailToCommunityUserMap.keySet()]){ 61 | emailToContactMap.put(String.valueOf(c.Email).trim().tolowercase(), c); 62 | } 63 | 64 | //List lstCon = new List(); 65 | Set profilesIDWithPermissions = new Set(); 66 | for(permissionset ps : [select id, name, Profile.Name,Profile.id, (SELECT Id, SObjectType, PermissionsRead FROM ObjectPerms where SObjectType='Li_Community_User1__c') from permissionset where PermissionSet.Profile.Name!=null]){ 67 | System.debug('_____ps.Profile.Name___'+ps.ObjectPerms); 68 | 69 | if(ps.ObjectPerms != null && !ps.ObjectPerms.isEmpty()){ 70 | System.debug('___has_value__'); 71 | profilesIDWithPermissions.add(ps.Profile.id); 72 | } 73 | } 74 | System.debug('___profilesIDWithPermissions____'+profilesIDWithPermissions); 75 | 76 | for(String usr_email : emailToCommunityUserMap.keySet()) { // loop through all community users with email address 77 | 78 | System.debug('__inside_for_loop__'); 79 | usr_email = usr_email.trim().tolowercase(); 80 | Lead aLead = emailToLeadMap.get(usr_email); 81 | Contact aContact = emailToContactMap.get(usr_email); 82 | 83 | Li_Community_User1__c aCommunityUser = emailToCommunityUserMap.get(usr_email); 84 | 85 | /***************LEAD ALREADY EXISTS**************/ 86 | if(aLead != NULL && liSetting.Sync_With_Contact__c){ // attach Lead record 87 | System.debug('__lead_already_exists___just_attach_it_'+'___Profileid___'+aLead.owner.profileid); 88 | aCommunityUser.Lead__c = aLead.Id; // use existing Lead 89 | if(aLead.ownerId != null && aLead.owner.isactive == true && String.valueOf(aLead.id.getSObjectType()) == 'User' && profilesIDWithPermissions.contains(aLead.owner.profileid)){ 90 | system.debug('___reached__LEad'); 91 | aCommunityUser.ownerid = aLead.ownerId; 92 | } 93 | //Comment after first run 94 | //if(aContact.LithiumId__c == null) 95 | //lstCon.add(new contact(id=aContact.Id,LithiumId__c = aCommunityUser.Lithium_User_Id__c)); 96 | //Comment ends 97 | } 98 | /***************IF LEAD DOES NOT EXISTS THN CONTACT MIGHT EXISTS************/ 99 | else if(aContact != null){ // attach Lead record 100 | System.debug('__lead_is_converted_or_lead_does_not_exist_attach_contact__'+aContact+'___Profileid___'+aContact.owner.profileid); 101 | aCommunityUser.contact__c = aContact.Id; // use existing Lead 102 | if(aContact.ownerId != null && aContact.owner.isactive == true && profilesIDWithPermissions.contains(aContact.owner.profileid)){ 103 | system.debug('___reached__contact'); 104 | aCommunityUser.ownerid = aContact.ownerId; 105 | } 106 | } 107 | /****************IF NITHER LEAD EXISTS AND NOE CONTACT THEN CREATE LEAD***************/ 108 | else if(aLead == NULL && aContact == null && liSetting.Create_New_Contact_record__c) { // Lead/Contact doesn't exist and lithium setting asks to create one 109 | System.debug('1___'+aCommunityUser+'__2____'+aCommunityUser.name); 110 | //if(aCommunityUser != null && aCommunityUser.name != null && !aCommunityUser.name.Startswith('a4m')){ 111 | if(aCommunityUser != null){ 112 | System.debug('__inside_creation_of_new_leads___'); 113 | aLead = new Lead(); // No Lead exists; create new record 114 | aLead.ownerid = '005C0000006hBOnIAM'; 115 | if(aCommunityUser.First_Name__c != null) 116 | aLead.FirstName = aCommunityUser.First_Name__c; 117 | // LastName is required for Lead; making sure it is not empty 118 | if(aCommunityUser.Last_Name__c == NULL){ 119 | aLead.LastName = 'Unknown'; 120 | aLead.LeadSource = 'Website'; 121 | aLead.Lead_Source_Category__c = 'Create Account'; 122 | aLead.Lead_Source_Subcategory__c = 'Optiverse'; 123 | }else 124 | aLead.LastName = aCommunityUser.Last_Name__c; 125 | aLead.Email = aCommunityUser.Email_Address__c; 126 | aLead.LithiumId__c = aCommunityUser.Lithium_User_Id__c; 127 | if(aCommunityUser.company__c != null) 128 | aLead.company = aCommunityUser.company__c; 129 | else 130 | aLead.company = 'N/A'; 131 | LeadsToInsert.add(aLead); // list of Lead to update/insert 132 | listOfCommunityUsers.add(aCommunityUser); // maintain a list of community users also; same order as LeadsToInsert 133 | } 134 | } 135 | } 136 | //Comment after first run 137 | /*if(lstCon != null && lstCon.size() > 0) 138 | { 139 | update lstCon; 140 | }*/ 141 | //Comment ends 142 | if(!LeadsToInsert.isEmpty()) { 143 | try{ 144 | Database.SaveResult[] iResults = Database.insert(leadsToInsert, false); // insert/update Leads; continue DML operations if a failure occurs 145 | Integer i = 0; 146 | for(Database.SaveResult result : iResults) { 147 | // Database.SaveResult[] in same order as leadsToInsert 148 | // Safe to assume same index for community users list 149 | if(!result.isSuccess()) { // error occured in lead update/insert 150 | system.debug('error updating ' + result.getErrors()[0].getMessage()); 151 | listOfCommunityUsers[i].addError('Error creating/updating Lead'); // add error to community user object; 152 | } 153 | else if(liSetting.Sync_With_Contact__c){ // attach new lead record or not 154 | listOfCommunityUsers[i].Lead__c = result.getId(); // assign lead id; this is not available before lead is inserted, hence done here 155 | } 156 | ++i; 157 | } 158 | } catch(Exception e) { 159 | system.debug('Catched the exception while inserting/updating Lead'); 160 | } 161 | } 162 | } -------------------------------------------------------------------------------- /CommunityUserBeforeTriggerCreateLead (2).tgr: -------------------------------------------------------------------------------- 1 | trigger CommunityUserBeforeTriggerCreateLead on Li_Community_User1__c (before insert, before update) { 2 | 3 | Map emailToCommunityUserMap = new Map(); 4 | Map emailToLeadMap = new Map(); 5 | Map emailToContactMap = new Map(); 6 | List listOfCommunityUsers = new List(); 7 | List leadsToInsert = new List(); 8 | LithiumSettings1__c liSetting = LithiumSettings1__c.getValues('DefaultSetting'); 9 | 10 | if(liSetting == NULL) { 11 | liSetting = new LithiumSettings1__c(); 12 | liSetting.Name = 'DefaultSetting'; 13 | liSetting.Sync_With_Contact__c = true; 14 | liSetting.Create_New_Contact_record__c = true; 15 | Database.insert(liSetting); 16 | } 17 | 18 | System.debug('__liSetting___'+liSetting); 19 | 20 | for(Li_Community_User1__c aCommunityUser : trigger.new) { 21 | if(aCommunityUser.Email_Address__c != NULL) 22 | emailToCommunityUserMap.put((aCommunityUser.Email_Address__c).trim().tolowercase(), aCommunityUser); // build a map of email>community user 23 | } 24 | 25 | System.debug('__emailToCommunityUserMap___'+emailToCommunityUserMap); 26 | 27 | //change comments after first run 28 | /*List cntLds = new List(); 29 | set stEmail = new set(); 30 | 31 | for(Lead c: [SELECT Id,FirstName, LastName, Name, LithiumId__c, Email, OwnerId, Owner.isActive FROM Lead WHERE IsConverted = FALSE AND Email IN :emailToCommunityUserMap.keySet() and LithiumId__c != null]) 32 | { 33 | cntLds.add(c); 34 | stEmail.add(String.valueOf(c.Email).trim().tolowercase()); 35 | } 36 | 37 | for(Lead c: [SELECT Id,FirstName, LastName, Name,LithiumId__c, Email, OwnerId, Owner.isActive FROM Lead WHERE IsConverted = FALSE AND Email IN : emailToCommunityUserMap.keySet() and LithiumId__c = null]) 38 | { 39 | if(!stEmail.contains(String.valueOf(c.Email).trim().tolowercase())) 40 | { 41 | cntLds.add(c); 42 | stEmail.add(String.valueOf(c.Email).trim().tolowercase()); 43 | } 44 | } 45 | //List cntcts = [SELECT Id, FirstName, LastName, LithiumId__c , Name, Email FROM Contact WHERE Email IN :emailToCommunityUserMap.keySet()]; 46 | //first run ends 47 | 48 | for(Lead aLead1 : cntLds) { 49 | if(!emailToLeadMap.containsKey(aLead1.Email)){ 50 | emailToLeadMap.put(String.valueOf(aLead1.Email).trim().tolowercase(), aLead1); // build a map of email>contact (Contacts which have same email as community users) 51 | } 52 | } */ 53 | 54 | /*************************************EMAIL TO LEAD MAP************************************/ 55 | for(Lead c : [SELECT Id,owner.profileid,FirstName,isconverted, LastName, Name, LithiumId__c, Email, OwnerId, Owner.isActive FROM Lead WHERE IsConverted = FALSE AND Email IN :emailToCommunityUserMap.keySet()]){ 56 | emailToLeadMap.put(String.valueOf(c.Email).trim().tolowercase(), c); 57 | } 58 | 59 | /*******************************EMAIL TO CONTACT MAP**************************************/ 60 | for(Contact c : [SELECT Id,owner.profileid,FirstName, LastName, Name, LithiumId__c, Email, OwnerId, Owner.isActive FROM Contact WHERE Email IN :emailToCommunityUserMap.keySet()]){ 61 | emailToContactMap.put(String.valueOf(c.Email).trim().tolowercase(), c); 62 | } 63 | 64 | //List lstCon = new List(); 65 | Set profilesIDWithPermissions = new Set(); 66 | for(permissionset ps : [select id, name, Profile.Name,Profile.id, (SELECT Id, SObjectType, PermissionsRead FROM ObjectPerms where SObjectType='Li_Community_User1__c') from permissionset where PermissionSet.Profile.Name!=null]){ 67 | System.debug('_____ps.Profile.Name___'+ps.ObjectPerms); 68 | 69 | if(ps.ObjectPerms != null && !ps.ObjectPerms.isEmpty()){ 70 | System.debug('___has_value__'); 71 | profilesIDWithPermissions.add(ps.Profile.id); 72 | } 73 | } 74 | System.debug('___profilesIDWithPermissions____'+profilesIDWithPermissions); 75 | 76 | for(String usr_email : emailToCommunityUserMap.keySet()) { // loop through all community users with email address 77 | 78 | System.debug('__inside_for_loop__'); 79 | usr_email = usr_email.trim().tolowercase(); 80 | Lead aLead = emailToLeadMap.get(usr_email); 81 | Contact aContact = emailToContactMap.get(usr_email); 82 | 83 | Li_Community_User1__c aCommunityUser = emailToCommunityUserMap.get(usr_email); 84 | 85 | /***************LEAD ALREADY EXISTS**************/ 86 | if(aLead != NULL && liSetting.Sync_With_Contact__c){ // attach Lead record 87 | System.debug('__lead_already_exists___just_attach_it_'+'___Profileid___'+aLead.owner.profileid); 88 | aCommunityUser.Lead__c = aLead.Id; // use existing Lead 89 | if(aLead.ownerId != null && aLead.owner.isactive == true && String.valueOf(aLead.id.getSObjectType()) == 'User' && profilesIDWithPermissions.contains(aLead.owner.profileid)){ 90 | system.debug('___reached__LEad'); 91 | aCommunityUser.ownerid = aLead.ownerId; 92 | } 93 | //Comment after first run 94 | //if(aContact.LithiumId__c == null) 95 | //lstCon.add(new contact(id=aContact.Id,LithiumId__c = aCommunityUser.Lithium_User_Id__c)); 96 | //Comment ends 97 | } 98 | /***************IF LEAD DOES NOT EXISTS THN CONTACT MIGHT EXISTS************/ 99 | else if(aContact != null){ // attach Lead record 100 | System.debug('__lead_is_converted_or_lead_does_not_exist_attach_contact__'+aContact+'___Profileid___'+aContact.owner.profileid); 101 | aCommunityUser.contact__c = aContact.Id; // use existing Lead 102 | if(aContact.ownerId != null && aContact.owner.isactive == true && profilesIDWithPermissions.contains(aContact.owner.profileid)){ 103 | system.debug('___reached__contact'); 104 | aCommunityUser.ownerid = aContact.ownerId; 105 | } 106 | } 107 | /****************IF NITHER LEAD EXISTS AND NOE CONTACT THEN CREATE LEAD***************/ 108 | else if(aLead == NULL && aContact == null && liSetting.Create_New_Contact_record__c) { // Lead/Contact doesn't exist and lithium setting asks to create one 109 | System.debug('1___'+aCommunityUser+'__2____'+aCommunityUser.name); 110 | //if(aCommunityUser != null && aCommunityUser.name != null && !aCommunityUser.name.Startswith('a4m')){ 111 | if(aCommunityUser != null){ 112 | System.debug('__inside_creation_of_new_leads___'); 113 | aLead = new Lead(); // No Lead exists; create new record 114 | aLead.ownerid = '005C0000006hBOnIAM'; 115 | if(aCommunityUser.First_Name__c != null) 116 | aLead.FirstName = aCommunityUser.First_Name__c; 117 | // LastName is required for Lead; making sure it is not empty 118 | if(aCommunityUser.Last_Name__c == NULL){ 119 | aLead.LastName = 'Unknown'; 120 | aLead.LeadSource = 'Website'; 121 | aLead.Lead_Source_Category__c = 'Create Account'; 122 | aLead.Lead_Source_Subcategory__c = 'Optiverse'; 123 | }else 124 | aLead.LastName = aCommunityUser.Last_Name__c; 125 | aLead.Email = aCommunityUser.Email_Address__c; 126 | aLead.LithiumId__c = aCommunityUser.Lithium_User_Id__c; 127 | if(aCommunityUser.company__c != null) 128 | aLead.company = aCommunityUser.company__c; 129 | else 130 | aLead.company = 'N/A'; 131 | LeadsToInsert.add(aLead); // list of Lead to update/insert 132 | listOfCommunityUsers.add(aCommunityUser); // maintain a list of community users also; same order as LeadsToInsert 133 | } 134 | } 135 | } 136 | //Comment after first run 137 | /*if(lstCon != null && lstCon.size() > 0) 138 | { 139 | update lstCon; 140 | }*/ 141 | //Comment ends 142 | if(!LeadsToInsert.isEmpty()) { 143 | try{ 144 | Database.SaveResult[] iResults = Database.insert(leadsToInsert, false); // insert/update Leads; continue DML operations if a failure occurs 145 | Integer i = 0; 146 | for(Database.SaveResult result : iResults) { 147 | // Database.SaveResult[] in same order as leadsToInsert 148 | // Safe to assume same index for community users list 149 | if(!result.isSuccess()) { // error occured in lead update/insert 150 | system.debug('error updating ' + result.getErrors()[0].getMessage()); 151 | listOfCommunityUsers[i].addError('Error creating/updating Lead'); // add error to community user object; 152 | } 153 | else if(liSetting.Sync_With_Contact__c){ // attach new lead record or not 154 | listOfCommunityUsers[i].Lead__c = result.getId(); // assign lead id; this is not available before lead is inserted, hence done here 155 | } 156 | ++i; 157 | } 158 | } catch(Exception e) { 159 | system.debug('Catched the exception while inserting/updating Lead'); 160 | } 161 | } 162 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advanced-Apex-Programming 2 | 3 | ![Apex Code Structure](https://s-media-cache-ak0.pinimg.com/originals/3d/ae/7c/3dae7cafff48d9440cd6928a23f3e4dd.png) 4 | 5 | Apex constructs: 6 | * support for single inheritance 7 | * interfaces 8 | * template support 9 | * control flow structures 10 | * operators 11 | * variable declarations 12 | 13 | Apex concepts: 14 | * Execution Contexts 15 | * Static Variables 16 | * Bulk Patterns 17 | * Limits 18 | 19 | ### Execution Context 20 | 21 | An execution context has two characteristics: 22 | * It defines the scope and lifetime of the static variables 23 | * It defines the context for those governor limits that are reset between execution contexts. 24 | * There is a limit to the size of memory heap you can use in an execution context. 25 | 26 | Ramifications of the execution context: 27 | * You can have multiple triggers on an event, but have no control over the order in which they execute. 28 | * Limits are shared within an Execution Context, thus you may be sharing limits with other code over which you have no control and which may be added after yours is built and tested. 29 | * Workflows can impact the order and execution context in unpredictable ways. 30 | 31 | ### Static Variables 32 | 33 | Static variables are maintained throughout an execution context, and are unique to an execution context. 34 | 35 | Many (but not all) limits are reset between execution contexts. For example, if governor limits restrict you to 100 database queries in an execution context, each execution context can have 100 database queries. Different types of execution contexts may have different limits. 36 | 37 | You know when the execution context starts. Generally, cant know when it ends. 38 | 39 | * Static variable's lifetime and scope is defined by the execution context. 40 | 41 | ### Running Apex Code 42 | 43 | An execution context begins when one of a number of possible external events or operations happen that have the ability to start running Apex code. These include: 44 | * A database trigger: Triggers can occur on insertion, update, deletion or undeletion of many standard Salesforce objects and all custom objects. 45 | * Future call (asynchronous call): Future calls can be requested from Apex code. They run with extended limits. 46 | * Scheduled Apex: You can implement an Apex class that can be called by the system on a scheduled basis. 47 | * Batch Apex: You can implement a class designed to process large numbers of database records. 48 | * Web service: You can implement a class that can be accessed via SOAP or REST from an external site or from Javascript on a web page. 49 | * Visualforce: Visualforce pages can execute Apex code in Visualforce controllers to retrieve or set page properties or execute methods. 50 | * Global Apex: You can expose a global method that can be called from other Apex code. 51 | * Anonymous Apex: Apex code can be compiled and executed dynamically from the developer console, Force.com IDE or through an external web service call. 52 | 53 | `public Static Boolean firstcall = false;` - Use the following design pattern in your trigger to determine if this was the first or subsequent call for this execution context 54 | 55 | ```Apex 56 | if(!myclass.firstcall) 57 | { 58 | // First call into trigger 59 | myclass.firstcall = true; 60 | } 61 | else 62 | { 63 | // Subsequent call into trigger 64 | } 65 | ``` 66 | Static variables in Apex have execution context scope and lifetime. 67 | 68 | ### Event Handler 69 | 70 | * Event handler is a callback routine that operates asynchronously and handles inputs received into a program (events). In this context, an event is some meaningful element of application information from an underlying development framework, usually from a GUI toolkit or some kind of input routine. 71 | * On the GUI side, events include key strokes, mouse activity, action selections, or timer expirations. 72 | * On the input side, events include opening or closing files and data streams, reading data, etc. 73 | 74 | ### Future Calls 75 | 76 | * Future calls execute at some indeterminate time in the future, but because platform can schedule them based on server load, they are granted higher limits than other execution contexts, and are thus ideal for computationally intensive tasks. 77 | * You can only make up to ten future calls from an execution context, and you cant make a future call from a future context. 78 | * In Apex, you have no way knowing that you are exiting the execution context. 79 | 80 | ```Apex 81 | // indicates that the call has already been made 82 | public class SomeFutureOperations { 83 | private static Boolean FutureCallCalled = false; 84 | public static void DoFutureCall() 85 | { 86 | if(FutureCallCalled || 87 | System.isFuture()) return; 88 | FutureCallCalled = true; 89 | ActualFutureCall(); 90 | } 91 | 92 | @future 93 | private static void ActualFutureCall() 94 | { 95 | // Actual async code here 96 | system.debug('ActualFutureCall async operation'); 97 | } 98 | 99 | } 100 | ``` 101 | 102 | ### Trigger Patterns 103 | 104 | https://help.salesforce.com/articleView?id=000176390&type=1 105 | 106 | 107 | ### Testing, Debugging and Deployment 108 | 109 | * You must have unit tests that cover at least 75% of your code to deploy software to production org. 110 | * 111 | 112 | ```Apex 113 | public static String CurrentLog() 114 | { 115 | if(DiagnosticLog == null) return null; 116 | String spaces = ' '; 117 | String result = ''; 118 | for(DiagnosticEntry de: DiagnosticLog) 119 | { 120 | Integer endindex = 3 * de.level; 121 | if(endindex >= spaces.length()) 122 | endindex = spaces.length()-1; 123 | result += spaces.substring(0,endindex) + 124 | de.description + '\n'; 125 | } 126 | return result; 127 | } 128 | ``` 129 | 130 | ### Debugging and Diagnostics 131 | 132 | Test-driven development environment. 133 | 134 | * Debugging is the process of figuring out why software is working incorrectly and fixing the problem. To debug software you want to have the following: 135 | * A way to reproduce the problem 136 | * A way to capture data about the problem 137 | * A way to modify the code to try different ways of solvign the problem 138 | 139 | * Diagnostics - capturing data about the operation of the software. 140 | 141 | During development, the easiest way to reproduce an error is using test classes. 142 | 143 | * `SeeAllData` attribute on a test class to view or hide existing data in an org. 144 | * Unlike test classes, anonymous Apex works on actual data. 145 | * Breakpoints - 146 | * Watchpoints - 147 | 148 | * The primary source for capturing runtime data from Apex are the debug logs. The debug logs have the following characteristics: 149 | * They are limited in size. 150 | * You can control the level of detail of the data you are capturing at the class level. Capture enough detail and you can view the values of variables - but you are more likely to exceed the max log size. 151 | * You can use the System.debug statement to add debug data to the log. Those statements can hard to find in large debug logs. 152 | * The Developer Console has the ability to extract and organize data from the debug logs, but only if the debug logs dont exceed a certain size. 153 | * The platform stores only a limited number of debug logs. 154 | * When instructed to capture debug logs, the monitoring continues for a limited time or number of logs. Continuous logging is possible for the current user using the Developer Console, though the number of logs kept is limited. 155 | * Debug logs are generated for a particular running user. 156 | * Debug logs do not capture detailed data from managed packages unless you are the package owner and log in via the Subscriber portal. 157 | 158 | Debug cycle: 159 | * Reproduce the error to obtain a log file and find a problem 160 | * Add some debugging code 161 | * Override the detail level of one or more classes so as not to exceed the max debug log size 162 | * Repeat 163 | 164 | The DiagnosticInstrumentation class starts by defining some static variables along with the DiagnosticEntry class that contains the current level and description of a diagnostic entry: 165 | 166 | ```Apex 167 | public static Boolean DiagnosticEnabled = true; 168 | 169 | private static List DiagnosticLog; 170 | private static Integer CurrentLevel = 0; 171 | 172 | private class DiagnosticEntry 173 | { 174 | Integer level; 175 | String description; 176 | 177 | public DiagnosticEntry(string entrydescription) 178 | { 179 | level = CurrentLevel; 180 | description = entrydescription; 181 | } 182 | } 183 | ``` 184 | 185 | The DiagnosticEnabled flag makes it possible to enable or disable the diagnostic system. This is important because the diagnostics code does use script lines, and in later implementations, performs SOQL and DML calls as well. Disable the diagnostics in cases where limits are an issue. 186 | 187 | ### Benchmarking 188 | 189 | * Place the operation you want to measure inside of the loop, perform the operation multiple times, then divide the time spent by the number of iterations. 190 | 191 | ```Apex 192 | @isTest 193 | private class Benchmarking { 194 | 195 | @istest 196 | public static void TestNewAllocate() 197 | { 198 | for(Integer x = 0; x < 10000; x++) 199 | ReturnNewMap(); 200 | } 201 | 202 | private static Map ReturnNewMap() 203 | { 204 | Map result = new Map(); 205 | return result; 206 | } 207 | } 208 | ``` 209 | ## Bulk Patterns 210 | 211 | * Salesforce does not support triggers on the `OpportunityContactRole` object. 212 | * Test-driven design methodology. 213 | * Test goals: 214 | * Does the code work? 215 | * Obtaining code coverage 216 | * Handling invalid input 217 | * 218 | --------------------------------------------------------------------------------