There are various ways for integrating two salesforce orgs (Salesforce to Salesforce, SOAP with enterprise or partner wsdl, REST, middleware, appexchange etc) each with its own pros and cons. In this blog I aim to show a very specific simple use case where we want to send a file from one salesforce org to another with some additional metadata using a simple REST API callout from one salesforce org to another. Of course this can be done through multiple ways but in my opinion this is the fastest way to accomplish this and for very specific cases in which a large scale integration is not required.
Server
On the server side we need two things a Connected App that defines the authentication method and access policies and a simple apex rest service to accept the file upload request.
Connected App
Couple of important things to notice in the connected app is the "refresh_token" OAuth scope and the callback url from the client org. These two settings are important for setting up seamless authentication between the two orgs. The "refresh token" allows the client to keep refreshing the token and keep a session alive without user interaction and the callback url generated by the Auth. Provider on the client org provides a seamless authentication redirect.
MockFileUploadService Connected App
FileUploadService.cls
@RestResource(urlMapping='/fileUpload/*')
global with sharing class FileUploadService {
public class File {
public String body;
public String contentType;
public String name;
}
public class FileContainer {
public File_Metadata__c metadata;
public File file;
}
public class FileUploadRequest {
public List<FileContainer> files;
}
@HttpPost
global static void doPost() {
//json will be taken directly from RestContext
FileUploadRequest payload = (FileUploadRequest)System.JSON.deserialize(
RestContext.request.requestBody.tostring(),
FileUploadRequest.class);
List<FileContainer> fileContainers = payload.files;
//Parse Metadata and files
Map<String, HOG_File_Metadata__c> metadataMap = new Map<String, HOG_File_Metadata__c>();
Map<String, File> fileMap = new Map<String, File>();
for(FileContainer container : fileContainers) {
System.debug('Metadata Name: ' + container.metadata.Name);
metadataMap.put(container.metadata.Name, container.metadata);
fileMap.put(container.metadata.Name, container.file);
//clear ids
container.metadata.Id = null;
}
//Setting state for transaction control
Boolean revertTransaction = false;
Savepoint sp = Database.setSavepoint();
//Insert Metadata
System.debug('metadataMap.values(): ' + metadataMap.values());
List<Database.SaveResult> saveResults = Database.insert(metadataMap.values(), false);
//Insert Attachments
List<Attachment> attachmentsToInsert = new List<Attachment>();
for (String metadataName : metadataMap.keySet()) {
HOG_File_Metadata__c metadata = metadataMap.get(metadataName);
File file = fileMap.get(metadataName);
attachmentsToInsert.add(
new Attachment(parentId = metadata.Id, name = file.name,
ContentType = file.ContentType,
Body = EncodingUtil.base64Decode(file.body)));
}
saveResults = Database.insert(attachmentsToInsert, false);
RestContext.response.statusCode = 200;
for(Database.SaveResult result : saveResults) {
if(!result.isSuccess()) {
RestContext.response.statusCode = 500;
revertTransaction = true;
}
}
RestContext.response.responseBody = Blob.valueOf(JSON.serialize(saveResults));
if(revertTransaction) Database.rollback(sp);
}
}
|
Client
On the client side all you need is a Named Credential configured with the consumer key and secret of the connected app on the server and an authentication provide authenticated with a valid user's credentials on the server org with access to the resources served by the REST service.
Auth. Provider
The important things to note in the Auth. Provider is the default scopes "refresh_token" this allows the auth provided to keep the session alive by refreshing the token generated and the callback URL generated (configured in the server connected app) allows for seamless authentication redirect.
MockFileUploadServiceAuth Auth. Provider
Named Credential
Important things to note in the named credential is the "Generate Authorization Header" checkbox. This will automatically generate the authentication header in a callout to the server done using the named credential.
MockFileUploadServiceCred Named Credential
Client Usage
HttpRequest httpReq = new HttpRequest();
httpReq.setMethod('POST');
httpReq.setEndpoint('callout:MockFileUploadServiceCred/services/apexrest/fileUpload');
httpReq.setHeader('Content-Type', 'application/json');
httpReq.setBody('{"files": [' +
'{' +
'"metadata": {' +
'"attributes": {' +
'"type": "File_Metadata__c"' +
'},' +
'"Folder__c": "Notification",' +
'"Subfolder__c": "Maintenance Reports",' +
'"Content_Group__c": "Functional Business Management",' +
'"Content_Type__c": "Report",' +
'"Business_Function__c": "Facility Operations and Maintenance",' +
'"Document_Type__c": "Report",' +
'"File_Label__c": "NO - Final Report - 2018-11-12 00:00:00"' +
'},' +
'"file": {' +
'"name": "NO - Final Report - 2018-11-12 00:00:00",' +
'"contentType": "text\/plain",' +
'"body": "TG9yZW0gSXBzdW0gaXMgc2ltcGx5IGR1bW15IHRleHQgb2YgdGhlIHByaW50aW5nIGFuZC' +
'B0eXBlc2V0dGluZyBpbmR1c3RyeS4gTG9yZW0gSXBzdW0gaGFzIGJlZW4gdGhlIGluZHVzdHJ5' +
'J3Mgc3RhbmRhcmQgZHVtbXkgdGV4dCBldmVyIHNpbmNlIHRoZSAxNTAwcywgd2hlbiBhbiB1bm' +
'tub3duIHByaW50ZXIgdG9vayBhIGdhbGxleSBvZiB0eXBlIGFuZCBzY3JhbWJsZWQgaXQgdG8g' +
'bWFrZSBhIHR5cGUgc3BlY2ltZW4gYm9vay4gSXQgaGFzIHN1cnZpdmVkIG5vdCBvbmx5IGZpdm' +
'UgY2VudHVyaWVzLCBidXQgYWxzbyB0aGUgbGVhcCBpbnRvIGVsZWN0cm9uaWMgdHlwZXNldHRp' +
'bmcsIHJlbWFpbmluZyBlc3NlbnRpYWxseSB1bmNoYW5nZWQuIEl0IHdhcyBwb3B1bGFyaXNlZC' +
'BpbiB0aGUgMTk2MHMgd2l0aCB0aGUgcmVsZWFzZSBvZiBMZXRyYXNldCBzaGVldHMgY29udGFp' +
'bmluZyBMb3JlbSBJcHN1bSBwYXNzYWdlcywgYW5kIG1vcmUgcmVjZW50bHkgd2l0aCBkZXNrdG' +
'9wIHB1Ymxpc2hpbmcgc29mdHdhcmUgbGlrZSBBbGR1cyBQYWdlTWFrZXIgaW5jbHVkaW5nIHZl' +
'cnNpb25zIG9mIExvcmVtIElwc3VtLg=="' +
'}' +
'}' +
']' +
'}');
Http http = new Http();
HttpResponse resp = http.send(httpReq);
System.debug('resp: ' + resp);
System.debug('resp.body: ' + resp.getBody());
|
Conclusion
For very specific use cases where a full fledged integration is not required a simple rest api from salesforce to salesforce can be the answer. It has helped me in multiple cases in a multitenant org environment. I hope this blog can provide you some guidance on how to setup a simple rest api connection between two salesforce orgs.
Comments