Best Practices for Creating OSGi Scheduler in AEM

Problem statement:

The article aims to provide guidelines for creating OSGi schedulers in Adobe Experience Manager, addressing the following queries:

  • What is new with the AEM scheduler with OSGi R7/8 annotations?
  • How to use the scheduler service?
  • Why is enable/disable important on a scheduler?

Requirement:

The article describes the best practices for creating OSGi Schedulers in Adobe Experience Manager (AEM). A scheduler is used to schedule time/cron-based jobs in AEM, and it executes the jobs. The article provides information on creating a scheduler, providing an enabled boolean attribute to start or stop the scheduler, and using the @Activate and @Modified methods. The article also provides a template to create an AEM scheduler.

Introduction:

A scheduler to schedule time/cron based jobs. A job is an object that is executed/fired by the scheduler. The object should either implement the Job interface or the Runnable interface. A job can be scheduled either by creating a ScheduleOptions instance through one of the scheduler methods and then calling schedule(Object, ScheduleOptions) or by using the whiteboard pattern and registering a Runnable service with either the PROPERTY_SCHEDULER_EXPRESSION or PROPERTY_SCHEDULER_PERIOD property. If both properties are specified, only PROPERTY_SCHEDULER_PERIOD is considered for scheduling. Services registered by the whiteboard pattern can by default run concurrently, which usually is not wanted. Therefore it is advisable to also set the PROPERTY_SCHEDULER_CONCURRENT property with Boolean.FALSE. Jobs started through the scheduler API are not persisted and are not restarted after a bundle restart. If the client bundle is stopped, the scheduler will stop all jobs started by this bundle as well. However, the client bundle does not need to keep a reference to the scheduler service.

Create Scheduler Config – OCD

Create a package for config for adding Scheduler related OCD
Creating separate configs will help in the long run if more configs are required for the scheduler

Things to keep in mind:
  1. Always provide an enabled boolean attribute to start or stop the scheduler (sometimes the scheduler takes a long time to run hence this helps to remove those schedulers)
  2. Add the scheduler based on the condition in @Activate, @Modified method
package com.mysite.core.schedulers.config;

import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;

@ObjectClassDefinition(name="A scheduled task", description = "Simple demo for cron-job like task with properties")
public @interface SimpleScheduledTaskConfig {

    @AttributeDefinition(name = "Cron-job expression")
    String schedulerExpression() default "*/30 * * * * ?";

    @AttributeDefinition(name = "Concurrent task", description = "Whether or not to schedule this task concurrently")
    boolean schedulerConcurrent() default false;

    @AttributeDefinition(name = "A parameter", description = "Can be configured in /system/console/configMgr")
    String myParameter() default "";

    @AttributeDefinition(name = "Enabled", description = "True, if scheduler service is enabled", type = AttributeType.BOOLEAN)
    public boolean enabled() default true;
}

Creates Scheduler

Create a scheduler using OSGi Component Service DS with a service that has runnable

Reference Scheduler service and sling setting to make sure the scheduler runs only in the author is recommended and override the run method

Make sure the scheduler runs in

  • author mode during @Activate @Modified method
  • get the class’s simple name and use it as a scheduler ID
@Activate
@Modified
protected void activate(SimpleScheduledTaskConfig simpleScheduledTaskConfig) {
  if (isAuthor()) {
    /**
     * Creating the scheduler id
     */
    this.schedulerJobName = this.getClass().getSimpleName();
    addScheduler(simpleScheduledTaskConfig);
    this.myParameter = simpleScheduledTaskConfig.myParameter();
  }
}

Add the scheduler to the scheduler service

private void addScheduler(SimpleScheduledTaskConfig simpleScheduledTaskConfig) {
  /**
   * Check if the scheduler is enabled
   */
  if (simpleScheduledTaskConfig.enabled()) {

    /**
     * Scheduler option takes the cron expression as a parameter and run accordingly
     */
    ScheduleOptions scheduleOptions = scheduler.EXPR(simpleScheduledTaskConfig.schedulerExpression());

    /**
     * Adding some parameters
     */
    scheduleOptions.name(schedulerJobName);
    scheduleOptions.canRunConcurrently(simpleScheduledTaskConfig.schedulerConcurrent());

    /**
     * Scheduling the job
     */
    scheduler.schedule(this, scheduleOptions);

    logger.info("{} Scheduler added", schedulerJobName);
  } else {
    logger.info("Scheduler is disabled");
    removeScheduler();
  }
}

Remove the scheduler if the scheduler is disabled

/**
 * This method removes the scheduler
 */
private void removeScheduler() {
  logger.info("Removing scheduler: {}", schedulerJobName);
  /**
   * Unscheduling/removing the scheduler
   */
  scheduler.unschedule(String.valueOf(schedulerJobName));
}

Use the below template to create the AEM scheduler

package com.mysite.core.schedulers;

import org.apache.sling.commons.scheduler.ScheduleOptions;
import org.apache.sling.commons.scheduler.Scheduler;
import org.apache.sling.settings.SlingSettingsService;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.metatype.annotations.Designate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.mysite.core.schedulers.config.SimpleScheduledTaskConfig;
import com.mysite.core.services.ExampleService;

/**
 * A simple demo for cron-job like tasks that get executed regularly.
 * It also demonstrates how property values can be set. Users can
 * set the property values in /system/console/configMgr
 */
@Component(service=Runnable.class)
@Designate(ocd= SimpleScheduledTaskConfig.class)
public class SimpleScheduledTask implements Runnable {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * Id of the scheduler based on its name
     */
    private String schedulerJobName;

    @Reference
    private Scheduler scheduler;

    @Reference
    private SlingSettingsService slingSettings;

    @Reference
    private ExampleService exampleService;

    private String myParameter;
    
    @Override
    public void run() {
        logger.debug("SimpleScheduledTask is now running, myParameter='{}'", myParameter);
        exampleService.generateContentList(myParameter);
    }

    @Activate
    @Modified
    protected void activate(SimpleScheduledTaskConfig simpleScheduledTaskConfig) {
    	if (isAuthor()) {
            /**
             * Creating the scheduler id
             */
            this.schedulerJobName = this.getClass().getSimpleName();
            addScheduler(simpleScheduledTaskConfig);
            this.myParameter = simpleScheduledTaskConfig.myParameter();
        }
    }

    private void addScheduler(SimpleScheduledTaskConfig simpleScheduledTaskConfig) {
        /**
         * Check if the scheduler is enabled
         */
        if (simpleScheduledTaskConfig.enabled()) {
            /**
             * Scheduler option takes the cron expression as a parameter and run accordingly
             */
            ScheduleOptions scheduleOptions = scheduler.EXPR(simpleScheduledTaskConfig.schedulerExpression());
            /**
             * Adding some parameters
             */
            scheduleOptions.name(schedulerJobName);
            scheduleOptions.canRunConcurrently(simpleScheduledTaskConfig.schedulerConcurrent());
            /**
             * Scheduling the job
             */
            scheduler.schedule(this, scheduleOptions);
            logger.info("{} Scheduler added", schedulerJobName);
        } else {
            logger.info("Scheduler is disabled");
            removeScheduler();
        }
    }

    /**
     * This method removes the scheduler
     */
    private void removeScheduler() {
        logger.info("Removing scheduler: {}", schedulerJobName);
        /**
         * Unscheduling/removing the scheduler
         */
        scheduler.unschedule(String.valueOf(schedulerJobName));
    }

    /**
     * It is use to check whether AEM is running in Publish mode or not.
     * @return Returns true is AEM is in publish mode, false otherwise
     */
    public boolean isAuthor() {
        return this.slingSettings.getRunModes().contains("author");
    }
}

One thought on “Best Practices for Creating OSGi Scheduler in AEM

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s