/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
*
* http://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 org.apache.karaf.scheduler.core;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
import org.apache.karaf.scheduler.Job;
import org.apache.karaf.scheduler.ScheduleOptions;
import org.apache.karaf.scheduler.Scheduler;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.DirectSchedulerFactory;
import org.quartz.impl.matchers.GroupMatcher;
import org.quartz.simpl.RAMJobStore;
import org.quartz.spi.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The quartz based implementation of the scheduler.
*
*/
public class QuartzScheduler implements Scheduler {
/** Default logger. */
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private static final String PREFIX = "Apache Karaf Quartz Scheduler ";
private static final String QUARTZ_SCHEDULER_NAME = "ApacheKaraf";
/** Map key for the job object */
static final String DATA_MAP_OBJECT = "QuartzJobScheduler.Object";
/** Map key for the job name */
static final String DATA_MAP_NAME = "QuartzJobScheduler.JobName";
/** Map key for the scheduling options. */
static final String DATA_MAP_OPTIONS = "QuartzJobScheduler.Options";
/** Map key for the logger. */
static final String DATA_MAP_LOGGER = "QuartzJobScheduler.Logger";
/** The quartz scheduler. */
private volatile org.quartz.Scheduler scheduler;
public QuartzScheduler(ThreadPool threadPool) throws SchedulerException {
// SLING-2261 Prevent Quartz from checking for updates
System.setProperty("org.terracotta.quartz.skipUpdateCheck", Boolean.TRUE.toString());
final DirectSchedulerFactory factory = DirectSchedulerFactory.getInstance();
// unique run id
final String runID = new Date().toString().replace(' ', '_');
factory.createScheduler(QUARTZ_SCHEDULER_NAME, runID, threadPool, new RAMJobStore());
// quartz does not provide a way to get the scheduler by name AND runID, so we have to iterate!
final Iterator<org.quartz.Scheduler> allSchedulersIter = factory.getAllSchedulers().iterator();
while ( scheduler == null && allSchedulersIter.hasNext() ) {
final org.quartz.Scheduler current = allSchedulersIter.next();
if ( QUARTZ_SCHEDULER_NAME.equals(current.getSchedulerName())
&& runID.equals(current.getSchedulerInstanceId()) ) {
scheduler = current;
}
}
if ( scheduler == null ) {
throw new SchedulerException("Unable to find new scheduler with name " + QUARTZ_SCHEDULER_NAME + " and run ID " + runID);
}
scheduler.start();
if ( this.logger.isDebugEnabled() ) {
this.logger.debug(PREFIX + "started.");
}
}
/**
* Deactivate this component.
* Stop the scheduler.
*/
public void deactivate() {
final org.quartz.Scheduler s = this.scheduler;
this.scheduler = null;
this.dispose(s);
}
/**
* Dispose the quartz scheduler
* @param s The scheduler.
*/
private void dispose(final org.quartz.Scheduler s) {
if ( s != null ) {
try {
s.shutdown();
} catch (SchedulerException e) {
this.logger.debug("Exception during shutdown of scheduler.", e);
}
if ( this.logger.isDebugEnabled() ) {
this.logger.debug(PREFIX + "stopped.");
}
}
}
/**
* Initialize the data map for the job executor.
*/
private JobDataMap initDataMap(final String jobName,
final Object job,
final InternalScheduleOptions options) {
final JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put(DATA_MAP_OBJECT, job);
jobDataMap.put(DATA_MAP_NAME, jobName);
jobDataMap.put(DATA_MAP_LOGGER, this.logger);
jobDataMap.put(DATA_MAP_OPTIONS, options);
return jobDataMap;
}
/**
* Create the job detail.
*/
private JobDetail createJobDetail(final String name,
final JobDataMap jobDataMap,
final boolean concurrent) {
return JobBuilder.newJob((concurrent ? QuartzJobExecutor.class : NonParallelQuartzJobExecutor.class))
.withIdentity(name)
.usingJobData(jobDataMap)
.build();
}
/**
* Check the job object, either runnable or job is allowed
*/
private void checkJob(final Object job)
throws IllegalArgumentException {
if (!(job instanceof Runnable) && !(job instanceof Job)) {
throw new IllegalArgumentException("Job object is neither an instance of " + Runnable.class.getName() + " nor " + Job.class.getName());
}
}
/** Used by the web console plugin. */
org.quartz.Scheduler getScheduler() {
return this.scheduler;
}
/**
* @see org.apache.karaf.scheduler.Scheduler#NOW()
*/
public ScheduleOptions NOW() {
return AT(new Date());
}
/**
* @see org.apache.karaf.scheduler.Scheduler#NOW(int, long)
*/
public ScheduleOptions NOW(int times, long period) {
return AT(new Date(), times, period);
}
/**
* @see org.apache.karaf.scheduler.Scheduler#AT(java.util.Date)
*/
public ScheduleOptions AT(Date date) {
return new InternalScheduleOptions(date);
}
/**
* @see org.apache.karaf.scheduler.Scheduler#AT(java.util.Date, int, long)
*/
public ScheduleOptions AT(Date date, int times, long period) {
return new InternalScheduleOptions(date, times, period);
}
/**
* @see org.apache.karaf.scheduler.Scheduler#EXPR(java.lang.String)
*/
public ScheduleOptions EXPR(String expression) {
return new InternalScheduleOptions(expression);
}
/**
* Schedule a job
* @see org.apache.karaf.scheduler.Scheduler#schedule(java.lang.Object, org.apache.karaf.scheduler.ScheduleOptions)
* @throws SchedulerException if the job can't be scheduled
* @throws IllegalArgumentException If the preconditions are not met
*/
public void schedule(final Object job, final ScheduleOptions options) throws IllegalArgumentException, SchedulerException {
this.checkJob(job);
if ( !(options instanceof InternalScheduleOptions)) {
throw new IllegalArgumentException("Options has not been created via schedule or is null.");
}
final InternalScheduleOptions opts = (InternalScheduleOptions)options;
if ( opts.argumentException != null ) {
throw opts.argumentException;
}
// as this method might be called from unbind and during
// unbind a deactivate could happen, we check the scheduler first
final org.quartz.Scheduler s = this.scheduler;
if ( s == null ) {
throw new IllegalStateException("Scheduler is not available anymore.");
}
final String name;
if ( opts.name != null ) {
// if there is already a job with the name, remove it first
try {
final JobKey key = JobKey.jobKey(opts.name);
final JobDetail jobdetail = s.getJobDetail(key);
if (jobdetail != null) {
s.deleteJob(key);
this.logger.debug("Unscheduling job with name {}", opts.name);
}
} catch (final SchedulerException ignored) {
// ignore
}
name = opts.name;
} else {
name = job.getClass().getName() + ':' + UUID.randomUUID();
opts.name = name;
}
final Trigger trigger = opts.trigger.withIdentity(name).build();
// create the data map
final JobDataMap jobDataMap = this.initDataMap(name, job, opts);
final JobDetail detail = this.createJobDetail(name, jobDataMap, opts.canRunConcurrently);
this.logger.debug("Scheduling job {} with name {} and trigger {}", job, name, trigger);
s.scheduleJob(detail, trigger);
}
/**
* @see org.apache.karaf.scheduler.Scheduler#unschedule(java.lang.String)
*/
public boolean unschedule(final String jobName) {
final org.quartz.Scheduler s = this.scheduler;
if ( jobName != null && s != null ) {
try {
final JobKey key = JobKey.jobKey(jobName);
final JobDetail jobdetail = s.getJobDetail(key);
if (jobdetail != null) {
s.deleteJob(key);
this.logger.debug("Unscheduling job with name {}", jobName);
return true;
}
} catch (final SchedulerException ignored) {
// ignore
}
}
return false;
}
@Override
public Map<Object, ScheduleOptions> getJobs() throws SchedulerException {
Map<Object, ScheduleOptions> jobs = new HashMap<>();
org.quartz.Scheduler s = this.scheduler;
if (s != null) {
for (String group : s.getJobGroupNames()) {
for (JobKey key : s.getJobKeys(GroupMatcher.jobGroupEquals(group))) {
JobDetail detail = s.getJobDetail(key);
ScheduleOptions options = (ScheduleOptions) detail.getJobDataMap().get(DATA_MAP_OPTIONS);
Object job = detail.getJobDataMap().get(DATA_MAP_OBJECT);
jobs.put(job, options);
}
}
}
return jobs;
}
}