7

I am trying to figure out the possibilities I have to solve the following problem.

a) I want to have a database table that uses "crontab syntax" to schedule tasks, the structure would be something like this:

    |-Id-|---Crontab Syntax---|---------Task----------|
    | 1  | 30 *  *  *  *    * | MyClass.TaskA(args[]) |
    | 2  | 0  1  *  *  1-5  * | MyClass.TaskB(args[]) |
    |    |                    |                       |

The above table will be modified at any time by an external application. Tasks added or removed should instantly affect the scheduler.

b) The scheduler itself should reside on a Java application server. It should constantly be synched with the active scheduled tasks in the database table. Whenever a schedule event occurs it should trigger/call an EJB with the value in 'Task' as argument.

I am not looking for an answer to the above problem. But rather some input in what frameworks can be used for the crontab parsing and in what manner the EJB representing the scheduler should be deployed.

Thanks in advance.

aksamit
  • 2,325
  • 8
  • 28
  • 40

3 Answers3

28

See the EJB 3.1 @Schedule API. The API we chose for the spec is a little closer to Quartz syntax than cron -- tiny variances between the two.

Here's an annotation example:

package org.superbiz.corn;

import javax.ejb.Lock;
import javax.ejb.LockType;
import javax.ejb.Schedule;
import javax.ejb.Schedules;
import javax.ejb.Singleton;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * This is where we schedule all of Farmer Brown's corn jobs
 */
@Singleton
@Lock(LockType.READ) // allows timers to execute in parallel
public class FarmerBrown {

    private final AtomicInteger checks = new AtomicInteger();

    @Schedules({
            @Schedule(month = "5", dayOfMonth = "20-Last", minute = "0", hour = "8"),
            @Schedule(month = "6", dayOfMonth = "1-10", minute = "0", hour = "8")
    })
    private void plantTheCorn() {
        // Dig out the planter!!!
    }

    @Schedules({
            @Schedule(month = "9", dayOfMonth = "20-Last", minute = "0", hour = "8"),
            @Schedule(month = "10", dayOfMonth = "1-10", minute = "0", hour = "8")
    })
    private void harvestTheCorn() {
        // Dig out the combine!!!
    }

    @Schedule(second = "*", minute = "*", hour = "*")
    private void checkOnTheDaughters() {
        checks.incrementAndGet();
    }

    public int getChecks() {
        return checks.get();
    }
}

Full source for this here

You can do the same thing programmatically via the ScheduleExpression class which is just a constructable version of the above annotation. Here's what the above example would look like if the schedule was done in code:

package org.superbiz.corn;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.ejb.Lock;
import javax.ejb.LockType;
import javax.ejb.ScheduleExpression;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.ejb.Timeout;
import javax.ejb.Timer;
import javax.ejb.TimerConfig;
import javax.ejb.TimerService;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * This is where we schedule all of Farmer Brown's corn jobs
 *
 * @version $Revision$ $Date$
 */
@Singleton
@Lock(LockType.READ) // allows timers to execute in parallel
@Startup
public class FarmerBrown {

    private final AtomicInteger checks = new AtomicInteger();

    @Resource
    private TimerService timerService;

    @PostConstruct
    private void construct() {
        final TimerConfig plantTheCorn = new TimerConfig("plantTheCorn", false);
        timerService.createCalendarTimer(new ScheduleExpression().month(5).dayOfMonth("20-Last").minute(0).hour(8), plantTheCorn);
        timerService.createCalendarTimer(new ScheduleExpression().month(6).dayOfMonth("1-10").minute(0).hour(8), plantTheCorn);

        final TimerConfig harvestTheCorn = new TimerConfig("harvestTheCorn", false);
        timerService.createCalendarTimer(new ScheduleExpression().month(9).dayOfMonth("20-Last").minute(0).hour(8), harvestTheCorn);
        timerService.createCalendarTimer(new ScheduleExpression().month(10).dayOfMonth("1-10").minute(0).hour(8), harvestTheCorn);

        final TimerConfig checkOnTheDaughters = new TimerConfig("checkOnTheDaughters", false);
        timerService.createCalendarTimer(new ScheduleExpression().second("*").minute("*").hour("*"), checkOnTheDaughters);
    }

    @Timeout
    public void timeout(Timer timer) {
        if ("plantTheCorn".equals(timer.getInfo())) {
            plantTheCorn();
        } else if ("harvestTheCorn".equals(timer.getInfo())) {
            harvestTheCorn();
        } else if ("checkOnTheDaughters".equals(timer.getInfo())) {
            checkOnTheDaughters();
        }
    }

    private void plantTheCorn() {
        // Dig out the planter!!!
    }

    private void harvestTheCorn() {
        // Dig out the combine!!!
    }

    private void checkOnTheDaughters() {
        checks.incrementAndGet();
    }

    public int getChecks() {
        return checks.get();
    }
}

The source for this example is here

Side note, both examples are runnable in a plain IDE and have test cases that use the Embeddable EJBContainer API also new in EJB 3.1.

@Schedule vs ScheduleExpression

  • @Schedule
    • Statically configured
    • Many schedule methods are possible
    • Not possible to pass arguments
    • Cannot be cancelled

The above is all done in the deployment descriptor and is therefore limited to only things that can be configured in advance. The more dynamic version uses the following signature of the TimerService:

TimerService.createCalendarTimer(javax.ejb.ScheduleExpression, javax.ejb.TimerConfig)

  • ScheduleExpression
    • Dynamically created
    • Exactly one @Timeout supports all ScheduleExpression
    • The timeout method must take javax.ejb.Timer as a parameter
    • Arguments can be passed
    • Can be cancelled by the caller or the @Timeout method

Also note that there is an interceptor @AroundTimeout annotation that functions identically to @AroundInvoke and allows interceptors to participate in the bean's timer functionality.

David Blevins
  • 19,178
  • 3
  • 53
  • 68
  • 1
    Thanks for your very descriptive answer. I have experimented a bit with the Schedule-API. However one limitation I see with this approach is that I can't schedule an event to a general method with a specific argument. So I would be limited to only be able to schedule predefined methods, or do you see a possibility I have overlooked? – aksamit Sep 19 '11 at 15:33
  • The ScheduleExpression API is exactly that: one generic method with specific arguments for each schedule. I've updated the answer to try and highlight that a bit more. – David Blevins Sep 19 '11 at 20:40
  • Thanks. Solved my problem, without having to add Quartz (which would also solve the task) – aksamit Sep 20 '11 at 08:28
  • 2
    still, it would be nice if there were new ScheduleExpression("0 1 * * 1-5 *" /*crontabLine*/) – weberjn Nov 28 '17 at 17:24
0

EJB has its own built in timers, but you'll have to write the boilerplate code to translate cron parsing. The parsing of the cron instructions themselves should be trivial.

If you're not afraid of venturing outside of EJB, Quartz is as lexicore mentioned an excellent option.

mikek
  • 1,555
  • 17
  • 30
-1

Take a look at Quartz. If you use Spring there's very good support there. A neat, reliable good-working thing.

lexicore
  • 42,748
  • 17
  • 132
  • 221