Clowder supports scheduling of repetitive tasks by executing a job registered in MongoDB Collection called jobs.
The only implemented job as of August 2016 is EmailDigest
triggered by setting an e-mail option in user's profile page as seen in Figure 1. Selecting hourly, daily or monthly option in the pull down menu creates a job called Digest('id')
in the database which is then executed at pre-defined times, currently at the top of an hour (hourly), at 7:00 am (daily) or every Monday at 7:00 am (weekly).
Implementation
A jobTimer
in the Clowder calls JobsScheduler.runScheduledJobs()
every minute. This is done from Akka.system().scheduler.schedule
in app/Global.
This time is then split into minute
, hour
, day_of_week,
day_of_month
variables in the JobsScheduler
(app/models/JobsScheduler.scala
) for further use in the code and for jobs’ execution and maintenance.
The time variables are compared with a set of integers stored in every job object in the MongoDB Collection (jobs
). In other words the time comparison is done by integer equality. A job gets fired hourly when minute=minute
job if no other values of hour
, day_of_week,
day_of_month
are defined. Similarly, the job gets executed with minutes and hours set (daily) when minute=minute
job and hour=hourjob
(no other values of day_of_week,
day_of_month
are defined) etc. Note that there is no verification of time inserted in the system, nor there is time/date object comparison.
A job model in Clowder is called TimerJob
(app/models/TimerJob.scala
). A programmer can create different job schema but the TimerJob
is sufficient for the most repetitive tasks.
A full time, or only subset of it is set in the TimerJob
with minute
(0-59), hour
(0-23), day_of_week
(1-7 for Monday-Sunday) and day_of_month
(1-31) precision. An option frequency
is meant to be ‘hourly’, ‘daily’, ‘weekly’, ‘monthly’ but can be any descriptive string. The lastJobTime
field is useful for getting the time interval since the last job call (set by scheduler.updateLastRun(‘jobName’)
). Additionally, parameters
can be used for any object id, function
is a string describing action (e.g. EmailDigest
).
Creating and calling a new timer job
Create and update job
A new
TimerJob
job is created and set in the MongoDBCollection
by newly coded functions inapp/services/ScheduleService
andapp/services/mongoldb/MongoDBSchedulerService.scala
called for exampleupdateMyJob():
def updateMyJob(id: UUID, name: String, setting: String)
and
def updateMyJob(id: UUID, name: String, setting: String) = { if (jobExists(name) == false) { Jobs.insert(new TimerJob(name, None, None, None, None, Option(‘function’), Option(id), None, Option(new Date()))) } if (setting == "hourly"){ updateJobTime(name, Option(0), None, None, Option(setting)) } else if (setting == "daily"){ updateJobTime(name, Option(0), Option(7), None, Option(setting)) } else if (setting == "weekly"){ updateJobTime(name, Option(0), Option(7), Option(1), Option(setting)) } else { deleteJob(name) } }
This is similar to a function
updateEmailJob()
already implemented in the Clowder whereupdateJobTime(name, Option(0), Option(7), Option(1), Option(setting))
refers tot the time set to minute=0, hour=7 and day_of_week=1 (Monday) in the database as mentioned above. When Clowder time matches the values the job is returned from the database and Action is fired.
You can either change time directly in the code or pass the time values from a Play template as additional parameters such as:def updateMyJob(id: UUID, name: String, setting: String, minute: Integer)= { updateJobTime(name, Option(minute), None, None, Option(setting)) }
from the Play
request
or directly in Scala on the server side call your job update:scheduler.updateMyJob(id, name, setting)
here theid
is a parameters id (parameters: Option[UUID]
, seeTimerJob
model),name
is the job's name andsettings
are used to distinguish time frequency (from options of pull down menus for example - hourly, weekly etc.)
Call and get job
Create function
getMyJobs()
inapp/models/JobsScheduler.scala
to get your job (TimerJob
) at a certain timedef getMyJobs (minute: Integer, hour: Integer, day_of_week: Integer) = { var myJobs = scheduler.getJobByTime(minute, hour, day_of_week) myJobs }
and register it with
runScheduledJobs()
var myJobs = getMyJobs(minute.toInt, hour.toInt, day_of_week.toInt) myAction.myActionJob(myJobs)
Create a new class
myAction.scala
for example in a packagemodels
(seemodels/Event.scala
as an example)The
ObjectService
above, for example calledUserService
in the case of sending email digest and the user id was used as a parameter in theTimerJob
and MongoDB job objects.implement
Do something
inmyAction()
Note
The day_of_month
variable is part of the TimerJob
model but it is not used in the scheduler.getJobByTime()
Adding it is straightforward:
app/services/SchedulerService.scala
app/services/mongoldb/MongoDBSchedulerService.scala
add extra parameter (
None
) toupdateEmailJob()
Testing
Add functioning e-mail in
app/util/Mail.scala
if you use a 'fake' e-mail in your local Clowder developmental branchUse functioning
smtp
insecuresocial.conf
or override it by settingsmtp.host
and (optional)smtp.from
in yourcustom.conf
smtp.host=smtp.ncsa.illinois.edu smtp.from="ondrejce@illinois.edu"
Note that the host above can be used only within the NCSA's network.
- Disable
scheduler.updateLastRun(‘jobName’)
inmyAction.scala
(or inmodels/Event.scala
for sending email digests) by commenting it out. Your events (followed objects for example) will become 'permanent' and the timer job will always execute since there is no update of thelastJobTime
variable in the MongoDB job object. Don't forget to enable theupdateLastRun()
when you are done debugging. - Set