Last active
December 5, 2024 08:15
-
-
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
This file contains hidden or 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
/*** | |
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); | |
} | |
} |
This file contains hidden or 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
@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); | |
} | |
} |
This file contains hidden or 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
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, ' '); | |
} | |
} |
This file contains hidden or 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
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