Org2Org File Transfer in salesforce (code written in destination org)



Last Updated: 09-11-2023
 
Let's define the terms we will use in this blog post:
  • Source org - The org that has all the files.
  • Current org (destination) - The destination org where all files are to be fetched from the source org. Because all code will be written in the destination org only; no coding will be done in the source org.
To upload files from one Salesforce org to another using Salesforce REST APIs, you can follow these general steps: 
  1. Get Session Token, Org Base URL of source org.
  2. Get Record Ids from which we need to get the related files.
  3. Hit the Salesforce REST APIs to fetch the files from source org. 
  4. Create Content Version in current org (destination).
  5. Create Content Document Link in current org (destination) which will associate the file with the record you want.

Before going to code let's make our current org ready, we will create 2 fields in our object level 
  1. Ext_Record_Id__c - The record id of the record in the source org that we need to retrieve the files from.
  2. Processed_CV_Ids__c - All of the source org's Content Version ids that are saved in the current org will be added to this field, separated by semi-colon.
We'll create two classes. There will be two types of classes: batch and queueable. they will be connected, and batch call will handle processing all content version data in source org and all content document link data associated with the provided record ids. The Queueable class is in charge of retrieving a single file, creating a content version and content document link, and saving it in the current org.

Batch Class: FetchFilesFromSourceOrgBatch

global class FetchFilesFromSourceOrgBatch implements Database.Batchable<sObject>, Database.AllowsCallouts { global Set<Id> currentOrgRecordIds; global String sourceSessionId; global String sourceDomainURL; global FetchFilesFromSourceOrgBatch(Set<Id> currentOrgRecordIds, String sourceSessionId, String sourceDomainURL){ this.currentOrgRecordIds = currentOrgRecordIds; this.sourceSessionId = sourceSessionId; this.sourceDomainURL = sourceDomainURL; } global Database.QueryLocator start(Database.BatchableContext BC) { String query = 'SELECT ID, Ext_Record_Id__c, Processed_CV_Ids__c FROM Opportunity WHERE id IN :currentOrgRecordIds'; return Database.getQueryLocator(query); } global void execute(Database.BatchableContext BC, List<Opportunity> oppList) { try { // Batch size is 1. Opportunity record = oppList[0]; String externalRecordId = record.Ext_Record_Id__c; String currentOrgRecordId = record.id; // Fetch all CDL associated with source record String query_CDL_URL = sourceDomainURL + '/services/data/v58.0/query?q=SELECT+id,ContentDocumentId+FROM+ContentDocumentLink+WHERE+LinkedEntityId=\''+externalRecordId+'\''; Http ht = new Http(); HttpRequest cdlReq = new HttpRequest(); cdlReq.setMethod('GET'); cdlReq.setEndpoint(query_CDL_URL); cdlReq.setHeader('Content-Type','application/json'); cdlReq.setHeader('Authorization','OAuth '+sourceSessionId); HttpResponse cdlRes = ht.send(cdlReq); FetchFilesFromSourceOrgBatch.CDLRecordsWrapper cdlObj = new FetchFilesFromSourceOrgBatch.CDLRecordsWrapper(); FetchFilesFromSourceOrgBatch.CDLRecordsWrapper resCDL = cdlObj.parse(cdlRes.getBody()); String cdIds = '('; for(cdlRecords cdl : resCDL.records) { cdIds += '\'' + cdl.ContentDocumentId + '\','; } if(cdIds.endsWith(',')) { cdIds = cdIds.substring(0, cdIds.length() - 1); } cdIds += ')'; String query_CV_URL = ''; if(record.Processed_CV_Ids__c!=null) { String processedCVId = '('; for(String cvId : record.Processed_CV_Ids__c.split(';')) { processedCVId += '\'' + cvId + '\','; } if(processedCVId.endsWith(',')) { processedCVId = processedCVId.substring(0, processedCVId.length() - 1); } processedCVId += ')'; query_CV_URL = sourceDomainURL + '/services/data/v58.0/query?q=SELECT+id,+title,+FileExtension+FROM+ContentVersion+WHERE+ContentDocumentId+IN+'+cdIds+'+AND+ID+NOT+IN+'+processedCVId+'+LIMIT+1'; } else { query_CV_URL = sourceDomainURL + '/services/data/v58.0/query?q=SELECT+id,+title,+FileExtension+FROM+ContentVersion+WHERE+ContentDocumentId+IN+'+cdIds+'+LIMIT+1'; } // Fetch all CV Id associated with source record Http ht2 = new Http(); HttpRequest cvReq = new HttpRequest(); cvReq.setMethod('GET'); cvReq.setEndpoint(query_CV_URL); cvReq.setHeader('Content-Type','application/json'); cvReq.setHeader('Authorization','OAuth '+sourceSessionId); HttpResponse cvRes = ht.send(cvReq); FetchFilesFromSourceOrgBatch.CVRecordsWrapper cvObj = new FetchFilesFromSourceOrgBatch.CVRecordsWrapper(); FetchFilesFromSourceOrgBatch.CVRecordsWrapper resCV = cvObj.parse(cvRes.getBody()); // We will fetch one file from source org at time and save it in current org. System.enqueueJob(new FetchFilesFromSourceOrgQueue(currentOrgRecordId, resCV.records[0], sourceSessionId, sourceDomainURL)); } catch (Exception ex) { System.debug('NO CV FOUND....'+ ex); } } global void finish(Database.BatchableContext BC) {} public class CDLRecordsWrapper{ public CDLRecords[] records; public CDLRecordsWrapper parse(String json){ return (CDLRecordsWrapper) System.JSON.deserialize(json, CDLRecordsWrapper.class); } } public class CDLRecords { public String Id; public String ContentDocumentId; } public class CVRecordsWrapper{ public CVRecord[] records; public CVRecordsWrapper parse(String json){ return (CVRecordsWrapper) System.JSON.deserialize(json, CVRecordsWrapper.class); } } public class CVRecord { public String Id; public String Title; public String FileExtension; } }

Queueable Class: FetchFilesFromSourceOrgQueue

public class FetchFilesFromSourceOrgQueue implements System.Queueable, Database.AllowsCallouts { private Id currentOrgRecordId; private FetchFilesFromSourceOrgBatch.CVRecord cvRecord; private String sourceSessionId; private String sourceDomainURL; public FetchFilesFromSourceOrgQueue(Id currentOrgRecordId, FetchFilesFromSourceOrgBatch.CVRecord cvRecord, String sourceSessionId, String sourceDomainURL) { this.currentOrgRecordId = currentOrgRecordId; this.cvRecord = cvRecord; this.sourceSessionId = sourceSessionId; this.sourceDomainURL = sourceDomainURL; } public void execute(System.QueueableContext context) { String getVersionDataURL = sourceDomainURL + '/services/data/v54.0/sobjects/ContentVersion/' +cvRecord.Id+ '/VersionData'; HttpRequest req0 = new HttpRequest(); req0.setEndpoint(getVersionDataURL); req0.setMethod('GET'); req0.setHeader('Content-Type','application/json'); req0.setHeader('Authorization','OAuth '+sourceSessionId); HTTPResponse res0 = new Http().send(req0); if (res0.getStatusCode() == 200) { // Create CV String pathOnClient = cvRecord.Title + '.' + cvRecord.FileExtension; ContentVersion cv = new ContentVersion( Title= cvRecord.Title, PathOnClient= pathOnClient, ContentLocation= 'S', VersionData= res0.getBodyAsBlob() ); insert cv; ContentVersion newCVRecord = [SELECT Id, ContentDocumentId FROM ContentVersion WHERE id = :cv.id LIMIT 1]; ContentDocumentLink cdl = new ContentDocumentLink( ContentDocumentId=newCVRecord.ContentDocumentId, LinkedEntityId=currentOrgRecordId, ShareType='I', Visibility='AllUsers' ); insert cdl; Opportunity record = [SELECT Id, Processed_CV_Ids__c FROM Opportunity WHERE id=:currentOrgRecordId LIMIT 1]; if(record.Processed_CV_Ids__c!=null) { record.Processed_CV_Ids__c += ';'+cvRecord.Id; } else { record.Processed_CV_Ids__c = cvRecord.Id; } update record; } } public class CDRecordsWrapper{ public CDRecords[] records; public CDRecordsWrapper parse(String json){ return (CDRecordsWrapper) System.JSON.deserialize(json, CDRecordsWrapper.class); } } class CDRecords { public String ContentDocumentId; } }


Thanks for reading till end, if you find this blog helpful share with your friends and add your valuable feedback.

Comments

Popular posts from this blog

How to Add a Dynamic Child List to Parent in Apex Class || Trigger using map

(Salesforce Apex 2024 Release) getSalesforceBaseUrl method is deprecated

A - Z Guide to Using the Wire Decorator in LWC Salesforce