Fire Platform Events from Batch Apex Classes

Introduction 

With winter 19 release you can able to fire the platform events from the batch apex.Batch Apex classes can opt in to fire platform events when encountering an error or exception. Clients listening on an event can obtain actionable information, such as how often the event failed and which records were in scope at the time of failure. Events are also fired for Salesforce Platform internal errors and other uncatchable Apex exceptions such as LimitExceptions, which are caused by reaching governor limits. An event record provides more granular error tracking than the Apex Jobs UI. It includes the record IDs being processed, exception type, exception message, and stack trace. You can also incorporate custom handling and retry logic for failures. You can invoke custom Apex logic from any trigger on this type of event, so Apex developers can build functionality like custom logging or automated retry handling. To fire a platform event, a batch Apex class declaration must implement the Database.RaisesPlatformEvents interface.

Step 1:  Create a Platform event

Here is the simple platform event object created for this example 

Step 2: Subscribe to platform event

I just created a simple trigger to subscribe for the platform events. During this beta release, Process Builder and flows do not support subscribing to these events.

// Trigger for listening to Cloud_News events.
trigger CloudNewsTrigger on Cloud_News__e (after insert) {    
    // List to hold all cases to be created.
    List<Case> cases = new List<Case>();
    
    
    // Iterate through each notification.
    for (Cloud_News__e event : Trigger.New) {
        if (event.Urgent__c == true) {
            // Create Case to dispatch new team.
            Case cs = new Case();
            cs.Priority = 'High';
            cs.Subject = 'News team dispatch to ' +event.Location__c;
            cases.add(cs);
        }
    }
    
    // Insert all cases corresponding to events received.
    insert cases;
}

Step 3: Raise Platform events

now you can raise the platform event from the batch apex.here is the simple batch apex that will raise the platform evens. After raising platform events all the subscribed channel will be receiving the events.

global with sharing class PlatformEventRaise implements Database.Batchable<SObject>, Database.RaisesPlatformEvents{
    // class implementation
    global Database.QueryLocator start(Database.BatchableContext BC){
        return Database.getQueryLocator('Select Id ,Name,Rating,Industry, BillingAddress,BillingStreet,BillingCity, BillingCountry, BillingPostalCode,BillingState,Phone from Account  where BillingStreet!=NULL');
    }
    
    global void execute(Database.BatchableContext BC, List<sObject> scope){
        
        List<Account> accs =(List<Account>) scope ; 
        List<Cloud_News__e> cnewList = new List<Cloud_News__e>();
        for(Account a : accs){
            // Create an instance of the event and store it in the newsEvent variable
            Cloud_News__e newsEvent = new Cloud_News__e(
                Location__c=a.BillingStreet, 
                Urgent__c=true, 
                News_Content__c=a.BillingStreet);
            cnewList.add(newsEvent) ;
            
        }
        EventBus.publish(cnewList);
        
    }
    
    global void finish(Database.BatchableContext BC){
    }
}

Go and execute batch apex

After raising the platform events, the subscribed trigger will create a case as shown below

 

Event Error Handling

The BatchApexErrorEvent object represents a platform event associated with a batch Apex class. This example creates a trigger to determine which accounts failed in the batch transaction. Custom field Dirty__c indicates that the account was one of a failing batch and ExceptionType__c indicates the exception that was encountered. JobScope and ExceptionType are fields in the BatchApexErrorEvent object.

trigger MarkDirtyIfFail on BatchApexErrorEvent (after insert) {
    Set<Id> asyncApexJobIds = new Set<Id>();
    for(BatchApexErrorEvent evt:Trigger.new){
        asyncApexJobIds.add(evt.AsyncApexJobId);
    }
    
    Map<Id,AsyncApexJob> jobs = new Map<Id,AsyncApexJob>(
        [SELECT id, ApexClass.Name FROM AsyncApexJob WHERE Id IN :asyncApexJobIds]
    );
    
    List<Account> records = new List<Account>();
    for(BatchApexErrorEvent evt:Trigger.new){
        //only handle events for the job(s) we care about
        if(jobs.get(evt.AsyncApexJobId).ApexClass.Name == 'PlatformEventRaise'){
            for (String item : evt.JobScope.split(',')) {
                Account a = new Account(
                    Id = (Id)item,
                    ExceptionType__c = evt.ExceptionType,
                    Dirty__c = true
                );
                records.add(a);
            }
        }
    }
    update records;
}

 

Apex Inherited Sharing

Introduction

Salesforce apex with sharing or without sharing keywords on a class to specify whether sharing rules must be enforced. Use the inherited sharing keyword on an Apex class to run the class in the sharing mode of the class that called it. Apex without a sharing declaration is insecure by default. Designing Apex classes that can run in either with sharing or without sharing mode at runtime is an advanced technique. Such a technique can be difficult to distinguish from one where a specific sharing declaration is accidentally omitted. An explicit inherited sharing declaration makes the intent clear, avoiding ambiguity arising from an omitted declaration or false positives from security analysis tooling.

Using inherited sharing enables you to pass AppExchange Security Review and ensure that your privileged Apex code is not used in unexpected or insecure ways. An Apex class with inherited sharing runs as with sharing when used as a Lightning component controller, a Visualforce controller, an Apex REST service, or any other entry point to an Apex transaction.

There is a distinct difference between an Apex class that is marked with inherited sharing and one with an omitted sharing declaration. If the class is used as the entry point to an Apex transaction, an omitted sharing declaration runs as without sharing. However, inherited sharing ensures that the default is to run as with sharing. A class declared as inherited sharing runs as without sharing only when explicitly called from an already established without sharing context.

This example declares an Apex class with inherited sharing and a Visualforce invocation of that Apex code. Because of the inherited sharing declaration, only contacts for which the running user has sharing access are displayed. If the declaration is omitted, even contacts that the user has no rights to view are displayed due to the insecure default behavior of omitting the declaration.

public inherited sharing class InheritedSharingClass{
    public List<Contact> getAllTheSecrets(){
        return [SELECT Name FROM Contact];
    }
}
<apex:page controller="InheritedSharingClass">
    <apex:repeat value="{!allTheSecrets}" var="record">
        {!record.Name}
    </apex:repeat>
</apex:page>

 

 

Apex Crypto Example

Salesforce Crypto Provides methods for creating digests, message authentication codes, and signatures, as well as encrypting and decrypting information.
The methods in the Crypto class can be used for securing content in Lightning Platform, or for integrating with external services such as Google or Amazon Webservices (AWS).The cryptographic capabilities of the Crypto class are normally used in the following scenarios:
  • Confidentiality – the protection of data either at rest or in transit from unauthorized parties
  • Integrity – the data is complete and correct
  • Authenticity – proof of the authenticity of the sender or receiver of the message

Encryption and Decryption

Salesforce supports encrypt and decrypt information using AES128, AES192, and AES256 algorithms. Currently, only symmetric private key encryption using the AES algorithm is supported. The length of privateKey must match the specified algorithm: 128 bits, 192 bits, or 256 bits, which is 16, 24, or 32 bytes, respectively. You can use a third-party application or the generateAesKey method to generate this key for you.Here is the example that will show encryption and decryption.

AES128 algorithms

Blob initializationVector = Blob.valueOf('SixtenDigitlen16');
Blob key = Crypto.generateAesKey(128);
Blob cipherText = Blob.valueOf('The Data to be encrypted');
Blob encrypted = Crypto.encrypt('AES128', key, initializationVector, cipherText);


Blob decrypted = Crypto.decrypt('AES128', key, initializationVector, encrypted);
String decryptedString = decrypted.toString();
System.debug(decryptedString);

AES192 algorithms

Blob initializationVector = Blob.valueOf('123456789012345678901234');
Blob key = Crypto.generateAesKey(192);
Blob cipherText = Blob.valueOf('The Data to be encrypted');
Blob encrypted = Crypto.encrypt('AES192', key, initializationVector, cipherText);

Blob decrypted = Crypto.decrypt('AES192', key, initializationVector, encrypted);
String decryptedString = decrypted.toString();
System.debug(decryptedString);

AES256 algorithms

Blob initializationVector = Blob.valueOf('12345678901234567890123456789012');
Blob key = Crypto.generateAesKey(256);
Blob cipherText = Blob.valueOf('The Data to be encrypted');
Blob encrypted = Crypto.encrypt('AES256', key, initializationVector, cipherText);


Blob decrypted = Crypto.decrypt('AES256', key, initializationVector, encrypted);
String decryptedString = decrypted.toString();
System.debug(decryptedString);

Encrypt Decrypt With ManagedIV

Decrypts the Blob IVAndCipherText using the specified algorithm and private key. Use this method to decrypt blobs encrypted using a third party application or the encryptWithManagedIV method. These are all industry standard Advanced Encryption Standard (AES) algorithms with different size keys. They use cipher block chaining (CBC) and PKCS5 padding.The length of privateKey must match the specified algorithm: 128 bits, 192 bits, or 256 bits, which is 16, 24, or 32 bytes, respectively. You can use a third-party application or the generateAesKey method to generate this key for you.
AES128 algorithms
Blob exampleIv = Blob.valueOf('Example of IV123');
Blob key = Crypto.generateAesKey(128);
Blob data = Blob.valueOf('Data to be encrypted');
Blob encrypted = Crypto.encrypt('AES128', key, exampleIv, data);

Blob decrypted = Crypto.decrypt('AES128', key, exampleIv, encrypted);
String decryptedString = decrypted.toString();
System.assertEquals('Data to be encrypted', decryptedString);

AES192 algorithms

Blob key = Crypto.generateAesKey(192);
Blob data = Blob.valueOf('Data to be encrypted');
Blob encrypted = Crypto.encryptWithManagedIV('AES192', key, data);

Blob decrypted = Crypto.decryptWithManagedIV('AES192', key, encrypted);
String decryptedString = decrypted.toString();
System.assertEquals('Data to be encrypted', decryptedString);

AES256 algorithms

Blob key = Crypto.generateAesKey(256);
Blob data = Blob.valueOf('Data to be encrypted');
Blob encrypted = Crypto.encryptWithManagedIV('AES256', key, data);

Blob decrypted = Crypto.decryptWithManagedIV('AES256', key, encrypted);
String decryptedString = decrypted.toString();
System.assertEquals('Data to be encrypted', decryptedString);

 

Digital signature 

Computes a unique digital signature for the input string, using the specified algorithm and the supplied private key.The algorithm name. The valid values for algorithmName are RSA-SHA1, RSA-SHA256, or RSA.RSA-SHA1 is an RSA signature (with an asymmetric key pair) of a SHA1 hash.
String algorithmName = 'RSA';
String key = '';
Blob privateKey = EncodingUtil.base64Decode(key);
Blob input = Blob.valueOf('12345qwerty');
Crypto.sign(algorithmName, input, privateKey);

You can use Salesforce Certificate to use for signing as shown below.The Unique Name for a certificate stored in the Salesforce organization’s Certificate and Key Management page to use for signing.

Blob data = Blob.valueOf('12345qwerty');
System.Crypto.signWithCertificate('RSA-SHA256', data, 'signingCert');

 

 

Apex Callouts in Read-Only Mode

let’s understand how to handler the apex callouts during the salesforce read-only mode.During read-only mode, Apex callouts to external services execute and aren’t blocked by the system. Typically, you might execute some follow-up operations in the same transaction after receiving a response from a callout. For example, you might make a DML call to update a Salesforce record. But write operations in Salesforce, such as record updates, are blocked during read-only mode. Instance refreshes result in periods of read-only mode to facilitate infrastructure upgrades. Another is site switches. Continuous site switching enables Salesforce to improve our operations and infrastructure and meet the compliance requirement of many of our customers. Planned instance refreshes and site switches will put your Salesforce org in read-only mode for a portion of your preferred maintenance windows. To check whether the org is in read-only mode, call  System.getApplicationReadWriteMode(). The following example checks the return value of System.getApplicationReadWriteMode(). If the return value is equal to ApplicationReadWriteMode.READ_ONLY enum value, the org is in read-only mode and the callout is skipped. Otherwise (ApplicationReadWriteMode.DEFAULT value), the callout is performed.

To test read-only mode in the sandbox, contact Salesforce to enable the read-only mode test option. Once the test option is enabled, you can toggle read-only mode on and verify your apps. Here is the  code

public class AccountMatchReadOnly {
    
    public class MyReadOnlyException extends Exception {}
    public void getCalloutResponseContents(List<Account> acc) {
        
        // Get Read-only mode status
        ApplicationReadWriteMode mode = System.getApplicationReadWriteMode();
        if (mode == ApplicationReadWriteMode.READ_ONLY) {
            // Prevent the callout
            throw new MyReadOnlyException('Read-only mode. Skipping callouts!');
        } else if (mode == ApplicationReadWriteMode.DEFAULT) {
            // Instantiate a new http object
            for(Account a :acc){
                String addStr = EncodingUtil.urlEncode(a.BillingStreet+','+a.BillingCity+','+a.BillingCountry , 'UTF-8');
                Http h = new Http();
                HttpRequest request = new HttpRequest();
                request.setEndpoint('https://api.addressfinder.io/api/nz/address/cleanse?key=68JQFL39HNBKDUGWTVY4&secret=MHXT7LJBWYNGPF98KRV&q='+addStr+'&format=json');
                request.setMethod('POST');
                request.setHeader('Content-Type','application/json');
                HttpResponse response = h.send(request);
                
                if (response.getStatusCode() == 200) {
                    Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());
                    //System.debug('results'+results);
                    Boolean isMatched =(Boolean) results.get('matched') ; 
                    if(isMatched){
                        a.Verification_Status__c = 'Completed' ;
                        // update a ; 
                    }else{
                        a.Verification_Status__c = 'Pending' ;
                        //  update a ;     
                    }
                    
                }
            }
            update acc ;
            
        }
    }
}