There is an example for doing this with Play! v2.0 but I’m trying to do the same for v1.2.4
I’ve tried to follow the same general approach, which is to get the Heroku Scheduler to explicitly start the JVM with a class that initialises the Play environment and then executes the required task within that environment. My specific task was to retrieve some data from the database using JPA and then use the Mailer to generate an email using that data. The initial approach was just to execute the logic directly after initialising Play but I got an exception that the ‘JPA context is not initialized’.
Procfile:
web: play run --http.port=$PORT $PLAY_OPTS
reports: java -Dapplication.path=./ -Dplay.id=staging -Dprecompiled=true -DlogLevel=INFO -cp "precompiled/java:lib/*:conf:.play/framework/play-1.2.4.jar:.play/framework/lib/*:modules/mailerPlus-0.1/lib/*:modules/fastergt-1.7/lib/*" jobs.Reports .
Reports.java:
public class Reports {
private Reports() {
super();
}
public static void main(String[] args) {
File root = new File(System.getProperty("application.path"));
if (System.getProperty("precompiled", "false").equals("true")) {
Play.usePrecompiled = true;
}
try {
Play.init(root, System.getProperty("play.id", ""));
Mails.itjbSurveyReport();
} catch (Exception e) {
Logger.error(e, "");
} finally {
Play.stop();
}
}
}
Log:
2012-06-20T21:58:27+00:00 app[run.1]: play.exceptions.JPAException: The JPA context is not initialized. JPA Entity Manager automatically start when one or more classes annotated with the @javax.persistence.Entity annotation are found in the application.
2012-06-20T21:58:27+00:00 app[run.1]: at play.db.jpa.JPA.get(JPA.java:22)
2012-06-20T21:58:27+00:00 app[run.1]: at play.db.jpa.JPA.em(JPA.java:51)
2012-06-20T21:58:27+00:00 app[run.1]: at play.db.jpa.JPQL.em(JPQL.java:18)
2012-06-20T21:58:27+00:00 app[run.1]: at play.db.jpa.JPQL.count(JPQL.java:26)
2012-06-20T21:58:27+00:00 app[run.1]: at models.User.count(User.java)
2012-06-20T21:58:27+00:00 app[run.1]: at notifiers.Mails.itjbSurveyReport(Mails.java:246)
2012-06-20T21:58:27+00:00 app[run.1]: at jobs.Reports.main(Reports.java:35)
Then decided to wrap the logic in a ‘Job’ to ensure the JPA context was initialised but then I got a class loading issue with an object returned from the JPA query:
Reports.java:
public class Reports extends Job {
private Reports() {
super();
}
@Override
public void doJob() throws Exception {
Mails.itjbSurveyReport();
}
public static void main(String[] args) {
File root = new File(System.getProperty("application.path"));
if (System.getProperty("precompiled", "false").equals("true")) {
Play.usePrecompiled = true;
}
try {
Play.init(root, System.getProperty("play.id", ""));
Reports reports = new Reports();
reports.now().get();
} catch (Exception e) {
Logger.error(e, "");
} finally {
Play.stop();
}
}
}
Log:
2012-06-20T19:04:24+00:00 app[run.1]: ClassCastException occured : securesocial.provider.ProviderType cannot be cast to securesocial.provider.ProviderType
2012-06-20T19:04:24+00:00 app[run.1]:
2012-06-20T19:04:24+00:00 app[run.1]: play.exceptions.JavaExecutionException: securesocial.provider.ProviderType cannot be cast to securesocial.provider.ProviderType
2012-06-20T19:04:24+00:00 app[run.1]: at play.jobs.Job.call(Job.java:155)
2012-06-20T19:04:24+00:00 app[run.1]: at play.jobs.Job$1.call(Job.java:66)
2012-06-20T19:04:24+00:00 app[run.1]: at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
2012-06-20T19:04:24+00:00 app[run.1]: at java.util.concurrent.FutureTask.run(FutureTask.java:166)
2012-06-20T19:04:24+00:00 app[run.1]: at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$101(ScheduledThreadPoolExecutor.java:165)
2012-06-20T19:04:24+00:00 app[run.1]: at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:266)
2012-06-20T19:04:24+00:00 app[run.1]: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
2012-06-20T19:04:24+00:00 app[run.1]: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
2012-06-20T19:04:24+00:00 app[run.1]: at java.lang.Thread.run(Thread.java:636)
2012-06-20T19:04:24+00:00 app[run.1]: Caused by: java.lang.ClassCastException: securesocial.provider.ProviderType cannot be cast to securesocial.provider.ProviderType
2012-06-20T19:04:24+00:00 app[run.1]: at notifiers.Mails.itjbSurveyReport(Mails.java:259)
2012-06-20T19:04:24+00:00 app[run.1]: at jobs.Reports.doJob(Reports.java:36)
2012-06-20T19:04:24+00:00 app[run.1]: at play.jobs.Job.doJobWithResult(Job.java:50)
2012-06-20T19:04:24+00:00 app[run.1]: at play.jobs.Job.call(Job.java:146)
2012-06-20T19:04:24+00:00 app[run.1]: ... 8 more
Interestingly in the v2.0 version, mentioned above, it looks like the local classloader is passed into the Play environment which probably resolves this issue. Not sure how this can be done in v1.2.4
Not sure what to do next…
Here is a working example app:
https://github.com/jamesward/play1-scheduled-job-demo
The trick is to load the Job from the Play’s classloader (from HelloJob.java):