Skip to content

Instantly share code, notes, and snippets.

@gbutt
Last active February 6, 2022 15:24
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save gbutt/11151983 to your computer and use it in GitHub Desktop.
Save gbutt/11151983 to your computer and use it in GitHub Desktop.
apex scheduled dispatcher - this is how you can schedule code in SFDC without locking up all your classes
/***
Adapted from the great Dan Appleman.
For more on this and many other great patterns - buy his book - http://advancedapex.com/
This class can be used to schedule any scheduled job without risk of locking the class.
DO NOT CHANGE THIS CLASS! It is locked by the scheduler. Instead make changes to ScheduledHelper or your own IScheduleDispatched class
To use:
1) Create a new class to handle your job. This class should implement ScheduledDispatcher.IScheduleDispatched
2) Create a new instance of ScheduledDispatcher with the type of your new class.
3) Schedule the ScheduledDispatcher instead of directly scheduling your new class.
See ScheduledRenewalsHandler for a working example.
***/
global class ScheduledDispatcher implements Schedulable {
private Type targetType;
public ScheduledDispatcher(Type targetType) {
System.debug('Creating new dispatcher for class: ' + targetType.getName());
this.targetType = targetType;
}
global void execute(SchedulableContext sc) {
((IScheduleDispatched)targetType.newInstance()).execute(sc);
}
public interface IScheduleDispatched {
void execute(SchedulableContext sc);
}
}
@isTest
private class ScheduledDispatcherTest {
public without sharing class ScheduledRenewalsHandler
implements ScheduledDispatcher.IScheduleDispatched {
public void execute(SchedulableContext sc) {
ScheduledDispatcherTest.testFlag = true;
}
}
public static Boolean testFlag;
static testMethod void can_create_new_instance_of_scheduled_dispatcher() {
ScheduledDispatcher dispatcher = new ScheduledDispatcher(ScheduledRenewalsHandler.class);
system.assert(dispatcher != null);
testFlag = false;
dispatcher.execute(null);
System.assert(testFlag == true);
}
static testMethod void can_schedule_new_job() {
DateTime fireTime = DateTime.Now().addSeconds(10);
String nextFireCron = ScheduledHelper.createCronExpressionFromDateTime(fireTime);
Test.startTest();
ScheduledHelper.scheduleJob(ScheduledRenewalsHandler.class, nextFireCron);
Test.stopTest();
String jobName = ScheduledRenewalsHandler.class.getName();
List<CronTrigger> jobs = [
SELECT Id, CronJobDetail.Name, State, NextFireTime
FROM CronTrigger
WHERE CronJobDetail.Name = :jobName
];
system.assert(jobs.size() == 1);
system.debug('Job State: ' + jobs[0].State);
system.assert(jobs[0].State == 'WAITING');
}
static testMethod void can_abort_scheduled_job() {
DateTime fireTime = DateTime.Now().addSeconds(10);
String nextFireCron = ScheduledHelper.createCronExpressionFromDateTime(fireTime);
ScheduledHelper.scheduleJob(ScheduledRenewalsHandler.class, nextFireCron);
String jobName = ScheduledRenewalsHandler.class.getName();
List<CronTrigger> jobs = [
SELECT Id, CronJobDetail.Name, State, NextFireTime
FROM CronTrigger
WHERE CronJobDetail.Name = :jobName
];
system.assert(jobs.size() == 1);
Test.startTest();
ScheduledHelper.abortJob(jobName);
Test.stopTest();
jobs = [
SELECT Id, CronJobDetail.Name, State, NextFireTime
FROM CronTrigger
WHERE CronJobDetail.Name = :jobName
];
system.assert(jobs.size() == 0);
}
}
public with sharing class ScheduledHelper {
public static final String CRON_MIDNIGHT_FIRST_OF_THE_MONTH = '0 0 0 1 * ?';
/*
The bootstrap can be called by anonymous apex to schedule jobs.
*/
public static void scheduledBootstrap() {
scheduleJob(ScheduledRenewalsHandler.class, CRON_MIDNIGHT_FIRST_OF_THE_MONTH);
}
public static void scheduleJob(Type targetType, String cronExpression) {
String jobName = targetType.getName();
scheduleJob(targetType, jobName, cronExpression);
}
public static void scheduleJob(Type targetType, String jobName, String cronExpression) {
abortJob(jobName);
ScheduledDispatcher scheduledDispatcher = new ScheduledDispatcher(targetType);
System.schedule(jobName, cronExpression, scheduledDispatcher);
}
public static void abortJob(String jobName) {
Set<String> stateList = new Set<String>{'COMPLETED', 'ERROR', 'DELETED'};
List<CronTrigger> jobs = [
SELECT Id, CronJobDetail.Name, State, NextFireTime
FROM CronTrigger
WHERE CronJobDetail.Name = :jobName
AND State NOT IN :stateList
];
if (jobs.size()>0) {
System.abortJob(jobs[0].Id);
}
}
public static String createCronExpressionFromDateTime(DateTime fireTime) {
List<String> timeParts = new List<String>();
timeParts.add(String.valueof(fireTime.second()));
timeParts.add(String.valueof(fireTime.minute()));
timeParts.add(String.valueof(fireTime.hour()));
timeParts.add(String.valueof(fireTime.day()));
timeParts.add(String.valueof(fireTime.month()));
timeParts.add('?');
timeParts.add(String.valueof(fireTime.year()));
return String.join(timeParts, ' ');
}
}
public without sharing class ScheduledRenewalsHandler
implements ScheduledDispatcher.IScheduleDispatched {
public void execute(SchedulableContext sc) {
// do renewals logic here
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment