InCountry logo
mobile-nav
Search
  • Products
    • Products
      • InCountry for Salesforce
      • Data Residency-as-a-Service
      • Alibaba Cloud InCountry Service
      • Compliance and security
    • Gateways
      • Email
      • Payment Vault
      • Web Forms
      • HTML
    • Developers
      • REST API
      • SDK
  • Solutions
    • Automotive
    • Energy
    • Financial services
    • Healthcare
    • Retail
    • Technology
    • Latest success story
      • IBM Consulting
  • Integrations
    • Cegid
    • Intertrust
    • MuleSoft
    • PayPal
    • Salesforce
    • ServiceNow
    • Stripe
    • Veeva Systems
    • Yandex
  • Resources
    • Country compliance
    • Documentation
    • Library
    • Partners
    • Pricing
  • About
    • News and Blog
    • Careers
    • Contact Us
    • FAQ
    • Leadership
  • Login
  • Schedule a Demo

›Administrator's guide

Home
  • InCountry Platform
Portal
  • Getting started
  • Documentation
    • Dashboard
    • Managing environments
    • Managing SDK credentials and services
    • Managing Border configuration
    • Managing payment vaults
    • Managing email gateways
    • Managing resident functions
    • Managing file imports
    • Managing profile and organization
    • Managing users
    • Managing encryption keys
  • Release notes
Border
  • Documentation
  • Release notes
REST API
  • Documentation
  • How to test CRUD requests through REST API
  • Release notes
Resident Functions
  • Documentation
Salesforce
  • About
  • Overview
  • Quick start guide for three-model package
  • Quick start guide for legacy package
  • Administrator's guide
    • Managing the package
    • Managing permissions
    • Managing OAuth2 authentication and authorization
    • Managing certificates
    • Registering CSP Trusted Sites
    • Managing InCountry Endpoints
    • Managing REST endpoints
    • Managing InCountry flags
    • Loading the application
    • Managing data regulation policies
    • Managing protected fields
    • Hashing the UserName field
    • Managing custom objects
    • Replacing standard elements
    • Configuring record search
    • Managing components
    • Setting up Salesforce Experience Cloud
    • Managing resident functions
    • Managing InCountry cache
    • Managing Apex triggers
    • Managing record synchronization
    • Managing web forms
    • Tracking changes to data regulation policies and regulated fields
    • Using Email-to-Case feature
    • Debugging
    • Migrating data from one Salesforce organization to another
  • Developer’s guide
    • Apex SDK
    • JavaScript API
    • Retrieving record statistics
    • Tracking field history
  • User's guide
    • Working with protected fields
    • Sending compliant email messages
    • Importing data into Salesforce
    • Migrating records
    • Managing audit reports
    • Converting leads
    • Managing reports
    • Using formula fields
    • Using frontend validations
    • FAQ
    • Release notes
Payment Vault
  • Documentation
BYOK
  • Documentation
FAQ
  • Get started with the platform
  • Integration options
  • Data regulation models
  • Limits and quotas
  • Video tutorials
Service Status
  • Status

Managing Apex triggers

Hide Heading
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.

← Managing InCountry cacheManaging record synchronization →
  • Lead trigger example
    • handler.handleBeforeInsert();
    • handler.handleAfterInsert();
  • Asynchronous update of records with protected values from the InCountry platform
  • Records synchronization in the batch context
    • Trigger modifications
InCountry logo blue
© InCountry 2022.
All rights reserved. InCountry, Inc
  • PRIVACY POLICY
  • TERMS OF SERVICE
  • Social share
    • YouTube logo
    • Facebook logo
    • Twitter logo
    • LinkedIn
  • Column 1
    • Products
      • Products
        • InCountry for Salesforce
        • Data Residency-as-a-Service
        • Alibaba Cloud InCountry Service
        • Compliance and security
      • Gateways
        • Email
        • Payment Vault
        • Web Forms
        • HTML
      • Developers
        • REST API
        • SDK
  • Column 2
    • Solutions
      • Automotive
      • Energy
      • Financial services
      • Healthcare
      • Retail
      • Technology
    • Integrations
      • Cegid
      • Intertrust
      • MuleSoft
      • PayPal
      • Salesforce
      • ServiceNow
      • Stripe
      • Veeva Systems
      • Yandex
  • Column 3
    • Resources
      • Country compliance
      • Documentation
      • Library
      • Partners
      • Pricing
    • About
      • News and Blog
      • Careers
      • Contact Us
      • FAQ
      • Leadership