Last active
January 24, 2017 16:38
-
-
Save pcon/10196436 to your computer and use it in GitHub Desktop.
Object Snapshotting
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
trigger CaseTrigger on Case (after insert) { | |
ObjectSnapshotUtils.createSnapshots(Trigger.new); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata"> | |
<actionOverrides> | |
<actionName>Accept</actionName> | |
<type>Default</type> | |
</actionOverrides> | |
<actionOverrides> | |
<actionName>Clone</actionName> | |
<type>Default</type> | |
</actionOverrides> | |
<actionOverrides> | |
<actionName>Delete</actionName> | |
<type>Default</type> | |
</actionOverrides> | |
<actionOverrides> | |
<actionName>Edit</actionName> | |
<type>Default</type> | |
</actionOverrides> | |
<actionOverrides> | |
<actionName>List</actionName> | |
<type>Default</type> | |
</actionOverrides> | |
<actionOverrides> | |
<actionName>New</actionName> | |
<type>Default</type> | |
</actionOverrides> | |
<actionOverrides> | |
<actionName>Tab</actionName> | |
<type>Default</type> | |
</actionOverrides> | |
<actionOverrides> | |
<actionName>View</actionName> | |
<type>Default</type> | |
</actionOverrides> | |
<deploymentStatus>Deployed</deploymentStatus> | |
<enableActivities>false</enableActivities> | |
<enableEnhancedLookup>false</enableEnhancedLookup> | |
<enableFeeds>false</enableFeeds> | |
<enableHistory>false</enableHistory> | |
<enableReports>false</enableReports> | |
<fields> | |
<fullName>Case__c</fullName> | |
<deleteConstraint>SetNull</deleteConstraint> | |
<externalId>false</externalId> | |
<label>Case</label> | |
<referenceTo>Case</referenceTo> | |
<relationshipLabel>Object Snapshots</relationshipLabel> | |
<relationshipName>Object_Snapshots</relationshipName> | |
<required>false</required> | |
<type>Lookup</type> | |
</fields> | |
<fields> | |
<fullName>JSON_Data_0__c</fullName> | |
<externalId>false</externalId> | |
<label>JSON Data 0</label> | |
<length>32768</length> | |
<type>LongTextArea</type> | |
<visibleLines>3</visibleLines> | |
</fields> | |
<fields> | |
<fullName>JSON_Data_1__c</fullName> | |
<externalId>false</externalId> | |
<label>JSON Data 1</label> | |
<length>32768</length> | |
<type>LongTextArea</type> | |
<visibleLines>3</visibleLines> | |
</fields> | |
<fields> | |
<fullName>JSON_Data_2__c</fullName> | |
<externalId>false</externalId> | |
<label>JSON Data 2</label> | |
<length>32768</length> | |
<type>LongTextArea</type> | |
<visibleLines>3</visibleLines> | |
</fields> | |
<fields> | |
<fullName>JSON_Data_3__c</fullName> | |
<externalId>false</externalId> | |
<label>JSON Data 3</label> | |
<length>32768</length> | |
<type>LongTextArea</type> | |
<visibleLines>3</visibleLines> | |
</fields> | |
<fields> | |
<fullName>JSON_Data_4__c</fullName> | |
<externalId>false</externalId> | |
<label>JSON Data 4</label> | |
<length>32768</length> | |
<type>LongTextArea</type> | |
<visibleLines>3</visibleLines> | |
</fields> | |
<fields> | |
<fullName>JSON_Data_5__c</fullName> | |
<externalId>false</externalId> | |
<label>JSON Data 5</label> | |
<length>32768</length> | |
<type>LongTextArea</type> | |
<visibleLines>3</visibleLines> | |
</fields> | |
<fields> | |
<fullName>JSON_Data_6__c</fullName> | |
<externalId>false</externalId> | |
<label>JSON Data 6</label> | |
<length>32768</length> | |
<type>LongTextArea</type> | |
<visibleLines>3</visibleLines> | |
</fields> | |
<fields> | |
<fullName>JSON_Data_7__c</fullName> | |
<externalId>false</externalId> | |
<label>JSON Data 7</label> | |
<length>32768</length> | |
<type>LongTextArea</type> | |
<visibleLines>3</visibleLines> | |
</fields> | |
<fields> | |
<fullName>JSON_Data_8__c</fullName> | |
<externalId>false</externalId> | |
<label>JSON Data 8</label> | |
<length>32768</length> | |
<type>LongTextArea</type> | |
<visibleLines>3</visibleLines> | |
</fields> | |
<fields> | |
<fullName>JSON_Data_9__c</fullName> | |
<externalId>false</externalId> | |
<label>JSON Data 9</label> | |
<length>32768</length> | |
<type>LongTextArea</type> | |
<visibleLines>3</visibleLines> | |
</fields> | |
<fields> | |
<fullName>Object_Name__c</fullName> | |
<externalId>false</externalId> | |
<label>Object Name</label> | |
<length>255</length> | |
<required>true</required> | |
<type>Text</type> | |
<unique>false</unique> | |
</fields> | |
<label>Object Snapshot</label> | |
<nameField> | |
<displayFormat>OS-{0000000}</displayFormat> | |
<label>Object Snapshot Name</label> | |
<type>AutoNumber</type> | |
</nameField> | |
<pluralLabel>Object Snapshots</pluralLabel> | |
<searchLayouts/> | |
<sharingModel>ReadWrite</sharingModel> | |
</CustomObject> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Utility methods for use with the Object Snapshot object | |
* | |
* @author Patrick Connelly | |
*/ | |
public with sharing class ObjectSnapshotUtils { | |
public static String JSON_DELIMITER = '[delimiter]'; | |
public static Integer JSON_FIELD_SIZE = 32768 - (JSON_DELIMITER.length() * 2); | |
public static Integer JSON_FIELD_COUNT = 10; | |
public static final String JSON_FIELD_TEMPLATE = 'JSON_Data_{0}__c'; | |
public static Boolean SPARSE_JSON = true; | |
public static final String SNAPSHOT_VERSION = '20120625'; | |
public static final String SNAPSHOT_VERSION_LABEL = 'snapshot_version'; | |
public static final String SNAPSHOT_SUCCESS_LABEL = 'snapshot_success'; | |
public static final String SNAPSHOT_MESSAGE_LABEL = 'snapshot_message'; | |
public static final String MSG_JSON_TO_LARGE = 'JSON data too large to store'; | |
public static final Map<String, Object> SNAPSHOT_INFO = new Map<String, Object> { | |
SNAPSHOT_VERSION_LABEL => SNAPSHOT_VERSION, | |
SNAPSHOT_SUCCESS_LABEL => true, | |
SNAPSHOT_MESSAGE_LABEL => '' | |
}; | |
public static final Map<String, Object> SNAPSHOT_INFO_JSON_TO_LARGE = new Map<String, Object> { | |
SNAPSHOT_VERSION_LABEL => SNAPSHOT_VERSION, | |
SNAPSHOT_SUCCESS_LABEL => false, | |
SNAPSHOT_MESSAGE_LABEL => MSG_JSON_TO_LARGE | |
}; | |
// These fields should never be added into the field map | |
public static final Map<String, Set<String>> FIELD_BLACKLIST_MAP = new Map<String, Set<String>>{ | |
'Case' => new Set<String>{ | |
'lastvieweddate', | |
'lastreferenceddate' | |
} | |
}; | |
private static Map<String, Map<String, Object>> objectDescription = new Map<String, Map<String, Schema.sObjectField>>(); | |
/** | |
* Gets a map of field name to their values | |
* | |
* NOTE: Before calling this method, make sure that objectDescription has been populated | |
* for the called object. getSnapshot handles this, but if you are going to call | |
* this directly, make sure it's populated. | |
* | |
* @param obj The sObject to build the map from | |
* @return A map of field name to value | |
*/ | |
private static Map<String, Object> getMapOfAllFields(String objName, sObject obj) { | |
Map<String, Object> result = new Map<String, Object>(); | |
Set<String> fieldNames = objectDescription.get(objName).keySet(); | |
if (FIELD_BLACKLIST_MAP.containsKey(objName)) { | |
fieldNames.removeAll(FIELD_BLACKLIST_MAP.get(objName)); | |
} | |
for (String fieldName: fieldNames) { | |
if ( | |
!SPARSE_JSON || ( | |
obj.get(fieldName) != null && | |
String.valueOf(obj.get(fieldName)).trim() != '' | |
) | |
) { | |
result.put(fieldName, obj.get(fieldName)); | |
} | |
} | |
return result; | |
} | |
/** | |
* Adds the delimiter to the start and end of the string | |
* | |
* NOTE: This is done because SFDC trims whitespace from the start/end of | |
* All fields | |
* | |
* @param data The string | |
* @return The appended data | |
*/ | |
public static String appendDelimiter(String data) { | |
return JSON_DELIMITER + data + JSON_DELIMITER; | |
} | |
/** | |
* Converts a string of json data into an object snapshot | |
* | |
* @param jsonData The json data | |
* @return The object snapshot | |
*/ | |
private static ObjectSnapshot__c jsonToSnapshot(Map<String, Object> fieldMap) { | |
ObjectSnapshot__c result = new ObjectSnapshot__c(); | |
String jsonData = JSON.serialize(fieldMap); | |
// Figure out if we have enough room in our fields for all the json data | |
Integer numberOfFieldsRequired = (Integer)(Math.floor(jsonData.length() / JSON_FIELD_SIZE)); | |
if (numberOfFieldsRequired >= JSON_FIELD_COUNT) { | |
fieldMap = SNAPSHOT_INFO_JSON_TO_LARGE; | |
jsonData = JSON.serialize(fieldMap); | |
} | |
for (Integer lowerBound = 0; lowerBound < jsonData.length(); lowerBound += JSON_FIELD_SIZE) { | |
Integer upperBound = ((lowerBound + JSON_FIELD_SIZE) > jsonData.length()) ? jsonData.length() : lowerBound + JSON_FIELD_SIZE; | |
Integer index = (Integer)(Math.floor(lowerBound / JSON_FIELD_SIZE)); | |
String fieldName = String.format(JSON_FIELD_TEMPLATE, new List<String>{String.valueOf(index)}); | |
String field = appendDelimiter(jsonData.subString(lowerBound, upperBound)); | |
result.put(fieldName, field); | |
} | |
return result; | |
} | |
/** | |
* Removes the delimiter from the start and end of the string | |
* | |
* @param data The data | |
* @return The stipped down data | |
*/ | |
public static String removeDelimiter(String data) { | |
if (data == null) { | |
return data; | |
} | |
return data.removeStart(JSON_DELIMITER).removeEnd(JSON_DELIMITER); | |
} | |
/** | |
* Converts a Object Snapshot object to a json string | |
* | |
* @param snapshot The snapshot to convert | |
* @return The json data | |
*/ | |
public static String snapshotToJson(ObjectSnapshot__c snapshot) { | |
List<String> JSONDataList = new List<String>(); | |
for (Integer i = 0; i < ObjectSnapshotUtils.JSON_FIELD_COUNT; i += 1) { | |
String fieldName = String.format(ObjectSnapshotUtils.JSON_FIELD_TEMPLATE, new List<String>{String.valueOf(i)}); | |
String field = (String)(snapshot.get(fieldName)); | |
field = removeDelimiter(field); | |
JSONDataList.add(field); | |
} | |
return String.join(JSONDataList, ''); | |
} | |
/** | |
* Gets the snapshot object of an sObject | |
* | |
* @param obj The sObject to snapshot | |
* @return The snapshot | |
*/ | |
public static ObjectSnapshot__c getSnapshot(sObject obj) { | |
ObjectSnapshot__c result = new ObjectSnapshot__c(); | |
Schema.DescribeSObjectResult describeResult = obj.getSobjectType().getDescribe(); | |
String objName = describeResult.getName(); | |
// Doing this to reduce the number of field queries we make so we don't hit the limit of 100 | |
if (!objectDescription.containsKey(objName)) { | |
objectDescription.put(objName, describeResult.fields.getMap()); | |
} | |
Map<String, Object> fieldMap = getMapOfAllFields(objName, obj); | |
fieldMap.putAll(SNAPSHOT_INFO); | |
result = jsonToSnapshot(fieldMap); | |
result.Object_Name__c = objName; | |
return result; | |
} | |
/** | |
* Creates and inserts the snapshots of a case | |
* | |
* @param cases A list of cases to snapshot | |
*/ | |
public static void createSnapshots(List<Case> cases) { | |
List<ObjectSnapshot__c> snapshots = new List<ObjectSnapshot__c>(); | |
for (Case newCase: cases) { | |
ObjectSnapshot__c snapshot = ObjectSnapshotUtils.getSnapshot(newCase); | |
snapshot.Case__c = newCase.Id; | |
snapshots.add(snapshot); | |
} | |
if (!snapshots.isEmpty()) { | |
insert snapshots; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment