/**
* 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
*
* 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.deephacks.westty.internal.job;
import com.google.common.base.Optional;
import com.google.common.base.Stopwatch;
import org.deephacks.confit.ConfigContext;
import org.deephacks.confit.model.AbortRuntimeException;
import org.deephacks.confit.model.Events;
import org.deephacks.westty.config.DataSourceConfig;
import org.deephacks.westty.config.JobConfig;
import org.deephacks.westty.config.JobSchedulerConfig;
import org.deephacks.westty.job.Job;
import org.deephacks.westty.job.Schedule;
import org.deephacks.westty.spi.ProviderShutdownEvent;
import org.deephacks.westty.spi.ProviderStartupEvent;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
import org.quartz.PersistJobDataAfterExecution;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerKey;
import org.quartz.core.QuartzScheduler;
import org.quartz.core.QuartzSchedulerResources;
import org.quartz.impl.StdJobRunShellFactory;
import org.quartz.impl.StdScheduler;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.jdbcjobstore.JobStoreTX;
import org.quartz.impl.jdbcjobstore.Semaphore;
import org.quartz.impl.jdbcjobstore.UpdateLockRowSemaphore;
import org.quartz.simpl.CascadingClassLoadHelper;
import org.quartz.utils.DBConnectionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.sql.DataSource;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import static org.quartz.CronScheduleBuilder.cronSchedule;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;
@Singleton
class JobSchedulerBootstrap extends StdSchedulerFactory {
private static final Logger log = LoggerFactory.getLogger(JobSchedulerBootstrap.class);
private static final String JOB_CLASS_KEY = "JOB_ID_KEY";
private static final String LAST_EXECUTION_TIMESTAMP = "LAST_EXECUTION_TIMESTAMP";
private static final String DERBY_EMBEDDED = "org.apache.derby.jdbc.EmbeddedDriver";
private static final String DERBY_INSTALL_DDL = "META-INF/install_job_derby.ddl";
private Scheduler scheduler;
private final JobThreadPool threadPool;
private final JobConnectionProvider provider;
private final JobExecutor executor;
private boolean isDerbyEmbedded;
private JobSchedulerConfig schedulerConfig;
private DataSourceConfig dataSourceConfig;
private ConfigContext config;
@Inject
public JobSchedulerBootstrap(JobSchedulerConfig schedulerConfig, DataSourceConfig dataSourceConfig,
ConfigContext config, JobThreadPool threadPool,
JobExecutor executor, DataSource dataSource) {
this.schedulerConfig = schedulerConfig;
this.config = config;
this.threadPool = threadPool;
this.provider = new JobConnectionProvider(dataSource);
this.executor = executor;
this.dataSourceConfig = dataSourceConfig;
if (this.dataSourceConfig.getDriver().equals(DERBY_EMBEDDED)) {
isDerbyEmbedded = true;
}
}
public void startup(@Observes ProviderStartupEvent event) throws SchedulerException {
if (isDerbyEmbedded) {
SQLExec exec = new SQLExec(dataSourceConfig.getUser(), dataSourceConfig.getPassword(),
dataSourceConfig.getUrl());
try {
exec.executeResource(DERBY_INSTALL_DDL, false);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
DBConnectionManager manager = DBConnectionManager.getInstance();
manager.addConnectionProvider(provider.getDataSourceName(), provider);
CascadingClassLoadHelper cl = new CascadingClassLoadHelper();
QuartzSchedulerResources resources = new QuartzSchedulerResources();
resources.setInstanceId(schedulerConfig.getInstanceId());
resources.setName(schedulerConfig.getInstanceName());
resources.setMakeSchedulerThreadDaemon(true);
resources.setThreadName(schedulerConfig.getInstanceName());
resources.setThreadPool(threadPool);
resources.setThreadExecutor(executor);
resources.setRunUpdateCheck(false);
resources.setMaxBatchSize(schedulerConfig.getBatchTriggerAcquisitionMaxCount());
resources.setBatchTimeWindow(schedulerConfig.getBatchTriggerAcquisitionFireAheadTimeWindow());
QuartzScheduler qs = new QuartzScheduler(resources, schedulerConfig.getIdleTimeWait(),
schedulerConfig.getDbFailureRetryInterval());
scheduler = new StdScheduler(qs);
StdJobRunShellFactory jobShell = new StdJobRunShellFactory();
resources.setJobRunShellFactory(jobShell);
JobStoreTX store = new JobStoreTX();
store.setLockHandler(getLockStrategy(schedulerConfig.getInstanceName()));
store.setLockOnInsert(true);
store.setInstanceName(schedulerConfig.getInstanceName());
store.setInstanceId(schedulerConfig.getInstanceId());
store.setIsClustered(schedulerConfig.getIsClustered());
store.setClusterCheckinInterval(schedulerConfig.getClusterCheckinInterval());
store.setTxIsolationLevelSerializable(true);
store.setDataSource(provider.getDataSourceName());
resources.setJobStore(store);
cl.initialize();
jobShell.initialize(scheduler);
store.initialize(cl, qs.getSchedulerSignaler());
try {
log.info("Starting scheduler");
scheduler.start();
schedule();
} catch (Exception e) {
e.printStackTrace();
}
}
public Semaphore getLockStrategy(String instanceName) {
UpdateLockRowSemaphore lock = new UpdateLockRowSemaphore();
lock.setSchedName(instanceName);
return lock;
}
public void shutdown(@Observes ProviderShutdownEvent event) {
try {
log.info("Shutdown scheduler");
if (scheduler != null){
scheduler.shutdown(true);
}
} catch (SchedulerException e) {
log.warn(e.getMessage(), e);
}
}
public void reschedule() {
try {
for (Class<? extends Job> cls : JobExtension.getJobs()) {
String id = cls.getName();
TriggerKey triggerKey = new TriggerKey(id);
JobKey jobKey = new JobKey(id);
Trigger trigger = newTrigger().withIdentity(triggerKey)
.withSchedule(cronSchedule(getCron(cls))).forJob(jobKey).build();
log.debug("Reschedule {}", id);
scheduler.rescheduleJob(triggerKey, trigger);
}
} catch (SchedulerException e) {
throw new RuntimeException(e);
}
}
public void schedule() throws SchedulerException {
for (Class<? extends Job> cls : JobExtension.getJobs()) {
String id = cls.getSimpleName();
try {
config.get(id, JobConfig.class);
} catch (AbortRuntimeException e) {
if (e.getEvent().getCode() == Events.CFG304) {
config.registerDefault(new JobConfig(id));
}
}
TriggerKey triggerKey = new TriggerKey(cls.getName());
JobKey jobKey = new JobKey(cls.getName());
JobDetail jobdetail = newJob(JobDelegate.class)
.usingJobData(JOB_CLASS_KEY, cls.getName()).withIdentity(jobKey).build();
String cron = getCron(cls);
Trigger trigger = newTrigger().withIdentity(triggerKey)
.withSchedule(cronSchedule(cron)).forJob(jobdetail).build();
if (!scheduler.checkExists(jobKey)) {
log.info("Scheduling {}", cls);
scheduler.scheduleJob(jobdetail, trigger);
}
}
}
private String getCron(Class<? extends Job> cls) {
Optional<JobConfig> jobConfig = config.get(cls.getSimpleName(), JobConfig.class);
if (!jobConfig.isPresent()) {
return cls.getAnnotation(Schedule.class).value();
}
return jobConfig.get().getCronExpression();
}
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
@SuppressWarnings({ "rawtypes", "unchecked" })
public static class JobDelegate implements org.quartz.Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDataMap map = context.getJobDetail().getJobDataMap();
if (map.get(LAST_EXECUTION_TIMESTAMP) != null) {
long ts = Long.parseLong(map.getString(LAST_EXECUTION_TIMESTAMP));
if (System.currentTimeMillis() - ts < 2000) {
return;
}
}
String className = map.getString(JOB_CLASS_KEY);
Class<? extends Job> cls;
try {
cls = Class.forName(className).asSubclass(Job.class);
} catch (ClassNotFoundException e) {
throw new JobExecutionException(e);
}
Logger logger = LoggerFactory.getLogger(cls);
BeanManager beanManager = JobExtension.getBeanManager();
Set<Bean<?>> jobBeans = beanManager.getBeans(cls);
Bean<?> jobBean = beanManager.resolve(jobBeans);
CreationalContext cc = beanManager.createCreationalContext(jobBean);
Job job = (Job) beanManager.getReference(jobBean, Job.class, cc);
Stopwatch s = new Stopwatch().start();
try {
logger.debug("Executing.");
job.execute(new JobDataImpl(map));
map.putAsString(LAST_EXECUTION_TIMESTAMP, System.currentTimeMillis());
} catch (Exception e) {
logger.warn("Unexpected exception", e);
}
logger.debug("Execution took " + s.elapsedTime(TimeUnit.NANOSECONDS) + "ns");
}
}
}