Welcome to the largest chapter of Salesforce Platform Dev II Exam Preparation: Logic and Process Automation. This chapter consists of 33% of total score! Better yet, let's dive in and have fun with it!

NOTE: This post is written in July 2019 and content might be changed/updated overtime. The content is inspired by focusonforce.com.

Order of Execution

  1. Load original record or new record is initialized
  2. Load field values into sObjects
  3. System Validations (NOTE: also Custom Validations on Opportunity Products)
  4. Apex Before Triggers
  5. Run System Validations again and Custom Validations
  6. Duplicate Rules
  7. Record saved but not committing to databaseyet
  8. Apex After Triggers
  9. Assignment Rules
  10. Auto Response Rules
  11. Workflow Rules
    • NOTE: if there are workflow field updates, update the record again (fired Before Update Triggers and After Update Triggers one more time (and only one more time!).
    • NOTE: Only System Validations will be running again. Custom Validations, Duplicate Rules, Escalation Rules and etc. will not run again.
    • NOTE: this is not the same idea as 'Re-evaluate Workflow Rules after Field Change' which indicates that all workflow rules on the associated object will be re-evaluated again if checked.
  12. Execute Processes (like those in Process Builder)
    • NOTE: Custom Validation Rules will be running again after a field update caused by a process, but not after a workflow field update.
    • NOTE: Updates via processes cause the record to go through the save procedure again, which results in re-evaluation of all the rules again.
  13. Workflow Flow (like those flow in Process Builder)
  14. Escalation Rules
  15. Entitlement Rules
  16. Calculate Roll-up Summary field in parent record
  17. Parent record saved
  18. Calculate Roll-up Summary field in grandparent record
  19. Grandparent record saved
  20. Evaluate Criteria Based Sharing
  21. Commit to database
  22. Post-commit logic, such as email alerts

Important Notes on Order of Executions

  • Order of execution is not guaranteed when multiple triggers, workflows or processes exist for same object.
  • Salesforce skips steps after After Triggers after recursive save.
  • Trigger.old in last Update Trigger won't contain the version of the object immediately prior to the workflow update when record is updated and subsequently triggers a workflow field update, but the object before initial update was made.
  • myVariable_current contains all of the record's current field values while myVariable_old contains all of the field values of the record immediately before it was changed.

Visualforce Order of Execution

  • Order of executions depend on the lifecycle of Visualforce page which is determined by the content of the page and how it is requested.
    • GET Request: initial request for a page by user via URL.
      1. Evaluate constructors on controller and extensions
      2. Check if page contains custom components. If yes,
        • create custom components and execute constructor methods on custom controller and extensions
        • evaluate expressions on attribute definitions
        • execute any assignTo attributes on custom components
      3. Evaluate expressions, <apex:page> action attributes, and other method calls
      4. Check if page contains <apex:form>. If yes, create View State.
      5. Send HTML to browser and execute client-side technologies such as JavaScript.
    • POST Request: request made when user interacts with UI.
      1. Decode View State, which is used for updating values on the page
      2. Evaluate expressions and execute set methods in controller and extensions
      3. Check if all methods are executed successfully. If not, show appropriate error messages. If yes,
        • execute action that triggered the POST request.
        • update data and also update View State on the returned page
      4. Send HTML to browser.

Notes on Apex, Process Builder, Flow and Visualforce

  • Apex Action can be invoked in Process Builder by specifying @InvocableMethod annotation. The invocable methods must be static and either public or global.
  • @InvocableVariable annotation can be used to identify class variables used as input or output parameter.
  • NOTE: only one invocable method per Apex Class and can only have at most one input parameter and that must be of sObject or primitive type list variables or field values from related record.
  • You can add annotation attributes 'label' and 'description' on invocable methods. You can also use 'required' to indicate variable is required and add a prompt in UI.
public class Test{
    @InvocableMethod(required = true label = 'Test Label' description = 'Test Description')
    public static void testInvocableMethod(List<String> testString){
        
    }
}
  • Apex Code can be called from Flow using Apex Plug-in and Call Apex.
  • Bulk operations are supported by @InvocableMethod but not Process.Plugin interface.
  • NOTE: @invocableMethod() is preferred over Process.Plugin interface as it is newer and provide more functionality. It can be used in Flows, Processes, and Rest API.
  • Apex Class that implements Process.Plugin interface can only be used in Flow, not in Process Builder!
    • the interface accepts input values and returns output values back to Flow (Blob, Collection, sObject, and Time data types are not supported)
    • the interface supports three top-level classes:
      • Process.PluginRequest - pass input params to flow
      • Process.PluginResult - return output params from class
      • Process.PluginDescribeResult - pass input params from flow to class
public class LookupAccountPlugin implements Process.Plugin{
    public Process.PluginResult invoke(Process.PluginRequest request){
        String name = (String) request.inputParameters.get('name');
        Account account = [SELECT Id FROM Account WHERE Name = :name LIMIT 1][0];
        Map<String, Object> result = new Map<String, Object>();
        result.put('objestId', account.Id);
        return new Process.PluginResult(result);
    }
    
    public Process.PluginDescribeResult describe(){
        Process.PluginDescribeResult result = new Process.PluginDescribeResult();
        result.Name = 'Look up Account by Name';
        result.Tag = 'Account Class';
        result.inputParameters = new List<Process.PluginDescribeResult.InputParameter>{
            new Process.PluginDescribeResult.InputParameter(
                    'name',
                    Process.PLuginDescribeResult.ParameterType.STRING, 
                    true
            )
        };
        result.outputParameters = new List<Process.PluginDescribeResult.OutputParameter>{
            new Process.PluginDescribeResult.OutputParameter(
                'accountId',
                Process.PluginDescribeResult.ParameterType.STRING
            )
        };
        return result;
    }

}

  • Flow can be called from Apex by using Flow.Interview system class.
    • start() method can be used to launch an autolaunched flow or user provisioning flow from Apex.
    • getVariableValue(name) method can be used to returned the value of specific flow variable.
    • NOTE: flow needs to be active to be called from Apex.
    • Flow can also be called dynamically by creating Interview object.
Map<String, Object> params = new Map<String, Object>();
Flow.Interview.Test_Flow flow = new Flow.Interview.Test_Flow(params);
flow.start();
String testString = (String) flow.getVariableValue('testString');
System.debug(testString);
public class DynamicFlowInterview{
    public static void createDynamicFlow(String namespace, String flowName, Map<String, Object> inputs){
        Flow.Interview flow = Flow.Interview.createInterview(namespace, flowName, inputs);
        flow.start();
    }
}

Map<String, Object> params = new Map<String, Object>();
DynamicFlowInterview.createDynamicFlow('xa35', 'Test_Flow', params);
  • Flow can be embedded in Visualforce using <flow:interview> component.
  • NOTE: access to Visualforce page permission is required and also the users need to have 'Run Flows' permission or 'Flow User' is checked on user detail page.
  • The value of variables can be set using <apex:param> component.
<apex:page standardController="Account">
    <flow:interview name="Test_Flow" finishLocation="{!URLFOR('/home/home.jsp)}">
        <apex:param name="accountDescription" value="Describe test" />
    </flow:interview>
</apex:page>

Debug Process for Order of Execution

  • Typical debug process includes the following:
    • Check and verify process automation features in Setup
    • Match each automation in the order of execution
    • Setup Debug Trace and Debug Levels in Debug Log
      • you can set Traced Entity Type to be Automated Process, Platform Integration, User, Apex Class and Apex Trigger.
      • you can also set the Start Date and Expiration Date and Debug Level.
        debug-log
      • create New Debug Level with level of None, Error, Warn, Info, Debug, Fine, Finer, Finest
      • event types supported: Workflow, Validation, Callouts, Apex Code, Apex Profiling, Visualforce, System, Wave, NBA
        new-debug-level
    • Use Log Inspector in Developer Console:
      debug-log-inspector
      • log panels can be selected in Debug > View Log Panels.. or simply press CTRL + P to select the panels (default only shows Execution Log):
        • Stack Tree - show the order of called methods in top-down view and display amount of time for code to execute (no time taken by each category of operations)
          • Execution Tree - can show Duration and Heap
            stack-tree-execution-tree
          • Performance Tree - can show Duration, Duration Percentage, Heap, Heap Percentage, and Iterations
            stack-tree-performance-tree
        • Execution Stack - show the order of called methods in bottom-up view and display amount of time taken by by a specific section of the code selected in Stack Tree.
          debug-log-execution-stack
        • Execution Log - contains various logging levels for different event types
          debug-log-execution-log
        • Source and Source List - show executed code and a count of how many times a line of code executed
          debug-log-source
          debug-log-source-list
        • Variables - show the value assigned to a variable at the time of execution
          debug-log-variables
        • Execution Overview - Used to obtain summary information above processes.
          • Save Order - color-coded view of when events fired (each color represents an event type)
            execution-overview-save-order
          • Limits - overall system limit counts (require profiling log level set to Finest)
            execution-overview-limits
          • Timeline - show how long each process had taken (percentage of time taken and scale of time also included)
            execution-overview-timeline
          • Executed Units - filterable view of various execution units (Methods, Queries, Workflow, Callouts, DML, Validations, Triggers and Pages)
            • Display system resource used by each item in the process
            • Provide information about the name of process, total, average, min/max duration, heap size, query type, total and etc. (can be sorted)
            • Quickly view information about number of rows returned by a query, number of times a method got called, and etc.
              execution-overview-execution-units
      • Log level can be changed by selecting Debug > Change Log Levels...
        debug-log-change-log-levels
        • 3 types debug logs can be set:
          • General Trace Settings for You - Expiration is automatically updated while you are on the Dev Console
          • Class and Trigger Trace Overrides - add or remove debug logs for Apex Class and Apex Trigger (will not be displayed on Debug Logs in Setup)
          • User Tracing for All Users - add or remove debug logs for specific users (will be displayed on Debug Logs in Setup)
        • You can add, modify and remove debug levels:
          debug-log-change-log-levels-add-or-change
        • NOTE: deleting Debug Level will also delete related debug logs.
        • NOTE: SFDC_DevConsole is Salesforce default debug level which cannot be deleted.
  • Notes on debugging:
    • Time-based workflows and Scheduled Actions from Process Builder do not include in Debug Log.
    • A code unit is a discrete unit of work within a transaction, ex: one trigger, one web service method, one validation rule.
    • Maximum log size for debug log is 20MB. Older log entries will be deleted (about 10% of log entries which is 200KB)
    • NOTE: not all log entries will be deleted during truncation as some events are necessary for processing debug log.
  • Event naming convention and its minimum debug levels are shown below:
Item (by execution order)Naming ConventionProfile TypeMinimum Debug Level
ValidationsVALIDATION_*ValidationINFO
TriggersCODE_UNIT_STARTED
CODE_UNIT_FINISHED
Apex CodeERROR
WorkflowsWF_*WorkflowINFO (mostly)
FINE
ERROR
ProcessesFLOW_*
"FLOW_RULE_" (criteria)
"FLOW_WAIT_" (schedule)
WorkflowFINE
FINER (recommended)
ERROR
WARNING
FlowsFLOW_*WorkflowFINE
FINER (recommended)
ERROR
WARNING
Escalation RulesWF_ESCALATION_*WorkflowINFO
DatabaseDML_*DatabaseINFO
GeneralUSER_DEBUGApex CodeDEBUG
  • NOTE: to set debug log level in Apex Code, you can write like this: System.debug(LoggingLevel.INFO, 'My Text');

Error Handling and Exceptions

  • Exceptions denote errors and other events that disrupt the normal flow of Apex Code execution, usually occurred when Apex tries to execute code that is failing.
  • Exceptions can be manually thrown based on the the code logic.
  • Unhandled exceptions occurred when no code is written to deal with an exception, all DML operations before exception are rolled back and not committed to database.
  • Developer will receive an email with the details of execution for an exception that is not caught by Apex code.
  • Exceptions are logged in debug logs.
  • Errors can be handled in many ways, such as assertions, returning error codes, and exception handling.
  • Error class can represent information above an error that occurred during a DML operation when using a Database method.
  • Error class has some useful methods:
    • getMessage() - return error message
    • getFields() - return an array of one or more field names
    • getStatusCode() - return code that characterizes the error
  • addError() method from SObject class (such as Account, Case) can be thrown with custom error message displayed to prevent any DML operation.
  • Exceptions can be handled by using throw statements which are used to generate exceptions, while try, catch and finally statements are used to recover from exceptions.
  • Types of exceptions:
    • DML Exception
      • error with DML statement using DmlException built-in exception type
      • example: insert record without required fields
    • List Exception
      • error with a list using ListException built-in exception type
      • example: trying to access an index that is out of bounds
    • Null Pointer Exception
      • error with dereferencing a null variable using NullPointerException built-in exception type
      • example: call a method on a variable which has been created but not initialized with values
    • Query Exception
      • error with SOQL query in code using QueryException built-in exception type
      • example: SOQL query returns no or more than one record is assigned to a single sObject variable
      • example: Aggregate Query has too many rows for direct assignment
    • SObject Exception
      • error with handling a problem with sObject records using SObjectException built-in exception type
      • example: attempt to assign variable to a field that has not been retrieved in SOQL query, or trying to access field that is unavailable
    • Limit Exception
      • error with exceeding governor limit and this type of exception cannot be caught!
      • Limit methods:
        • Limits.getLimitQueries() - 100 number of SOQL queries
        • Limits.getLimitDmlRows() - 10000 number of records that can be queried
        • Limits.getLimitDmlStatements() - 150 number of DML statements
        • Limits.getLimitCpuTime() - 10000 milliseconds of CPU usage time
        • Limits.getQueries()
        • Limits.getDmlRows()
        • Limits.getDmlStatements()
        • Limits.getCpuTime()
      • examples:
        • too many DML statements, ex: System.LimitException: Too Many DML statements: 151.
        • too many SOQL queries, ex: System.LimitException: Too many SOQL queriesL 101.
        • too many SOSL queries, ex: System.LimitException: Too many SOSL queries: 21.
      • NOTE: if a namespace is defined, it will be included in error message.
    • Custom Exception
      • custom defined error throw and catch in Apex
public class CustomException extends Exception {}

...

try{
    String s = 'Hello';
    if(s.equals('World')){
        throw new CustomException('Hello not equal to World!');
    }

} catch (Exception e){
    System.debug('Exception Error: ' + e.getMessage());
}

    • Visualforce Exception
      • exceptions in custom controller or controller extension
      • message can be displayed using ApexPages.Message class
    • Bulk DML Exception
      • error with bulk DML statements
      • NOTE: all records must be updated successfully or else the entire operation will be rolled back.
      • NOTE: mixed DML operations in one transaction might cause mixed DML error, for example, updating an account and user profile in same transaction.
      • Database class methods can allow partial success of bulk DML operation with 'allOrNone' parameter, true(default) means all records must be updated successfully or else exception will be thrown, false means partial save is allowed.
      • SaveResult object or SaveResult object array can be used to return the status of each operation and any errors encountered.

List<Account> accList = new List<>();

for(Integer i = 0; i < 10; i++){
    accList.add(
        new Account(
            Name = 'Test ' + i
        )
    );
}

Database.SaveResult[] srList = Database.insert(accList, false);

for(Database.SaveResult sr : srList){
    if(sr.isSuccess()){
        System.debug('Record ' + sr.getId() + ' saved successfully!');
    } else{
        for(Database.Error err : sr.getErrors()){
            System.debug('Record failed to save!');
            System.debug(err.getStatusCode() + ': ' + err.getMessage());
        }
    }   
}
  • Exception methods that commonly in use:
    • getMessage() - return the error message associated with the exception
    • getCause() - return the cause of exception
    • getLineNUmber() - return the line number where exception was thrown
    • getStackTraceString() - return the stack trace in String
    • getTypeName() - return the type name of exception, ex: DmlException, NullPointerException and etc.
    • getDmlFieldNames() - return the names of fields that caused the error (DmlException only)
    • getDmlId() - return the Id of failed record that caused the error (DmlException only)
    • getDmlMessage() - return the error message for the failed record (DmlException only)
    • getNumDml() - return the number of failed records (DmlException only)
  • Tips about using multiple exception types:
    • multiple different types of exceptions can be defined but only one catch block will be executed
    • the generic exception type can be put in the last catch block
  • Savepoint and transaction control:
    • A savepoint variable will be generated to roll back partial code so that processing can continue in another direction.
    • Database can be restored to the same condition it was in at the time the savepoint variable was created and any DML statements after the savepoint can be discarded.
    • Database method setSavepoint() and rollback() can be used to generate a savepoint and perform rollback operation.
    • NOTE: If multiple savepoints are created and that user rolls back to a savepoint that is not the last savepoint, the later savepoint variables will become invalid.
    • Each savepoint and rollback counts against governor limit for DML statements.
Account acc = new Account(Name = 'Test');
insert acc;

Savepoint sp1 = Database.setSavepoint();

acc.Type = 'Customer;
update acc;

Savepoint sp2 = Database.setSavepoint();

acc.Type = 'Reseller';
update acc;

Database.rollback(sp1);

// exception thrown because already rollback
// System.TypeException: Savepoint does not exist in this context
Database.rollback(sp2);

  • Mitigation techniques to avoid exceeding governor limits, making code more efficient and scalable:
    • Process records in bulk using FOR loop in Apex
    • Queries multiple batches of records using SOQL FOR Loop
List<Account> accList = new List<Account>();
for(List<Account> accs : [Select Id, Name FROM Account WHERE Type = 'Customer']){
    accList.add(accs);
}
    • Invoke asynchronous method with batch of records
    • Do not hardcode ID in Apex code
    • Use of Limits class methods to avoid hitting governor limits
    • Use of OrgLimits class methods to obtain information about limits associated with API requests (SOAP API, Bulk API, Streaming API and etc.)
      • getAll() - return list of all OrgLimit instances
      • getMap() - return a map of all OrgLimit instances with limit name as key
Map<String, System.OrgLimit> limitsMap = OrgLimits.getMap();
System.OrgLimit l = limitsMap.get('DailyApiRequests');
System.debug('Name: ' + l.getName());
System.debug('Usage: ' + l.getValue());
System.debug('Limit: ' + l.getLimit());

SOQL & SOSL

  • SOQL statement return types can be:
    • List of sObjects
      • example: `List accList = [SELECT Id FROM Account];
    • a single sObject
      • example: Account acc = [SELECT Id FROM Account LIMIT 1];
    • Integer
      • example: Integer count = [SELECT COUNT() FROM Account];
  • SOSL statements return a list of lists of sObjects where each list contains the search results for a particular sObject type.
    • example: List<List<sObject>> resultLists = [FIND 'name' IN ALL FIELDS RETURNING Account(Name), Contact(FirstName, LastName), Opportunity(Name, StageName)];
List<List<sObject>> resultLists = [
    FIND 'name' IN ALL FIELDS RETURNING 
    Account(Name),
    Contact(FirstName, LastName),
    Opportunity(Name, StageName)
];

Account[] accList = (List<Account>) resultLists[0];
Contact[] conList = (List<Contact>) resultLists[1];
Opportunity[] oppList = (List<Opportunity>) resultLists[2];

for(Account acc : accList){
    System.debug('Account: ' + acc.Name);
}

for(Contact con : conList){
    System.debug('Contact: ' + con.FirstName + ' ' + con.LastName);
}

for(Opportunity opp : oppList){
    System.debug('Opportunity: ' + opp.Name + ' ' + opp.StageName);
}

  • NOTE: The result lists are returned in the same order as they are specified in the query.
  • NOTE: You cannot assign a field variable without querying the field.

Asynchronous Apex

  • Asynchronous Apex executes task in the background thread, which allows handling parallel processes that require higher limits, callouts to external systems, or code to run at certain time.

  • Apex jobs can be viewed in Setup > Apex Jobs:
    apex-jobs

  • 4 ways to run Apex code asynchronously:

    • Future Methods
      • run asynchronously in separate thread in background and will start if resources are available
      • can be used for long running operations to prevent delays in Apex transaction, callouts to external web services and segregating DML operations to prevent mixing DML errors.
      • A future method must be annotated with @future and must be static and return void type with parameters of primitive data types, arrays of primitive data types, or collections of primitive date types.
      • NOTE: sObjects or objects cannot be used as arguments.
      • NOTE: an org can have up to 2,000 unprocessed asynchronous requests. The additional requests are delayed while queue handles requests from other orgs.
global class FutureClass{
    @future
    public static void updateAccount(List<Id> ids){
        List<Account> accList = [SELECT Id, Type FROM Account where Id IN :ids];
        List<Account> accToBeUpdatedList = new List<Account>();
        for(Account acc : accList){
            a.Type = 'Potential Customer';
            accToBeUpdatedList.add(a);
        }
        update accToBeUpdatedList;
    }
}

...

Account acc = new Account(
    Name = 'Test'
);
insert acc;
List<Id> ids = new List<Id>();
ids.add(acc.Id);
FutureClass.updateAccount(ids);

    • Queueable Apex
      • Apex class that implements 'Queueable' interface can be added to Apex Job queue and to be run asynchronously when resources are available.
      • Unlike Future Methods, Queueable Apex allows job-chaining (sequential processing - a job can be started from another running job) and using non-primitive types, such as sObjects or custom Apex types.
      • Queueable Apex allows the use of member variables of non-primitive types, such as sObjects.
      • An Id of the AsyncApexJob record is returned when a queueable job is submitted by invoking the System.enqueueJob() method.
      • You can view the Apex Job status in 'Apex Jobs' from Setup or querying AsyncApexJob using SOQL.
      • NOTE: Up to 50 jobs can be added to the queue with System.enqueueJob in a single transaction.
      • NOTE: a job can be chained to another job and this process can be repeated with each new child job, but only one child job can exist for each parent queueable job (no limits on the depth of chained jobs).
global class QueueableClass implements Queueable{

    public void execute(QueueableContext context){
        List<Account> accList = [SELECT Id, Type FROM Account];
        List<Account> accToBeUpdatedList = new List<Account>();
        for(Account acc : accList){
            a.Type = 'Potential Customer';
            accToBeUpdatedList.add(a);
        }
        update accToBeUpdatedList;
        
        // Queueable Apex allow job-chaining
        SecondJob sj = new SecondJob();
        Id sjId = System.enqueueJob(sj);
    }
}

...
FirstJob fj = new FirstJob();
Id fjId = System.enqueueJob(fj);
AsyncApexJob aJob = [SELECT Id, Status FROM AsyncApexJob WHERE Id = :fjId];
System.debug('AsyncApexJob ' + aJob.Id + ' has status of ' + aJob.Status);

    • Batch Apex
      • Apex class that implements 'Database.Batchable' interface can be used to process millions of records asynchronously.
      • Commonly used methods from Database.Batchable interface:
        • start() - used to collect records or objects to be passed to the interface method 'execute' for processing, returning either a Database.QueryLocator object or an Iterable.
        • execute() - process each batch of data (default batch size is 200 records)
          • NOTE: batches are not guaranteed to execute in the order they are received from 'start' method
        • finish() - execute post-processing operations (such as sending email) after all batches are processed
      • All the methods in Database.Batchable interface require a reference to Database.BatchableContext object.
      • You can view the Batch Apex Job status in 'Apex Jobs' from Setup or querying AsyncApexJob using SOQL.
      • When a batch class is invoked, a job is placed on Apex Job queue and executed as a discrete transaction.
      • Every Batch Apex transaction starts with new set of governor limits. When one batch fails to process, all other successful batch transactions are not rolled back.
      • Batch class can be invoked by instantiating it and calling Database.executeBatch() with the instance of class. Number of records per batch can be passed into execute() method.
      • NOTE: Batch Apex should only be used if there are more than 1 batch of records to process.
      • NOTE: A trigger that invokes a batch job must not add more jobs than the limit.
      • The governor limit for Database.QueryLocator to retrieve total number of records by SOQL queries can be up to 50 million records! Likewise, an Iterable has a governor limit of 50,000 records.
global class BatchClass implements Database.Batchable<sObject>{
    
    public Database.QueryLocator start(Database.BatchableContext bc){
        return Database.getQueryLocator(
            'SELECT Id FROM Account WHERE Type = \'Customer\''
        );
    }
    
    public void execute(Database.BatchableContext bc, List<Account> accList){
        List<Account> accToBeUpdatedList = new List<Account>();
        for(Account acc : accList){
            a.Description = 'Test';
            accToBeUpdatedList.add(a);
        }
        update accToBeUpdatedList;
    }
    
    public void finish(Database.BatchableContext bc){
        // Get the ID of the AsyncApexJob representing this batch job
        // from Database.BatchableContext.
        // Query the AsyncApexJob object to retrieve the current job's information.
        AsyncApexJob a = [
            SELECT Id, Status, NumberOfErrors, JobItemsProcessed,
            TotalJobItems, CreatedBy.Email
            FROM AsyncApexJob WHERE Id = :bc.getJobId()
        ];
   
        // Send an email to the Apex job's submitter notifying of job completion.
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        String[] toAddresses = new String[] {a.CreatedBy.Email};
        mail.setToAddresses(toAddresses);
        mail.setSubject('Async Job ' + a.Status);
        mail.setPlainTextBody(
            'Job Status: ' + a.Status +
            ', Job Items Processed: ' + a.JobItemsProcessed +
            ', Total Job Items: ' + a.TotalJobItems
        );
        Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });        
    }
}

...

Database.executeBatch(new BatchClass());

    • Scheduled Apex
      • Apex Class that implements 'Schedulable' interface can be run at a specified time using Apex Scheduler.
      • execute() is the only method needs to be implemented in Schedulable interface and that System.schedule() method can be called to schedule Apex Job.
      • System.schedule() methods takes 3 parameters: the name of scheduled job, cron expression, and the ScheduledClass object
      • After a class has been scheduled, a CronTrigger object is created representing the scheduled job, getTriggerId() can be used to return the Id of CronTrigger object.
      • Apex Job can also be scheduled in 'Apex Classes' in Setup by clicking Scheduled Apex button.
        scheduled-apex-job
        scheduled-jobs
      • NOTE: actual execution of Apex Class will be delayed based on service availability (ex: schedules run after maintenance).
global class ScheduledClass implements Schedulable{
    public void execute(SchedulableContext sc){
        List<Account> accList = [SELECT Id, Type FROM Account];
        List<Account> accToBeUpdatedList = new List<Account>();
        for(Account acc : accList){
            a.Type = 'Potential Customer';
            accToBeUpdatedList.add(a);
        }
        update accToBeUpdatedList;    
    }
}

...

// CRON scheduled job at 12AM every day
String cron = '0 0 0 * * ?';
String jobId = System.Schedule('Scheduled Job at 12AM', cron, new ScheduledClass());
  • Some use case of each Apex Features:
    • Future Methods:
      • External web service callouts
      • Resource-intensive operations
      • Isolating DML operations
    • Queueable Apex:
      • Using sObjects as parameters
      • Monitoring job progress using Id
      • Sequential Apex processing (job chaining)
    • Batch Apex:
      • Processing large data volumes
      • Querying with large results
    • Scheduled Apex
      • Scheduling Apex code

Continuation Server

  • App Server sends the request from Visualforce page to Continuation Server. The Continuation Server then submits the request to the external web service. The App Server will wait for a response from the Continuation Server, the callback method on Visualforce page controller will be invoked when App Server receives response from Continuation Server.
  • addHttpRequest() method of Continuation class instance can be used to chain callout request up to 3 callouts can be simultaneously made. Next request will be processed only when the previous one has completed.
  • NOTE: when using Continuation class, a callback method must be implemented.

Dynamic Apex

  • Dynamic Apex allows accessing metadata information in Salesforce.
  • SObject Describe Information:
    • Token or the describeSObjects Schema method can be used to describe sObjects
    • Token is a serializable lightweight reference to an sObject or a field that is validated at compile time.
    • An object of type Schema.DescribeSObjectResult can describe properties for the sObject by using either the sObject token or the describeSObjects method.
    • Schema commonly used methods:
      • getGlobalDescribe() - return a map of all sObject names (keys) to sObject tokens (values) for the standard and custom objects defined in your organization.
      • describeSObjects(sObjectTypes) - describe metadata (field list and object properties) for the specified sObject or array of sObjects.
      • describeTabs() - return information about the standard and custom apps available to running user
    • Schema.SObjectType commonly used methods:
      • getDescribe() - return the describe sObject result for this field
    • Schema.DescribeSObjectResult commonly used methods:
      • fields (see example below)
      • fieldSets (see example below)
      • getLabel() - return object's label (might not always be the object name)
      • getName() - return the name of the object
      • getRecordTypeInfos() - return a list of record types
      • getRecordTypeInfosByDeveloperName() - return a map that matches developer names to their associated record type
      • getRecordTypeInfosById() - return a map that matches record IDs to their associated record types
      • getRecordTypeInfosByName() - return a map that matches record labels to their associated record type.
      • getSobjectType() - return the Schema.SObjectType object for the sObject
      • isAccessible() - return true if the current user can see this object, false otherwise
      • isCreateable() - return true if the object can be created by the current user
      • isCustom() - return true if the object is a custom object, false if it is a standard object
      • isCustomSetting() - return true if the object is a custom setting
      • isDeletable() - return true if the object can be deleted by the current user
      • isFeedEnabled() - return true if Chatter feeds are enabled for the object (API v19.0 and above)
      • isMergeable() - return true if the object can be merged with other objects of its type by the current user, false otherwise. (Lead, Account and Contact is true)
      • isQueryable() - return true if the object can be queried by the current user
      • isSearchable() - return true if the object can be searched by the current user
      • isUndeletable() - return true if the object can be undeleted by the current user
      • isUpdateable() - return true if the object can be updated by the current user

// access sObject Token
Schema.sObjectType o1 = Account.sObjectType; // accessed by sObjectType member variable
o1 = new Account().getSObjectType(); // accessed by calling getSObjectType() on an sObject variable
 
// access Describe Result
Schema.DescribeSObjectResult o2 = Account.sObjectType.getDescribe(); // accessed by calling getDescribe() on sObject Token
o2 = Schema.sObjectType.Account; // accessed by using sObjectType variable with the name of sObject

// access Describe Result using Schema.describeSObjects(objectTypes)
String [] objectTypes = new String[]{'Account', 'Contact'};
Schema.DescribeSObjectResult[] or = Schema.describeSObjects(objectTypes);

// access Describe Result using Schema.getGlobalDescribe()
Map<String, Schema.SObjectType> objectMap = Schema.getGlobalDescribe();
Schema.DescribeSObjectResult o3 = objectMap.get('Account').getDescribe();

// get record type id by accessing Schema.DescribeSObjectResult
Map<String, Schema.RecordTypeInfo> rtiMap = o3.getRecordTypeInfosByName();
if(rtiMap.containKeys('Customer')){
    Id recordId = recordTypeInfo.get('Customer').getRecordTypeId();
    System.debug('Record Type Id: ' + recordId);
}
  • Field Describe Information:
    • Similar to sObjects, tokens can be used to describe fields

    • Field token uses Schema.SObjectField data type.

    • Field Describe Result uses Schema.DescribeFieldResult data type.

    • Schema.SObjectField commonly used methods:

      • getDescribe() - return the describe field result for this field
    • Schema.DescribeFieldResult commonly used methods:

      • getByteLength() - return the max size of the field in bytes
      • getDefaultValues()
      • getLabel() - return text label that is displayed next to the field
      • getLength() - return the max size of the field for DescribeFieldResult object in Unicode characters (not bytes)
      • getName() - return the field name used in Apex
      • getPicklistValues() - return a list of PicklistEntry objects
      • getSObjectField() - return the token for this field
      • getType() - return one of the DisplayType enum values, depending on the type of field
      • isAccessible() - return true if the current user can see this field
      • isAutoNumber()
      • isCalculated()
      • isCaseSensitive()
      • isCreateable()
      • isCustom()
      • isUnique()
      • isUpdateable()
      • and etc.
    • getMap() method can be used on field describe result to return a map of all the field names (keys) and field tokens (values) for an sObject.

    • getLabel() method can be used on field describe result to return field label.

    • getPicklistValues() method returns a list of PicklistEntry objects.

    • NOTE: getSObjectField() method returns a token for a field, but it requires to obtain metadata information about all the fields on the custom object, which is why using getMap() to generate a map of field tokens would be better.


// access sObject token for a field
Schema.SObjectField f = Account.Name; // accessed by static member variable name of an sObject static type
f = Schema.sObjectType.Account.fields.Name.getSObjectField(); // accessed by calling getSObjectField() on field describe result

// access Describe Result for a field
Schema.DescribeFieldResult fr = Account.Name.getDescribe(); // accessed by calling getDescribe() on a field token
fr = Schema.sObjectType.Account.fields.Name; // accessed by fields member variable of an sObject token with a field member variable

// use of DescribeSObjectResult.fieldSets
Schema.DescribeSObjectResult d = Account.sObjectType.getDescribe();
Map<String, Schema.FieldSet> fsMap = d.fieldSets.getMap();

// get fields on object from map
Map<String, Schema.SObjectField> fieldMap = o3.fields.getMap();
for(String field: fieldMap.keySet()){
    Schema.DescribeFieldResult dfr = fieldMap.get(field).getDescribe();
    System.debug('Describe Field Result Label: ' + dfr.getLabel());
    if(dfr.getType() == Schema.DisplayType.Picklist){
        for(PicklistEntry pe : dfr.getPicklistValues()){
            System.debug('Picklist Label: ' + pe.getLabel());
        }
    }
}
  • App and Tab Describe Information:
    • Metadata information about apps and tabs can be obtained through a describe call.
    • A list of type Schema.DescribeTabSetResult can be used with Schema.describeTabs() method to get tab describes for standard and custom apps.
    • A list of type Schema.DescribeTabResult can be used with getTabs() method to obtain the describe result for the tabs in the app.
    • Schema.DescribeTabResult commonly used methods:
      • getColors() - return a list of color metadata information for all colors associated with this tab
      • getIconUrl() - return the URL for 32x32 pixel icon for a tab
      • getIcons() - return a list of icon metadata information for all icons associated with this tab
      • getLabel() - return the display label of this tab
      • getMiniIconUrl() - return the URL for 16x16 pixel icon that represents the tab
      • getSobjectName() - return the name of sObject that is displayed on this tab
      • getUrl() - return a fully qualified URL for viewing tab
      • isCustom()
    • Schema.DescribeTabSetResult commonly used methods:
      • getDescription() - return the description for standard or custom app
      • getLabel() - return the display label for the standard or custom app
      • getLogoUrl() - return a fully qualified URL to the logo associated with the standard or custom app
      • getNamespace() - return the developer namespace prefix of managed package
      • getTabs() - return metadata information about the standard or custom app's displayed tabs
      • isSelected() - return true if standard or custom app is currently selected
List<Schema.DescribeTabSetResult> dtsrList = Schema.describeTabs();
for(DescribeTabSetResult dtsr : dtsrList){
    String appLabel = dtsr.getLabel();
    System.debug('App label: ' + appLabel;
    
    if(appLabel == 'Sales){
        List<Schema.DescribeTabResult> dtrList = dtsr.getTabs();
        for(Schema.DescribeTabResult dtr : dtrList){
            System.debug('Tab label: ' + dtr.getLabel());
            System.debug('Tab icon url: ' + dtr.getIconUrl());
            System.debug('Tab url: ' + dtr.getUrl());
        }
    }
}

Dynamic SOQL, SOSL and DML

  • Sample Database query method: Database.query(), Database.countQuery().
  • NOTE: simple bind variables can be used in SOQL query strings, but bind variable fields cannot be used.
  • Search.query() method can be used to create dynamic SOSL query.
  • newSObject() can be used to create SObjects dynamically using DML.
  • NOTE: SObject token must be cast into sObject type in order to use the newSObject() method.

// example of SOQL
String s = 'test';
String q1 = 'SELECT Id FROM Account WHERE Name = :test';
List<sObject> sObjectList = Database.query(q1);
String q2 = 'SELECT Count() FROM Account WHERE Name = :test';
Integer c = Database.countQuery(q2);

// example of SOSL
String q3 = 'FIND \'test*\' IN ALL FIELDS RETURNING Account(Name), Contact, Lead(Status)';
List<List<SObject>> searchList = Search.query(q3);

// example of DML
Schema.sObjectType token = Account.sObjectType;
Account a = (Account) token.newSObject();

  • Inline SOQL is SOQL embedded in Apex code.
  • NOTE: dynamic SOQL cannot use bind variable fields in the query string. It will yield 'variable does not exist' error. However, it can be used in regular assignment statements.

Apex Managed Sharing (Classic Only)

  • Apex Managed Sharing can be used to build dynamic programmatically sharing that allows defining a custom Sharing Reason for the share.
    user-and-group-sharing
  • Objects with OWD of 'Private' or 'Public Read Only' have related 'Share' object.
  • Share objects for standard objects would be like AccountShare while custom objects would be like Shipment_Tracker__Share.
  • NOTE: Apex Sharing Reason and Apex Managed Sharing are available for custom objects only, but the Share objects exist for both.
  • NOTE: Apex Managed Sharing requires accessing Share objects, which cannot be accessed via Process Builder or Flows, only by Apex!
  • Example: SELECT Id, AccountId, RowCause, AccountAccessLevel, ContactAccessLevel, CaseAccessLevel, UserOrGroupId from AccountShare LIMIT 100
    account-share-query
  • NOTE: Sharing Reason must be defined for custom object, then only Apex code can be run to add or remove entries to Share Object to grant access to records.
  • Apex Sharing Reason can be found in Setup (Classic), navigating to Apex Sharing Reasons related list of an object.
    apex-sharing-reason-screen
  • Each Apex Sharing Reason has label (for user interface) and name (for Apex and API).
    create-apex-sharing-reason
  • Fields must be defined for custom object's share object:
    • ParentId - corresponds to the record being shared
    • UserOrGroupId - the id of user or public group to whom access is being granted
    • AccessLevel - can be either Read or Edit or
    • RowCause (also known as Sharing Reason) - used to specify the reason why the user or group is being granted access.
  • Either a custom or 'Manual' RowCause can be used in Apex Code to create records in share tables.
public static void apexManualSharing(Id parentId, Id userOrGroupId){
    Test_Object__Share testShareObj = new Test_Object__Share();
    testShareObj.ParentId = parentId;
    testShareObj.UserOrGroupId = userOrGroupId;
    testShareObj.AccessLevel = 'Read';
    testShareObj.RowCause = Schema.Test_Object__Share.RowCause.Custom_Test__c;
    insert testShareObj;
}

...

// create manual sharing
Test_Object__Share testShareObj = new Test_Object__Share();
// 'manual' is the default value for sharing objects
testShareObj.RowCause = Schema.Test_Object__Share.RowCause.Manual;
  • Apex managed sharing rules are automatically recalculated when a custom object's OWD access level is updated.
  • Custom Apex recalculation classes are executed during automatic recalculation.
  • 'Recalculate Apex Sharing' button on the Apex Sharing Recalculation section can be used to recalculate manually.
  • You can also create New Apex Sharing Recalculation (Developer Edition only and by request, and Apex class must implements Database.Batchable interface).
    create-new-apex-sharing-recalculation
  • NOTE: only user with 'Modify All Data' permissions can managed sharing records.
  • NOTE: when record owner is changed, the apex managed sharing does not get affected.

Email Services

  • Email services are automated processes that use Apex class to process the content, header, and attachments of inbound email.
    email-service
  • Create an email service:
    create-email-services
    create-email-service-finish
    • Create an email address to this email service.
      create-email-service-address
      create-email-service-address-finish
  • An Apex class that implements Messaging.InboundEmailHandler interface can be used to process incoming email messages.
global class myEmailHandler implements Messaging.InboundEmailHandler {
  global Messaging.InboundEmailResult handleInboundEmail(Messaging.InboundEmail email, Messaging.InboundEnvelope envelope) {
      
      Messaging.InboundEmailResult result = new Messaging.InboundEmailresult();
      
      String myPlainText= '';
      myPlainText = email.plainTextBody;
      // New Task object to be created
      Task[] newTask = new Task[0];

      // Try to look up any contacts based on the email from address
      // If there is more than one contact with the same email address,
      // an exception will be thrown and the catch statement will be called.
      try {
          Contact vCon = [SELECT 
                          Id, Name, Email
                          FROM Contact
                          WHERE Email = :email.fromAddress
                          LIMIT 1
                         ];

          // Add a new Task to the contact record we just found above.
          newTask.add(
              new Task(
                  Description =  myPlainText,
                  Priority = 'Normal',
                  Status = 'Inbound Email',
                  Subject = email.subject,
                  IsReminderSet = true,
                  ReminderDateTime = System.now()+1,
                  WhoId =  vCon.Id
              )
          );

          // Insert the new Task 
          insert newTask;    

          System.debug('New Task Object: ' + newTask );   
      }
      // If an exception occurs when the query accesses 
      // the contact record, a QueryException is called.
      // The exception is written to the Apex debug log.
      catch (QueryException e) {
          System.debug('Query Issue: ' + e);
      }

      // Set the result to true. No need to send an email back to the user 
      // with an error message
      result.success = true;          

      return result;
    }
}
  • Email-to-Case only allows the creation of cases from incoming emails.
  • Email-to-Salesforce can be used to relate emails to Salesforce leads, contacts, opportunities, and other specific Salesforce records.

Authentication Techniques

  • Single Sign-On (SSO) allow users to login from external environments.
    single-sign-on
  • Options to implement Single Sign-On (SSO):
    • Federated Authentication - allow affiliated but unrelated web services to share authentication data using SAML (Security Assertion Markup Language).
      • NOTE: this authentication method is automatically enabled by default.
    • Delegated Authentication - allow usage of a preferred authentication provider using LDAP (Lightweight Directory Access Protocol) server or a security token.
      • It is more secure as it makes login page private and protected by corporate firewall.
      • NOTE: this authentication method needs to contact Salesforce to enable it.
    • Other authentication providers such as Google, PayPal, and LinkedIn can be used to login to Salesforce using OpenID Connect protocol (Social Sign-On).
      auth-providers
  • NOTE: Social Sign-On can be used in Sites and Communities.
  • Administrator can decide which social network can be used to allow Single Sign-On.
  • User authentication is performed by the identity provider, not by Salesforce.
  • Salesforce can be enabled as an identity provider letting user use Salesforce SSO to login other websites.
    identity-provider
  • NOTE: You must configure and deploy My Domain and specify a certificate to use for secure communication.
    • Consumer keys and secrets are used when setting up Connected Apps.
    • NOTE: This option is available by default; no need to contact Salesforce support to enable it.
  • Benefit of using SSO is that the user will only need to remember one password.
  • Two-Factor Authentication allows users to provide second form of identity verification (either a code sent via text or email, or rotating code from authenticator app, such as Google Authenticator, Twilio Authenticator).
  • Lightning Login allows users to login without password using two factors of authentication (works in both Lightning and Classic):
    • mobile device with Salesforce Authenticator App installed and connected to user's Salesforce account
    • user's fingerprint or PIN number
  • NOTE: Lightning Login allows login without typing a password, but it doesn't necessarily mean it allows automatically login.
  • OAuth Protocol uses token to authenticate applications connecting to Salesforce via API.
  • CAPTCHA can be used to require users to pass a simple text-entry user verification test to export data from Salesforce (must be requested to enable this feature).

Salesforce Certificates

  • Salesforce certificates and key pairs are used for signature to verify a request coming from the organization.
    certificate-and-key-management
  • Salesforce certificates are used to authenticate SSL communication with external website or when org is being used as identity provider.
  • Certificates and key pairs can be exported into a keystore for storage or imported when required.
  • The exported file is in the Java Keystore (JKS) format, and the imported file must also be in the JKS format.
  • The API client certificate is used by workflow outbound messages, the AJAX proxy, and delegated authentication HTTPS callouts.
    api-client-certificate
  • Two-way SSL authentication can be used by sending certificate with a callout that was generated in Salesforce or signed by a Certificate Authority (CA).
  • Steps to enable authentication for a callout:
    1. Generate a certificate
    2. Integrate certificate with code containing a callout to a SOAP web service or HTTP request
    3. Share the certificate with 3rd party keystore, or configure the web or application server to request a client certificate
    4. Configure remote site settings or define a named credential for the callout
  • You can create Self-Signed Certificate by clicking Create Self-Signed Certificate.
    create-self-signed-certificate
    create-self-signed-certificate-finish
  • Certificates can be generated with 2048-bit or 4096-bit keys. 2048-bit keys last for one year and are faster while 4096-bit keys last for two years.
  • A Self-Signed Certificate can be downloaded with .crt extension.
  • A CA-Signed Certificate is a more authorative way of verifying SSL communication.
  • Fields such as Common Name, Email(optional), Company, Department, State, City and Country Code are required in order to create a unique CA-Signed certificate.
    create-CA-signed-certificate
    create-CA-signed-certificate-finish
  • Certificate Signing Request (CSR) can be downloaded and have .csr extension.
  • Mutual authentication certificate can be uploaded to Salesforce, but this feature needs to contact Salesforce to enable it.
  • Salesforce certificate can be integrated with a callout to SOAP web service or HTTP request.
// callouts to SOAP web service
docSample.DocSamplePort stub = new docSample.DocSamplePort();
stub.clientCertName_x = 'Self_Signed_Certificate';

// make HTTP request
HttpRequest request = new HttpRequest();
request.setClientCertificateName('Self_Signed_Certificate');
  • WSDL (Web Service Definition Language) is generated from 3rd party or the application that one wants to connect to.
  • Users can use certificate to authenticate themselves. Settings can be found in Setup > Session Settings > Identity Verification.
  • Certificate-based authentication needs to have My Domain set before using it.
  • In order to use certificate-based authentication, the following steps are needed:
    1. Check "Let users authenticate with a certificate" in Session Settings
      certificate-identity-verification
    2. Upload a User Authentication Certificate
      user-authentication-certificate
    3. Select "Certificate" as the authentication service on My Domain
      authentication-configuration-certificate
    4. Login using Certificate-Based Login.
      certificate-based-login
      certificate-based-login-select
  • Login Discovery can be used to simplify the login process by requiring the user to enter a unique identifier, such as an email address or phone number.
  • Handler class that implements MyDomainLoginDiscoveryHandler interface can be used in Login Discovery Handler.
    login-discovery-handler

Well, that's all! It is such a long post, but I hope it has been helpful!

Post was published on , last updated on .

Like the content? Support the author by paypal.me!