CronService.groovy
/*
Copyright 2012-now Jex Jexler (Alain Stalder)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ch.grengine.jexler.service
import ch.grengine.jexler.Jexler
import groovy.transform.CompileStatic
import org.quartz.CronScheduleBuilder
import org.quartz.Job
import org.quartz.JobBuilder
import org.quartz.JobDataMap
import org.quartz.JobDetail
import org.quartz.JobExecutionContext
import org.quartz.JobExecutionException
import org.quartz.Scheduler
import org.quartz.Trigger
import org.quartz.TriggerBuilder
import org.quartz.TriggerKey
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import static ch.grengine.jexler.service.ServiceState.IDLE
import static ch.grengine.jexler.service.ServiceState.OFF
/**
* A cron service, creates events at configurable times.
* Implemented using the quartz library.
*
* @author Jex Jexler (Alain Stalder)
*/
@CompileStatic
class CronService extends ServiceBase {
private static final Logger LOG = LoggerFactory.getLogger(CronService.class)
/**
* Pseudo cron string for a single cron event immediately.
*/
public static final String CRON_NOW = 'now'
/**
* Pseudo cron string for a single cron event immediately,
* followed by a single stop event.
*/
public static final String CRON_NOW_AND_STOP = "$CRON_NOW+stop"
private final Jexler jexler
private String cron
private Scheduler scheduler
private TriggerKey triggerKey
/**
* Constructor.
* @param jexler the jexler to send events to
* @param id the id of the service
*/
CronService(final Jexler jexler, final String id) {
super(id)
this.jexler = jexler
}
/**
* Set cron pattern, e.g. "* * * * *" or with seconds "0 * * * * *".
* Use "now" for now, i.e. for a single event immediately,
* or "now+stop" for a single event immediately, followed
* by a StopEvent, which can both be useful for testing.
* @return this (for chaining calls)
*/
CronService setCron(final String cron) {
this.cron = ServiceUtil.toQuartzCron(cron)
return this
}
/**
* Get cron pattern.
*/
String getCron() {
return cron
}
/**
* Set quartz scheduler.
* Default is a scheduler shared by all jexlers in the same jexler container.
* @return this (for chaining calls)
*/
CronService setScheduler(final Scheduler scheduler) {
this.scheduler = scheduler
return this
}
/**
* Get quartz scheduler.
*/
Scheduler getScheduler() {
return scheduler
}
/**
* Get jexler.
*/
Jexler getJexler() {
return jexler
}
@Override
void start() {
if (state.on) {
return
}
if (cron.startsWith(CRON_NOW)) {
LOG.trace("new cron event: $cron")
jexler.handle(new CronEvent(this, cron))
state = IDLE
if (cron == CRON_NOW_AND_STOP) {
jexler.handle(new StopEvent(this))
state = OFF
}
return
}
final String uuid = UUID.randomUUID()
final JobDetail job = JobBuilder.newJob(CronJob.class)
.withIdentity("job-$id-$uuid", jexler.id)
.usingJobData(['service':this] as JobDataMap)
.build()
final Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger-$id-$uuid", jexler.id)
.withSchedule(CronScheduleBuilder.cronSchedule(cron))
.startNow()
.build()
triggerKey = trigger.key
if (scheduler == null) {
scheduler = jexler.container.scheduler
}
scheduler.scheduleJob(job, trigger)
state = IDLE
}
@Override
void stop() {
if (state.off) {
return
}
if (scheduler != null) {
scheduler.unscheduleJob(triggerKey)
}
state = OFF
}
@Override
void zap() {
if (state.off) {
return
}
state = OFF
if (scheduler != null) {
new Thread() {
void run() {
try {
scheduler.unscheduleJob(triggerKey)
} catch (final Throwable tUnschedule) {
LOG.trace('failed to unschedule cron job', tUnschedule)
}
}
}.start()
}
}
/**
* Internal class, only public because otherwise not called by quartz scheduler.
*/
static class CronJob implements Job {
void execute(final JobExecutionContext ctx) throws JobExecutionException {
final CronService service = (CronService)ctx.jobDetail.jobDataMap.service
final String savedName = Thread.currentThread().name
Thread.currentThread().name = "$service.jexler.id|$service.id"
LOG.trace("new cron event: $service.cron")
service.jexler.handle(new CronEvent(service, service.cron))
Thread.currentThread().name = savedName
}
}
}