Edit: I've built an after insert trigger on ContentDocumentLink. On cropredy's direction, I've bulkified my code and I think improved it generally. It "works" in that it accomplishes my business requirement, however my insert relies on a try-catch handler that I should learn how to do without.
The goal: whenever someone creates a ContentNote or attaches a File (a ContentDocument), I want to automatically share it with a Chatter group that I created, called ContentNote Administration. This ensures we'll have back-end visibility on all notes and files.
Here are the steps I want my code to execute:
- When someone creates a new Document (like a File upload or a ContentNote), Salesforce inserts a record into ContentDocumentLink, relating that record (say, ContentNote) with the owner. Then, I want the trigger to fire.
- The trigger passes the ContentDocumentLink sObject, along with the name of the link-to object type and name (in this case, a CollaborationGroup named 'ContentNote Administration').
- If a ContentDocumentLink record already exists relating this ContentDocumentId with this LinkedEntityId, don't insert.
- If the current insert relates this ContentDocumentId with this LinkedEntityId, don't insert - I believe this is necessary to avoid infinite loop.
- Else, build a new ContentDocumentLink record and insert it.
Here is my trigger:
trigger vwContentDocumentLinkAction on ContentDocumentLink (before delete, before insert, before update, after delete, after insert, after update) {
if (Trigger.isAfter) {
if (Trigger.isInsert) {
List<ContentDocumentLink> cdLinks = new List<ContentDocumentLink>();
SObject cGID = [SELECT Id FROM CollaborationGroup WHERE Name = 'ContentNote Administration' LIMIT 1];
for (ContentDocumentLink cdl : Trigger.new) {
if(vwContentNoteVisible.shouldShare(cdl, cGID)) { // don't add if it's a recursive trigger
cdLinks.add(cdl);
}
}
vwContentNoteVisible.shareWithCollaborationGroup(cdLinks, cGID);
}
}
}
Here is my class:
public class vwContentNoteVisible {
public static void shareWithCollaborationGroup(List<ContentDocumentLink> cdlNeedLinks, SObject collabGroup) {
for (ContentDocumentLink cdl : cdlNeedLinks) {
ContentDocumentLink cdlNewLink = new ContentDocumentLink();
cdlNewLink.ContentDocumentId = cdl.ContentDocumentId;
cdlNewLink.LinkedEntityId = collabGroup.Id;
cdlNewLink.ShareType = 'V';
cdlNewLink.Visibility = 'AllUsers';
// exception handler: catches DML Field_Integrity_Exception on recursion trigger
// This is because ContentDocumentLink doesn't permit duplicates.
try {
insert(cdlNewLink);
} catch(DmlException e) { System.debug('Exception occurred on insert: ' + e.getMessage()); }
// do without this try catch, once you build your set of DocumentIDs
}
}
public static Boolean shouldShare(ContentDocumentLink thisLink, SObject collabGroup) {
if(thisLink.LinkedEntityId == collabGroup.Id) {
return false;
} else {
return true;
}
}
}
My try-catch handles the following Field Integrity Exception that results because the insert recursively calls this trigger:
caused by: System.DmlException: Insert failed. First exception on row 0; >first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, >vwContentDocumentLink_sharing: execution of AfterInsert
caused by: System.DmlException: Insert failed. First exception on row 0; >first error: FIELD_INTEGRITY_EXCEPTION, Document with ID: >0690j0000008nkf is already linked with the entity with ID: >0F90j0000008T0T: Linked Entity ID: [LinkedEntityId]
What are the best strategies for handling this recursive insert without relying on error handling? Any help would be appreciated.