To escape the current context and run as the "Automated Process" user (high priviliges), do this:
First define a platform event. For example: Async_Message__e
with a paylod and a type.
Then define an AsyncActionUtil class. This will contain a way of inserting these platform events and handling them after insert. The class should contain an insert method:
public static void insertAsyncMessage(String type, String payload) {
Async_Message__e[] messages = new List<Async_Message__e>{
new Async_Message__e(Type__c = type, Payload__c = payload)
};
// Call method to publish events
Database.SaveResult[] results = EventBus.publish(messages);
}
The handle insert method (called from a trigger)
public static void handle(Async_Message__e[] messages) {
for (Async_Message__e message : (Async_Message__e[]) Trigger.new) {
handleAsyncMessage(message);
}
}
public static void handleAsyncMessage(Async_Message__e message) {
try {
switch on message.Type__c {
when 'Callable' {
Map<String, Object> callableParams = (Map<String, Object>) JSON.deserializeUntyped(
message.Payload__c
);
String className = (String) callableParams.get('className');
String methodName = (String) callableParams.get('methodName');
String params = (String) callableParams.get('params');
Map<String, Object> paramsMap = (Map<String, Object>) JSON.deserializeUntyped(params);
//make the call!
Callable someClass = (Callable) Type.forName(className).newInstance();
someClass.call(methodName, paramsMap);
}
}
} catch (Exception e) {
System.debug('ASYNC EXCEPTION: ' + 'Line ' + e.getLineNumber() + ' ' + e.getMessage());
}
}
"Callable" is just a string type added to the Async_Message__e
on insert so that this object can be used for multiple porposes.
The Payload__c is just a JSON encoded string with any sort of data.
The trigger looks like this:
trigger AsyncMessageTrigger on Async_Message__e(after insert) {
AsyncActionUtil.handle(Trigger.new);
}
The Callable
class (implements the Callable
interface) uses the AsyncActionUtil like this:
//completely bypasses any restrictions on the user running this.
AsyncActionUtil.enqueueCallable(
'MyClass',
'saveSObjects',
new Map<String, Object>{'sObjects' => caseObjects}
);
The saveSObjects
method is a not actually a method - it's a action that invokes something on the Callable
class and looks like this:
// Dispatch actual methods
public Object call(String action, Map<String, Object> args) {
switch on action {
when 'saveSObjects' {
try {
Object[] objs = (Object[]) args.get('sObjects');
SObject[] sobjs = (SObject[]) JSON.deserialize(JSON.serialize(objs), SObject[].class);
Database.insert(sobjs, false);
} catch (Exception e) {
return null;
}
}
when else {
throw new YourCustomException('Callable Method not implemented');
}
}
return null;
}
So in short:
- Originating class uses AsyncActionUtil to insert a Platform Event.
- Originating class is a
Callable
- The Platform Event is handled by AsyncActionUtil
- AsyncActionUtil calls the action on the Callable class