Skip to main content

Managing Apex Triggers (Legacy Replicated)

note

Management of Apex triggers is required for operation of the legacy replication model only.

If you use the legacy replication data regulation policy within the InCountry Data Residency for Salesforce package, you need to create Apex triggers for each Salesforce object having regulated data.

The following example provides instructions on how to do this for the Lead object. Please use a similar approach for the rest of your Salesforce objects.

Lead trigger example

Warning

The records cannot be inserted/updated from the batch context. Otherwise, they will be hashed and clear-text values will be lost.

To avoid it, please see Records synchronization in batch context.

trigger InCountryLead on Lead (before insert, after insert, before update, after update, after delete, after undelete) {
SObjectType triggerType = trigger.isDelete ? Trigger.old.getSObjectType() : Trigger.new.getSObjectType();

if (!InCountryReplicationTriggerHandler.disableTrigger && !InCountryReplicationTriggerHandler.alreadyExecuted(triggerType)) {

InCountryReplicationTriggerHandler handler = new InCountryReplicationTriggerHandler(triggerType.getDescribe().getName());
if (Trigger.isBefore && (Trigger.isInsert || Trigger.isUpdate)) {
// any Before trigger customer logic should be put before
// handler.handleBeforeInsert(); call
handler.handleBeforeInsert();
} else if (Trigger.isAfter && (Trigger.isInsert || Trigger.isUpdate)) {
handler.handleAfterInsert();
InCountryReplicationTriggerHandler.registerExecution(triggerType);
// any After trigger customer logic should be put after
// handler.handleAfterInsert(); call
} else if (Trigger.isAfter && Trigger.isUndelete) {
handler.handleAfterUndelete();
}
}
if (Trigger.isDelete && Trigger.isAfter) {
SObjectType triggerType = trigger.isDelete ? Trigger.old.getSObjectType() : Trigger.new.getSObjectType();
InCountryReplicationTriggerHandler handler = new InCountryReplicationTriggerHandler(triggerType.getDescribe().getName());
handler.handleAfterDelete();
}
}

handler.handleBeforeInsert();

It is required to execute the handler.handleBeforeInsert(); method after initialization of the field that defines a record as regulated and verifies that it is set up correctly.

The call determines whether a record falls under any configured conditions at the time of its execution. This means that before the handleBeforeInsert method is executed the record should have all fields set up properly to identify the record as regulated. Otherwise, such record is ignored.

The handleBeforeInsert method hashes protected field values of the record. This means that any value in the protected field will be replaced with a hashed value. Once the method has been executed, the record will no longer have clear-text values in protected fields for a while. For example:

Before execution of the handler.handleBeforeInsert(); Trigger.new method, there is one Lead record with the Email field storing the john.doe@incountry.com email. The Email field is configured as protected and the used hash function is Fixed Value. We want the fixed value to be like, hidden@hidden.hidden.

Use this program code:

System.debug('1 - Trigger.new: ' + Trigger.new);
LeadTriggerHandler.handler.handleBeforeInsert();
System.debug('2 - Trigger.new: ' + Trigger.new);

The code debugging will output the following:

1 - Trigger.new: (Lead:{Id=null, Email=john.doe@incountry.com, IsDeleted=false, MasterRecordId=null, Salutation=null, Website=null, PhotoUrl=null, De

2 - Trigger.new: (Lead:{Id=null, Email=hidden@hidden.hidden, IsDeleted=false, MasterRecordId=null, Salutation=null, Website=null, PhotoUrl=null, De

handler.handleAfterInsert();

The handler.handleAfterInsert(); method must be executed right after execution of the insert/update operation. The handleAfterInsert method executes the subsequent method to transfer protected records to the InCountry platform. The handleAfterInsert method makes call to the InCountry REST endpoint.

note

The handler.handleAfterInsert(); method executes a future method (that in turns makes a callout). This future method is not executed from the batch or future contexts. If you need to have synchronization in a batch class, please refer to the Records synchronization in batch context section.

Asynchronous update of records with protected values from the InCountry platform

The handler.handleAfterInsert(); executes the asynchronous method that sends protected data values to the InCountry platform. Once a successful response is received the method executes a DML update of records with their non-hashed values in Salesforce. Before the DML statement, the disableTrigger flag is set as true (this is used to prevent the repeated (two-time) hashing of values).

InCountryReplicationTriggerHandler.disableTrigger = true;

During the trigger execution, the flag is set as true (InCountryReplicationTriggerHandler.disableTrigger == true). This is the only way to identify that non-hashed values are present in the transaction. You can run additional validations or other logic against PII fields when this flag is set as true.

Records synchronization in the batch context

Salesforce provides such tool as batch classes to process a large amount of data. To use this tool in the trigger-based replication model and get the business logic compliant, refer to the information below:

  1. The trigger-based replication model does not support the future and batch contexts in theInCountryReplicationTriggerHandler.cls class (this class is used in the template of triggers);

  2. To synchronize records in the batch context, you can use the Apex SDK.

Warning

Once the Apex SDK proceeds with the DML operation, it will run the trigger logic. The logic from trigger cannot be run from the batch context, because the records will be hashed and clear-text values will be lost. So, to avoid it, please add the flag to enable/disable the InCountry trigger logic to run from the batch context. See the following code examples.

Trigger modifications

note

This example is a part of the InCountry’s trigger template provided above.

InCountryReplicationTriggerHandler handler = new InCountryReplicationTriggerHandler(triggerType.getDescribe().getName());

// There is a flag to avoid the problems of sync from batch context
Boolean disableLogic = System.isBatch;

if (Trigger.isBefore && (Trigger.isInsert || Trigger.isUpdate)) {
// any Before trigger customer logic should be put before
// handler.handleBeforeInsert(); call

// prevent the InCountry trigger's logic
// to avoid the issues in a batch and to not lost the clear values
if (!disableLogic) {
handler.handleBeforeInsert();
}
} else if (Trigger.isAfter && (Trigger.isInsert || Trigger.isUpdate)) {
// prevent the InCountry trigger's logic
// to avoid the issues in a batch and to not lost the clear values
if (!disableLogic) {
handler.handleAfterInsert();
InCountryReplicationTriggerHandler.registerExecution(triggerType);
}
// any After trigger customer logic should be put after
// handler.handleAfterInsert(); call
} else if (Trigger.isAfter && Trigger.isUndelete) {
handler.handleAfterUndelete();
}

The batch class does not allow execution of future methods. That is why the InCountry trigger handler will not synchronize (execute the future method that makes callout to the InCountry platform) a record inserted or updated during batch processing. The Apex SDK should be utilized to implement the synchronization.

A sample batch class is below:

global class BatchLeadUpdate implements Database.batchable<Sobject>, Database.Stateful, Database.AllowsCallouts {

private final String OBJECT_NAME = 'Lead';

global BatchLeadUpdate(String recordId) {
...
}

global Iterable<sObject> start(Database.BatchableContext BC) {
...
List<String> fieldsToQuery = new List<String>();
fieldsToQuery.addAll(this.getCountryFields());
fieldsToQuery.addAll(this.getRegulatedFields());
...
return Database.query(query);
}

global void execute(Database.BatchableContext BC, List<sObject> scope) {
this.recordsToUpdate = ...;
}

List<sObject> recordsToUpdate;
global void finish(Database.BatchableContext BC) {
// sync records to InCountry
List<testInCountry1.InCountry.WriteRequest> writeResults = testInCountry1.InCountry.getInstance().write(this.recordsToUpdate);
// disable InCountry trigger handlers because records are already synced
testInCountry1.InCountryReplicationTriggerHandler.disableTrigger = true;
// update records in Salesforce
update this.recordsToUpdate;
}


private Set<String> getCountryFields() {
Set<String> regulatedFields = new Set<String>();
for (testInCountry1__Object_relationship__c orItem : [
SELECT testInCountry1__Country_field__c
FROM testInCountry1__Object_relationship__c
WHERE testInCountry1__Object_name__c = :OBJECT_NAME
]) {
regulatedFields.add(orItem.testInCountry1__Country_field__c);
}
return regulatedFields;
}

private Set<String> getRegulatedFields() {
Set<String> regulatedFields = new Set<String>();
for (testInCountry1__Object_relationship_fields__c orfItem : [
SELECT testInCountry1__Field_name__c
FROM testInCountry1__Object_relationship_fields__c
WHERE testInCountry1__Object_name__c = :OBJECT_NAME
]) {
regulatedFields.add(orfItem.testInCountry1__Field_name__c);
}
return regulatedFields;
}
}

They key point in the sample code above is the finish method where the proper usage of the Apex SDK and the DML operation is shown. First, the synchronization is done through the write method. Then, the InCountry trigger handler is disabled (to prevent the hashing of PII fields). Then, a regular DML operation is performed.

The getCountryFields and getRegulatedFields methods should be used to retrieve all necessary fields for the proper synchronization.