AEM Scheduler with Config Factory
In this article, we will try to explain how to create an AEM scheduler with multiple configs so that the job can be scheduled for different cron expressions. This scenario will be helpful in case we need to run the same job at a different type with some different parameters. For Example, we can have a scenario where we need to run the same job to create the sitemap but the locales are different, hence we need different schedulers for the same.
For creating an AEM Scheduler we will be using R6 annotations. We will start
by creating a schedulerConfiguration.java using @ObjectClassDefinition and
@AttributeDefinition annotations. Please have a look at the below code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | @ObjectClassDefinition(name = "Configuration of the Scheduler") public @interface SchedulerConfiguration { @AttributeDefinition( name = "Name of the scheduler", type = AttributeType.STRING) String name(); @AttributeDefinition( name = "Cron Expression of the scheduler", type = AttributeType.STRING) String expression(); @AttributeDefinition( name = "Whether or not to run the scheduler concurrently", type = AttributeType.BOOLEAN) String concurrent(); @AttributeDefinition( name = "Whether or not the scheduler is enabled", type = AttributeType.BOOLEAN) String enabled(); @AttributeDefinition( name = "Any other distinct parameter for the job", type = AttributeType.STRING) String distinctParam(); } |
In here, we have defined 5 fields for the configuration, you can add additional fields based on the requirement.
- The first field is for the scheduler name and we are assuming it should be configured uniquely for each configuration added.
- The second is for the scheduler cron expression and it can be set as per the requirement.
- The third field indicates if the job for each scheduler configured can run concurrently or not. This means if you have a scheduler for 1 minute and the job starts executing, the next job will only run once the old one is finished (if the concurrent is unchecked).
- The fourth indicates if the scheduler is active or not.
- The last field is for any distinct parameter which we may need to use in our job. For Ex: We may end up with a scenario where we need to distinct locale value for each scheduler configuration.
Once we have the scheduler configuration ready, we can start writing the
AddScheduler.java. In this, we need to use the @Designate and @Activate
annotations. Also, we need to understand different scenarios which could
arise with multiple configurations, and what methods will be called from
AddScheduler.java on the execution of all the scenarios. What I see is
there could be three scenarios:
- We have a possibility to add a new configuration - When a new configuration is added, only the method denoted by @Activate will be called.
- We have a possibility to remove an old existing configuration - When an old configuration is removed, only the method denoted by @Deactivate will be called.
- We have a possibility to modify an old existing configuration - When an old configuration is modified, first @Deactivate will be called followed by @Activate.
Now, let's have a look at the code and then we will deep dive and see
how the above scenarios are handled:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | @Component(service = Runnable.class, immediate = true) @Designate(ocd = SchedulerConfiguration.class, factory = true) public class AddScheduler implements Runnable { private static final Logger LOG = LoggerFactory.getLogger(AddScheduler.class); @Reference private Scheduler scheduler; @Override public void run(){ //Do Nothing } @Activate protected void activate(SchedulerConfiguration config){ //Write a JOB final Runnable job = () -> { //Do something using the distinct parameter. LOG.info("Inside Run Method for the scheduler with name: {} and distinctParam: {}", config.name(), config.distinctParam()); } SchedulerOptions options = scheduler.EXPR(config.expression()); options.canRunConcurrently(config.concurrent()); options.name(config.name()); if(config.enabled()){ LOG.info("Adding Schduler with Name:{}", config.name()); scheduler.schedule(job, options); } } @Deactivate protected void deactivate(SchedulerConfiguration config){ LOG.info("Removing Scheduler with name: {}", config.name()); scheduler.unschedule(config.name()); } } |
- When we add any new configuration, then the code flow does not go to @Deactivate and only execute whatever is inside @Activate. This means it creates a job and then uses the scheduler expression, name, and concurrent property of the config to create the scheduler options. Further, it checks if the scheduler is enabled or not and if the scheduler is enabled, it will scheduler our scheduler based on the values in the configuration.
- When we remove any old configuration, the code flow only goes to @Deactivate. This means if the configuration is removed/deleted, the scheduler is unscheduled based on the scheduler's name. That is why we have assumed that the configurations should be added with a unique name.
- When any configuration is modified, then the code flow goes to @Deactivate first and then to the @Activate. That means if any value is modified then we first unschedule the old scheduler with the old configuration values, and then we move on to @Activate where it creates a fresh job with new values of the parameter configured and schedules a new scheduler with the new values of the parameter.
In this way, this code will take care of all our scenarios discussed
above.