Skip to content

Instantly share code, notes, and snippets.

@pcon
Last active January 24, 2017 16:38
Show Gist options
  • Save pcon/10196436 to your computer and use it in GitHub Desktop.
Save pcon/10196436 to your computer and use it in GitHub Desktop.
Object Snapshotting
trigger CaseTrigger on Case (after insert) {
ObjectSnapshotUtils.createSnapshots(Trigger.new);
}
<?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>
/**
* 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