Scheduler


What is a Scheduler?

Apache Sling Scheduler enables you to easily schedule jobs within your application. Jobs can be executed at a specific time, regularly at a given period or at the time given by a cron expression by leveraging the Sling scheduler service.

Scheduling in CQ uses the open source Quartz library, part Sling Commons: ‘org.apache.sling.commons.scheduler.Scheduler’. It is used to execute jobs at predefined times either periodically or at a set time. It is based on the Quartz scheduling library and can therefore make use of its syntax for defining the execution times of jobs, making it possible to precisely indicate times .


How to Write a Scheduler?

The scheduler can be used in two ways,

  • by registering the job through the scheduler API and
  • by leveraging the whiteboard pattern that is supported by the scheduler.

In most cases the whiteboard pattern is preferred. 

I would be giving examples for both the scenarios which will help you give more clarity on Schedulers.


Sample Scheduler Templates:

1. WHITEBOARD PATTERN

@Component
@Service(value = Runnable.class)
@Property( name = "scheduler.expression", value = "0 * * * * ?")
public class ScheduledCronJob implements Runnable {
    /** Default log. */
    protected final Logger log = LoggerFactory.getLogger(this.getClass());

    public void run() {
        log.info("Executing a cron job (job#1) through the whiteboard pattern");
    }

}

  •  Fixed Periodic Scheduler
@Component
@Service(value = Runnable.class)
@Property( name = "scheduler.period", longValue = 10)
public class ScheduledPeriodicJob implements Runnable {

    /** Default log. */
    protected final Logger log = LoggerFactory.getLogger(this.getClass());

    public void run() {
        log.info("Executing a perodic job (job#2) through the whiteboard pattern");
    }
//
}

Sample Scheduler Template Example:

@Component(
        label = "Sample Sling Scheduler",
        description = "Sample Description",
        immediate = true,
        metatype = true
)

@Properties({
        @Property(
                label = "Enabled",
                description = "Enable/Disable the Scheduled Service",
                name = "service.enabled",
                boolValue = true
        ),
        @Property(
                label = "Cron expression defining when this Scheduled Service will run",
                description = "[every minute = 0 * * * * ?], [12:01am daily = 0 1 0 ? * *]",
                name = "scheduler.expression",
                value = "0 1 0 ? * *"
        ),
        @Property(
                label = "Allow concurrent executions",
                description = "Allow concurrent executions of this Scheduled Service",
                name = "scheduler.concurrent",
                boolValue = false
        ),
        @Property(
                label = "Vendor",
                name = "service.vendor",
                value = "SampleVendor",
                propertyPrivate = true
        )
})

@Service
public class SampleScheduledService implements Runnable {

   
    @Reference
    private ResourceResolverFactory resourceResolverFactory;

    private final Logger log = LoggerFactory.getLogger(this.getClass());
   
    @Override
    public void run() {
      
        ResourceResolver adminResourceResolver = null;
        try {
            adminResourceResolver = resourceResolverFactory.getAdministrativeResourceResolver(null);

            // Write your code logic here.
        } catch (LoginException ex) {
            java.util.logging.Logger.getLogger(SampleScheduledService.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            // ALWAYS close resolvers you open
            if (adminResourceResolver != null) {
                adminResourceResolver.close();
            }
        }
    }

    @Activate
    protected void activate(final ComponentContext componentContext) throws Exception {
        final Map<String, String> properties = (Map<String, String>) componentContext.getProperties();
    }

    @Deactivate
    protected void deactivate(ComponentContext ctx) {

    }
}

2. SCHEDULER API

A scheduler to schedule time/cron based jobs. A job is an object which is executed/fired by the scheduler. You will understand the difference when you see the code.

The below mentioned code implements a service that simultaneously executes the job based on 3 different kinds of scheduling can look as follows:

@Component
public class HelloWorldScheduledService {

    /** Default log. */
    protected final Logger log = LoggerFactory.getLogger(this.getClass());

    /** The scheduler for rescheduling jobs. */
    @Reference
    private Scheduler scheduler;


    protected void activate(ComponentContext componentContext) throws Exception {
        //case 1: with addJob() method: executes the job every minute
				String schedulingExpression = "0 * * * * ?";
				String jobName1 = "case1";
				Map<String, Serializable> config1 = new HashMap<String, Serializable>();
				boolean canRunConcurrently = true;
				final Runnable job1 = new Runnable() {
					public void run() {
						log.info("Executing job1");
					}
				};
				try {
					this.scheduler.addJob(jobName1, job1, config1, schedulingExpression, canRunConcurrently);
				} catch (Exception e) {
					job1.run();
				}

        //case 2: with addPeriodicJob(): executes the job every 3 minutes
				String jobName2 = "case2";
				long period = 180;
				Map<String, Serializable> config2 = new HashMap<String, Serializable>();
				final Runnable job2 = new Runnable() {
					public void run() {
						log.info("Executing job2");
					}
				};
				try {
					this.scheduler.addPeriodicJob(jobName2, job2, config2, period, canRunConcurrently);
				} catch (Exception e) {
					job2.run();
				}

        //case 3: with fireJobAt(): executes the job at a specific date (date of deployment + delay of 30 seconds)
				String jobName3 = "case3";
				final long delay = 30*1000;
				final Date fireDate = new Date();
				fireDate.setTime(System.currentTimeMillis() + delay);
				Map<String, Serializable> config3 = new HashMap<String, Serializable>();
				final Runnable job3 = new Runnable() {
					public void run() {
						log.info("Executing job3 at date: {} with a delay of: {} seconds", fireDate, delay/1000);
					}
				};
				try {
					this.scheduler.fireJobAt(jobName3, job3, config3, fireDate);
				} catch (Exception e) {
					job3.run();
				}
    }

    protected void deactivate(ComponentContext componentContext) {
        log.info("Deactivated, goodbye!");
    }

}

Here we are leveraging the underlying CQ scheduler instance which is using addJob(), addPeriodicJob() or fireJobAt() methods .

I would suggest to use approach 1 for creating your schedulers.  Happy Scheduling :)

28 thoughts on “Scheduler

  1. Hi,

    I have tried both approaches (method 1 and method 2). I didn’t see the log info in error.log file.

    I am using the bundle project to build the jar file to install in OSGI bundle.

    May i know is it automatically print out the
    log.info(“Executing a cron job (job#1) through the whiteboard pattern”); once the bundle is active?

    Else how to trigger this run()?

    Thank you. Appreciate your help.

    Like

      • Hi Hashim,

        Sorry for the late reply. Got stuck with project work. The issue was not with Scheduler configuration you mentioned but the dependencies weren’t getting injected and there were no error in log files. Fixed the issue. Thanks a lot for the artcle.

        Like

  2. when i am changing the scheduler cron expression from osgi console .The scheduler is not reading the new property value.i have to restart the component.is this a mandatory thing or do i need to make some changes.

    Like

  3. Hi,
    I have git source for qa and stage I have created scheduler and make it run in QA code then I promoted the code to Stage branch there I see my scheduler is not working. I am getting exception like Failure loooking up method bindRrFactory(org.osgi.framework.ServiceReference) in class class org.acs.cancer.core.scheduler.LocalAddressDetailUpdateScheduler. Assuming no such method. (java.lang.NoClassDefFoundError: javax/jcr/version/VersionException)
    java.lang.NoClassDefFoundError: javax/jcr/version/VersionException

    any help appreciated

    Like

  4. Thanks Hashim for the article.If we use the first method and the server is restarted at the same time when the scheduler is about to run,would it queue it and run it post restart or will have to wait for the next scheduled time to run it?
    ( I assume the latter since these are not persisted but would request you to comment in case of any pointers on this.)Also what is the basis for preferring approach 1 over the other options?

    Like

    • Yes you are correct. It should be scheduled for the next time slot as per the Cron expression. The first is more preferred and widely used as it gives a clearer picture of the time slots of various cron jobs. And thereby giving us more control to distribute the jobs throughout the clock so that there is not much load on the system at one given time.

      Like

  5. That’s really useful article Hashim.

    We have scheduled the job to run at 00 hrs and we stopped the server while the job was already running as there was too much data to be processed. When we restated the server, the job started running again. Is there a way to stop it explicitly?

    Like

  6. Hi Hashim,
    why do we write value=Runnable.Class or Servlet.class in Service annotation
    @Service(value = Runnable.class)
    what difference will it make if we dont write the value parameter.

    Like

    • Hi Harsha,
      That’s a good question. It’s nice to understand why we write code in such a way.

      The value parameter is used to define the name of the service interface provided by the component. If this property is not set explicitly then it will be generated for all the interfaces which that class implements. Generally, we want it to limit to 1 interface, hence it’s a norm to define it in value parameter.

      Although things have changed now with the introduction of OSGi Declarative Services. You should read more about it http://www.nateyolles.com/blog/2017/05/osgi-declarative-services-annotations-in-aem

      Like

  7. Hi Hashim,

    Thanks for your article, i was trying 2nd approach where i am getting below warning for addJob(), addPeriodicJob() or fireJobAt() methods –

    The method addPeriodicJob(String, Object, Map, long, boolean) from the type Scheduler is deprecated

    any help on this would be appreciated
    Thanks!

    Like

    • Hi,
      If you want to use the methods defined in scheduler Class you can either create an instance of that Class. Or better approach would be to move the common methods used in Servlet and Scheduler to a Util Class and let both of them use the methods from the Util class by passing parameters. The second approach is cleaner.

      Like

  8. Hi Hashim,

    I have few question.

    1)difference b/w felix annotation and osgi apache annotation
    2)what are the way for scheduler configuration in system/console/configMgr like
    @ property and @AttributeDefinition

    i need more details about the above question.

    3)Kindly provide some sample projects for convert html file to aem component.

    Like

  9. Hi Hashim, I am new to AEM and wanted to understand few things about scheduler.
    1. Is scheduler and workflow are same or there any difference. Can workflow can be developed without scheduler.
    Regards
    Shah

    Like

    • Scheduler is created for doing a process in a scheduled manner after a particular time period repeatedly. While workflow can be designed to get triggered on an event or manually.

      Yes you should be able to create scheduler / workflow using the same Code logic Service class. Use that service class from both scheduler and workflow.

      Like

Leave a comment