16

I need to execute a piece of code 1 time everyday in playframework2.0.4 when I try to do with the class extends GlobalSettings it works. But it works for every instance requesting. I want it works when server starts and does its duty everyday 1 time.

package controllers;
import java.util.concurrent.TimeUnit;
import akka.util.Duration;
import play.Application;
import play.GlobalSettings;
import play.libs.Akka;

public class ParserJobApp extends GlobalSettings{
@Override
public void onStart(Application app) {
    Akka.system().scheduler().schedule(Duration.create(0, TimeUnit.MILLISECONDS),Duration.create(6, TimeUnit.SECONDS), new Runnable() { 
        @Override
        public void run() {
            System.out.println("AAA ---    "+System.currentTimeMillis());
        }
    });
}
}

And this is my controller where start the class above

public class Application extends Controller {

public static Result index() { 
  ParserJobApp pr=new ParserJobApp();
  pr.onStart(null);
  System.out.println("sfsdfsdf");
return ok(index.render("Your new "));

}
}
biesior
  • 55,576
  • 10
  • 125
  • 182
gabby
  • 629
  • 2
  • 11
  • 34
  • This version of schedule() seems to have been removed in 2.1, how do you this in play 2.1? – nylund Mar 25 '13 at 18:34
  • Nevermind, I just found it. It now takes 4 arguments and the last being the execution contexts, Akka.system().dispatcher(). – nylund Mar 25 '13 at 18:35

3 Answers3

24

Scheduler tasks should be placed only in Global class. Create two tasks, schedule only once first with initialDelay = 0 milliseconds.

For the second task, you need to calculate seconds between current DateTime and next planned occurrence (ie. tomorrow at 8:00 o'clock) using common date/time classes, then set this difference as initialDelay and also set frequency to 24 hours.

In result, it will start at the application start and will schedule the task for execution each day at required hour.

Edit

There's complete sample, (save/edit the class: /app/Global.java):

import akka.util.Duration;
import org.joda.time.DateTime;
import org.joda.time.Seconds;
import play.Application;
import play.GlobalSettings;
import play.Logger;
import play.libs.Akka;
import java.util.concurrent.TimeUnit;

public class Global extends GlobalSettings {

    @Override
    public void onStart(Application application) {


        Akka.system().scheduler().scheduleOnce(
                Duration.create(0, TimeUnit.MILLISECONDS),
                new Runnable() {
                    @Override
                    public void run() {
                        Logger.info("ON START ---    " + System.currentTimeMillis());
                    }
                }
        );

        Akka.system().scheduler().schedule(
                Duration.create(nextExecutionInSeconds(8, 0), TimeUnit.SECONDS),
                Duration.create(24, TimeUnit.HOURS),
                new Runnable() {
                    @Override
                    public void run() {
                        Logger.info("EVERY DAY AT 8:00 ---    " + System.currentTimeMillis());
                    }
                }
        );
    }

    public static int nextExecutionInSeconds(int hour, int minute){
        return Seconds.secondsBetween(
                new DateTime(),
                nextExecution(hour, minute)
        ).getSeconds();
    }

    public static DateTime nextExecution(int hour, int minute){
        DateTime next = new DateTime()
                .withHourOfDay(hour)
                .withMinuteOfHour(minute)
                .withSecondOfMinute(0)
                .withMillisOfSecond(0);

        return (next.isBeforeNow())
                ? next.plusHours(24)
                : next;
    }
}
geizio
  • 105
  • 1
  • 16
biesior
  • 55,576
  • 10
  • 125
  • 182
  • every time I call http://localhost:9000 the task works again. So it means it is going to work when every user make a request. Is it not possible to start task one at the time app starts? I created the instance of Global class in controller class in index() method as Global gl=new Global(); gl.onStart(null); – gabby Feb 05 '13 at 13:51
  • 1
    I gave you whole Global class, just save it as `/app/Global.java` and DON'T create ANY instances in your action(s) – biesior Feb 05 '13 at 14:05
  • But I would like to ask something else. When I stop the app, second task (schedule) works once more like that [info] play - Shutdown application default Akka system. [info] application - EVERY DAY AT 8:00 --- 1360074567517 is it normal? – gabby Feb 05 '13 at 14:31
  • 2
    I realized that as well after answering : http://doc.akka.io/docs/akka/2.0/java/scheduler.html there's a comment: '_it MUST execute all outstanding tasks upon .close() in order to properly shutdown all dispatchers._', and probably that causes the last execution. If you won't find any solution it's good topic for new question (I'm interested with this to, but have no time now) – biesior Feb 05 '13 at 14:39
  • biesior, I faced the issue you describe. My Play executed all scheduled tasks on exit. I was able to fix that by manually calling .cancel on each Cancellable object that is returned by scheduler. – Denis Apr 10 '14 at 14:58
  • I really like your solution @biesior but like Denis I am facing the onStop problem and therefore my scheduler are running before the defined date. I found that someone ask informations about it: [link](https://stackoverflow.com/questions/19431049/play-closes-dispatchers-when-stopped) but without any answer. Do you know how to keep your solution but avoid this problem ? – Tyrael Aug 07 '14 at 09:37
  • 3
    Ok, so, this is how I did: Cancellable scheduler = Akka.system().scheduler().schedule(....); and create: @Override public void onStop(Application application) { scheduler.cancel(); } – Tyrael Aug 07 '14 at 12:56
16

Here is my solution which is lighter and supports cron expressions for scheduling. In this example, the scheduler will run everyday at 10:00 AM.

Following in your Global class:

private Cancellable scheduler;

@Override
public void onStart(Application application) {
    super.onStart(application); 
    schedule(); 
}

@Override
public void onStop(Application application) {
    //Stop the scheduler
    if (scheduler != null) {
        scheduler.cancel();
    }
}

private void schedule() {
    try {
        CronExpression e = new CronExpression("0 00 10 ? * *");
        Date nextValidTimeAfter = e.getNextValidTimeAfter(new Date());
        FiniteDuration d = Duration.create(
            nextValidTimeAfter.getTime() - System.currentTimeMillis(), 
            TimeUnit.MILLISECONDS);

        Logger.debug("Scheduling to run at "+nextValidTimeAfter);

        scheduler = Akka.system().scheduler().scheduleOnce(d, new Runnable() {

        @Override
        public void run() {
            Logger.debug("Ruuning scheduler");
            //Do your tasks here

            schedule(); //Schedule for next time

        }
        }, Akka.system().dispatcher());
    } catch (Exception e) {
        Logger.error("", e);
    }
}
null
  • 8,669
  • 16
  • 68
  • 98
Ilja S.
  • 1,150
  • 2
  • 12
  • 21
  • Thanks, your solution is very good. But your code needs to add Tyrael's solution for cancelling the scheduler when application is stopped. – null Sep 25 '14 at 18:18
  • is it ok, not to call super.onStop(application); inside onStop function? – behzad Aug 27 '15 at 09:05
4

This can be done using the Global Class, and over riding the onstart method. https://www.playframework.com/documentation/2.5.x/JavaGlobal

The code below prints the JVM stats in 10 Mins Intervals. The time duration can be configured in order to suit the need.

An abstract view of the coding is given below. Hope this help

public class Global extends GlobalSettings {

private Cancellable scheduler;

@Override
public void onStart(Application application) {
    int timeDelayFromAppStartToLogFirstLogInMs = 0;
    int timeGapBetweenMemoryLogsInMinutes = 10;
    scheduler = Akka.system().scheduler().schedule(Duration.create(timeDelayFromAppStartToLogFirstLogInMs, TimeUnit.MILLISECONDS),
            Duration.create(timeGapBetweenMemoryLogsInMinutes, TimeUnit.MINUTES),
            new Runnable() {
                @Override
                public void run() {
                    System.out.println("Cron Job");
                    // Call a function (to print JVM stats)
                }
            },
            Akka.system().dispatcher());
    super.onStart(application);
}

@Override
public void onStop(Application app) {
    scheduler.cancel();
    super.onStop(app);
}

}
Nirojan Selvanathan
  • 10,066
  • 5
  • 61
  • 82