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
- Load original record or new record is initialized
- Load field values into
sObjects
System Validations
(NOTE: alsoCustom Validations
on Opportunity Products)- Apex
Before Triggers
- Run
System Validations
again andCustom Validations
Duplicate Rules
- Record saved but not committing to databaseyet
- Apex
After Triggers
Assignment Rules
Auto Response Rules
Workflow Rules
- NOTE: if there are workflow field updates, update the record again (fired
Before Update Triggers
andAfter 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.
- NOTE: if there are workflow field updates, update the record again (fired
- Execute
Processes
(like those inProcess 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.
- NOTE:
Workflow Flow
(like those flow inProcess Builder
)Escalation Rules
Entitlement Rules
- Calculate
Roll-up Summary
field in parent record - Parent record saved
- Calculate
Roll-up Summary
field in grandparent record - Grandparent record saved
- Evaluate
Criteria Based Sharing
- Commit to database
- 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 lastUpdate 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 whilemyVariable_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.- Evaluate constructors on controller and extensions
- 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
- Evaluate expressions,
<apex:page>
action attributes, and other method calls - Check if page contains
<apex:form>
. If yes, createView State
. - Send HTML to browser and execute client-side technologies such as
JavaScript
.
POST
Request: request made when user interacts with UI.- Decode
View State
, which is used for updating values on the page - Evaluate expressions and execute set methods in controller and extensions
- 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
- execute action that triggered the
- Send HTML to browser.
- Decode
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 fromFlow
usingApex Plug-in
andCall Apex
.- Bulk operations are supported by @InvocableMethod but not Process.Plugin interface.
- NOTE:
@invocableMethod()
is preferred overProcess.Plugin
interface as it is newer and provide more functionality. It can be used inFlows
,Processes
, andRest API
. Apex Class
that implements Process.Plugin interface can only be used inFlow
, not inProcess 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
- the interface accepts input values and returns output values back to
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 fromApex
by using Flow.Interview system class.start()
method can be used to launch an autolaunched flow or user provisioning flow fromApex
.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 inVisualforce
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
andDebug Levels
inDebug 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.
- 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
- Use
Log Inspector
inDeveloper Console
:
- 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
- Performance Tree - can show Duration, Duration Percentage, Heap, Heap Percentage, and Iterations
- Execution Tree - can show Duration and Heap
- 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.
- Execution Log - contains various logging levels for different event types
- Source and Source List - show executed code and a count of how many times a line of code executed
- Variables - show the value assigned to a variable at the time of execution
- Execution Overview - Used to obtain summary information above processes.
- Save Order - color-coded view of when events fired (each color represents an event type)
- Limits - overall system limit counts (require profiling log level set to Finest)
- Timeline - show how long each process had taken (percentage of time taken and scale of time also included)
- 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.
- Save Order - color-coded view of when events fired (each color represents an event type)
- 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)
- Log level can be changed by selecting
Debug > 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
forApex Class
andApex Trigger
(will not be displayed onDebug Logs
inSetup
) - User Tracing for All Users - add or remove
debug logs
for specific users (will be displayed onDebug Logs
inSetup
)
- General Trace Settings for You - Expiration is automatically updated while you are on the
- You can add, modify and remove debug levels:
- NOTE: deleting
Debug Level
will also delete related debug logs. - NOTE:
SFDC_DevConsole
is Salesforce default debug level which cannot be deleted.
- 3 types debug logs can be set:
- log panels can be selected in
- Notes on debugging:
Time-based workflows
andScheduled Actions
fromProcess Builder
do not include inDebug 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 Convention | Profile Type | Minimum Debug Level |
---|---|---|---|
Validations | VALIDATION_* | Validation | INFO |
Triggers | CODE_UNIT_STARTED CODE_UNIT_FINISHED | Apex Code | ERROR |
Workflows | WF_* | Workflow | INFO (mostly) FINE ERROR |
Processes | FLOW_* "FLOW_RULE_" (criteria) "FLOW_WAIT_" (schedule) | Workflow | FINE FINER (recommended) ERROR WARNING |
Flows | FLOW_* | Workflow | FINE FINER (recommended) ERROR WARNING |
Escalation Rules | WF_ESCALATION_* | Workflow | INFO |
Database | DML_* | Database | INFO |
General | USER_DEBUG | Apex Code | DEBUG |
- 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 ofApex 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 messagegetFields()
- return an array of one or more field namesgetStatusCode()
- return code that characterizes the error
addError()
method fromSObject
class (such as Account, Case) can be thrown with custom error message displayed to prevent anyDML
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 fromexceptions
.- Types of exceptions:
- DML Exception
- error with DML statement using
DmlException
built-in exception type - example: insert record without required fields
- error with DML statement using
- List Exception
- error with a list using
ListException
built-in exception type - example: trying to access an index that is out of bounds
- error with a list using
- 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
- error with dereferencing a null variable using
- 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
- error with SOQL query in code using
- SObject Exception
- error with handling a problem with
sObject
records usingSObjectException
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
- error with handling a problem with
- Limit Exception
- error with exceeding governor limit and this type of exception cannot be caught!
- Limit methods:
Limits.getLimitQueries()
- 100 number of SOQL queriesLimits.getLimitDmlRows()
- 10000 number of records that can be queriedLimits.getLimitDmlStatements()
- 150 number of DML statementsLimits.getLimitCpuTime()
- 10000 milliseconds of CPU usage timeLimits.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.
- too many DML statements, ex:
- NOTE: if a
namespace
is defined, it will be included in error message.
- Custom Exception
- custom defined error throw and catch in
Apex
- custom defined error throw and catch in
- DML Exception
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
orcontroller extension
- message can be displayed using
ApexPages.Message
class
- exceptions in
- 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.
- Visualforce Exception
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 exceptiongetCause()
- return the cause of exceptiongetLineNUmber()
- return the line number where exception was throwngetStackTraceString()
- return the stack trace in StringgetTypeName()
- 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 theId
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()
androllback()
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
- Process records in bulk using FOR loop in
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 allOrgLimit
instancesgetMap()
- return a map of allOrgLimit
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];
- example: `List
- a single
sObject
- example:
Account acc = [SELECT Id FROM Account LIMIT 1];
- example:
- Integer
- example:
Integer count = [SELECT COUNT() FROM Account];
- example:
- List of
SOSL
statements return a list of lists ofsObjects
where each list contains the search results for a particularsObject
type.- example:
List<List<sObject>> resultLists = [FIND 'name' IN ALL FIELDS RETURNING Account(Name), Contact(FirstName, LastName), Opportunity(Name, StageName)];
- example:
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 inSetup > 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.
- Future Methods
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 toApex 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 assObjects
.- An Id of the
AsyncApexJob
record is returned when a queueable job is submitted by invoking theSystem.enqueueJob()
method. - You can view the
Apex Job
status in 'Apex Jobs' fromSetup
or queryingAsyncApexJob
usingSOQL
. - 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).
- Queueable Apex
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 aDatabase.QueryLocator
object or anIterable
.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 toDatabase.BatchableContext
object. - You can view the
Batch Apex Job
status in 'Apex Jobs' fromSetup
or queryingAsyncApexJob
usingSOQL
. - 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 callingDatabase.executeBatch()
with the instance of class. Number of records per batch can be passed intoexecute()
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, anIterable
has a governor limit of 50,000 records.
- Batch Apex
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 usingApex Scheduler
.execute()
is the only method needs to be implemented inSchedulable
interface and thatSystem.schedule()
method can be called to scheduleApex Job
.System.schedule()
methods takes 3 parameters: the name of scheduled job, cron expression, and theScheduledClass
object- After a class has been scheduled, a
CronTrigger
object is created representing the scheduled job,getTriggerId()
can be used to return the Id ofCronTrigger
object. Apex Job
can also be scheduled in 'Apex Classes' inSetup
by clicking Scheduled Apex button.
- NOTE: actual execution of
Apex Class
will be delayed based on service availability (ex: schedules run after maintenance).
- Scheduled Apex
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
- Future Methods:
Continuation Server
App Server
sends the request fromVisualforce
page toContinuation Server
. TheContinuation Server
then submits the request to the external web service. TheApp Server
will wait for a response from theContinuation Server
, the callback method onVisualforce
page controller will be invoked whenApp Server
receives response fromContinuation Server
.addHttpRequest()
method ofContinuation
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 thedescribeSObjects
Schema method can be used to describe sObjectsToken
is a serializable lightweight reference to ansObject
or a field that is validated at compile time.- An object of type
Schema.DescribeSObjectResult
can describe properties for thesObject
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 objectgetRecordTypeInfos()
- return a list of record typesgetRecordTypeInfosByDeveloperName()
- return a map that matches developer names to their associated record typegetRecordTypeInfosById()
- return a map that matches record IDs to their associated record typesgetRecordTypeInfosByName()
- return a map that matches record labels to their associated record type.getSobjectType()
- return theSchema.SObjectType
object for thesObject
isAccessible()
- return true if the current user can see this object, false otherwiseisCreateable()
- return true if the object can be created by the current userisCustom()
- return true if the object is a custom object, false if it is a standard objectisCustomSetting()
- return true if the object is a custom settingisDeletable()
- return true if the object can be deleted by the current userisFeedEnabled()
- return true ifChatter
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
andContact
is true)isQueryable()
- return true if the object can be queried by the current userisSearchable()
- return true if the object can be searched by the current userisUndeletable()
- return true if the object can be undeleted by the current userisUpdateable()
- 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 bytesgetDefaultValues()
getLabel()
- return text label that is displayed next to the fieldgetLength()
- return the max size of the field forDescribeFieldResult
object in Unicode characters (not bytes)getName()
- return the field name used in ApexgetPicklistValues()
- return a list ofPicklistEntry
objectsgetSObjectField()
- return the token for this fieldgetType()
- return one of theDisplayType
enum values, depending on the type of fieldisAccessible()
- return true if the current user can see this fieldisAutoNumber()
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 ofPicklistEntry
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 usinggetMap()
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 withSchema.describeTabs()
method to get tab describes for standard and custom apps. - A list of type
Schema.DescribeTabResult
can be used withgetTabs()
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 tabgetIconUrl()
- return the URL for 32x32 pixel icon for a tabgetIcons()
- return a list of icon metadata information for all icons associated with this tabgetLabel()
- return the display label of this tabgetMiniIconUrl()
- return the URL for 16x16 pixel icon that represents the tabgetSobjectName()
- return the name of sObject that is displayed on this tabgetUrl()
- return a fully qualified URL for viewing tabisCustom()
Schema.DescribeTabSetResult
commonly used methods:getDescription()
- return the description for standard or custom appgetLabel()
- return the display label for the standard or custom appgetLogoUrl()
- return a fully qualified URL to the logo associated with the standard or custom appgetNamespace()
- return the developer namespace prefix of managed packagegetTabs()
- return metadata information about the standard or custom app's displayed tabsisSelected()
- 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 dynamicSOSL
query.newSObject()
can be used to createSObjects
dynamically using DML.- NOTE:
SObject
token must be cast intosObject
type in order to use thenewSObject()
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 inApex
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.
- 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 likeShipment_Tracker__Share
. - NOTE:
Apex Sharing Reason
andApex Managed Sharing
are available for custom objects only, but the Share objects exist for both. - NOTE:
Apex Managed Sharing
requires accessingShare
objects, which cannot be accessed viaProcess Builder
orFlows
, only byApex
! - Example:
SELECT Id, AccountId, RowCause, AccountAccessLevel, ContactAccessLevel, CaseAccessLevel, UserOrGroupId from AccountShare LIMIT 100
- 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 inSetup
(Classic), navigating toApex Sharing Reasons
related list of an object.
- Each
Apex Sharing Reason
has label (for user interface) and name (for Apex and API).
- 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
orEdit
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).
- 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 useApex
class to process the content, header, and attachments of inbound email.
- Create an email service:
- Create an email address to this email service.
- Create an email address to this email service.
- An
Apex
class that implementsMessaging.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.
- 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).
- Federated Authentication - allow affiliated but unrelated web services to share authentication data using
- NOTE: Social Sign-On can be used in
Sites
andCommunities
. - 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.
- 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
andClassic
):- 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
andkey pairs
are used for signature to verify a request coming from the organization.
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.
- 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:
- Generate a certificate
- Integrate certificate with code containing a callout to a
SOAP
web service orHTTP
request - Share the certificate with 3rd party keystore, or configure the web or application server to request a client certificate
- Configure remote site settings or define a named credential for the callout
- You can create Self-Signed Certificate by clicking Create Self-Signed Certificate.
- 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.
- 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:
- Check "Let users authenticate with a certificate" in Session Settings
- Upload a User Authentication Certificate
- Select "Certificate" as the authentication service on
My Domain
- Login using Certificate-Based Login.
- Check "Let users authenticate with a certificate" in Session Settings
- 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 implementsMyDomainLoginDiscoveryHandler
interface can be used in 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!