2 things that surprised us while developing Attachment Exchange Interface for Salesforce instances with Mulesoft.

Our Task:

Build interface for synchronization of Cases with attachments between two Salesforce instances. Main assumption of the implementation:

  •      Attchaments linked directly to case and also attachments from emails are process separately. Exchange of Attachments should be triggered by successful transfer of Case.
  •      So generally first Case exange flow is process and when it is successfully done, Attachments exchange flow is triggered.

Today we are focused on Attachment part. Implementation for the first sight seems to be easy. All we need to do is to query needed Attachment data basing on Case Id from one Salesforce instance and insert it to second Salesforce instead.

Unexpected issue:

The main problem was proper approach for querying Attachments data. As we know the best practice for that is to use for that Content Version object. Because we need to retrieve attachments that are linked directly with Case as well as attachments from linked to Case Email Messages, we decided to use Salesforce Composite connector  (Execute Composite Request) with this code in Composite Request body:

%dw 2.0 
output application/json
--- 
{
	"allOrNone": false,
	"compositeRequest":[
                {
			"method": "GET",
			"referenceId": "getCase",
			"url": "/services/data/v49.0/query/?q=SELECT+Id+FROM+Case+WHERE+ExternalCaseNumber__c='"++ vars.caseId ++ "'"
		},
		{
			"method": "GET",
			"referenceId": "getCaseAttachments",
			"url": "/services/data/v49.0/query/?q=SELECT+ContentDocumentId,VersionData,Title,FileType,FileExtension,FirstPublishLocationId,ContentSize,Id+FROM+ContentVersion+WHERE+FirstPublishLocationId= '" ++ "@{getCase.records[0].Id}" ++ "'"
		},
		{
			"method": "GET",
			"referenceId": "getEmailsAttachments",
			"url": "/services/data/v46.0/query/?q=SELECT+ContentDocumentId,VersionData,Title,FileType,FileExtension,FirstPublishLocationId,ContentSize,Id+FROM+ContentVersion+WHERE+FirstPublishLocationId+IN+(SELECT+Id+FROM+EmailMessage+WHERE+ParentId= '" ++ "@{getCase.records[0].Id}" ++ "')"
                }
         ]
}

WHERE: vars.caseId is provided to API, and store in variable Case Id.


We were more than happy that with one request we are able to get all needed data for transferring them. But after tests we have realized that with this approach we are getting only Attachments that were created by our User. It was our first surprise.

Solution:

To get all Attachments from Case we need to first get all ContentDocumentId using Content Document Link object. Content Document Link object represents  the link between a Salesforce CRM Content document or Salesforce file and where it's shared. A file can be shared with other users, groups, records, and Salesforce CRM Content libraries.

%dw 2.0 
output application/json
--- 
{
	"allOrNone": false,
	"compositeRequest":[
		{
			"method": "GET",
			"referenceId": "getCase",
			"url": "/services/data/v49.0/query/?q=SELECT+Id+FROM+Case+WHERE+ExternalCaseNumber__c='"++ vars.caseid ++ "'"
		},
		{
			"method": "GET",
			"referenceId": "getCaseAttachments",
			"url": "/services/data/v49.0/query/?q=SELECT+ContentDocumentId+FROM+ContentDocumentLink+WHERE+LinkedEntityId='" ++ "@{getCase.records[0].Id}" ++ "'"
		}, 
		{
			"method": "GET",
			"referenceId": "getEmailsAttachments",
			"url": "/services/data/v49.0/query/?q=SELECT+ContentDocumentId+FROM+ContentDocumentLink+WHERE+LinkedEntityId+IN+(SELECT+Id+FROM+EmailMessage+WHERE+ParentId='" ++ "@{getCase.records[0].Id}" ++ "')"
		}
        ]
}

With this approach we were able to retrieve all Attachments linked to
Case.

In our design we use VM module to publish every Attachment's ContentDocumentId  value and process them one by one in asynchronous way. After that we have second Composite request to get Content Version data for specific ContentDocumentId:

{
	"allOrNone": false,
	"compositeRequest":[
                {
			"method": "GET",
			"referenceId": "getAttachmentData",
			"url": "/services/data/v49.0/query/?q=SELECT+ContentDocumentId,VersionData,Title,FileType,FileExtension,FirstPublishLocationId,ContentSize,Id+FROM+ContentVersion+WHERE+ContentDocumentId ='"++ vars.contentDocumentId ++ "'"
		}
         ]
}
WHERE: 
vars.contentDocumentId is provided to VM Listener, and store in variable Content Document Id.

We were close to celebrate! But wait… look at the VersionData value:
/services/data/v49.0/sobjects/ContentVersion/0770Q000001QiL6MAK/VersionData

After investigation we have realised that VersionData is returned as sObject Blob Retrieve resource. We have made research and it is bevaiors of REST Salesforce API which composite connector is using.

Second surprise!

With Salesforce Composite Request connector we are using SOAP API. So in the end we have used standard Salesforce Query Connector which use SOAP API: 

WHERE: :contentDocumentId is bind variable to vars.contentDocumentId


SELECT ContentDocumentId,VersionData,Title,FileType,FileExtension,FirstPublishLocationId,ContentSize,Id FROM ContentVersion WHERE ContentDocumentId = ':contentDocumentId'

And now we can finally celebrate 😊


See you soon,
Monika on behalf of Salesforce Freaks ;)


Comments

Popular posts from this blog

How to start work with Anypoint Studio?

Security Vulnerabilities in Salesforce

Fetch dependent picklist values depending on record type