/*
* Copyright 2015-2016 http://hsweb.me
*
* 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.hsweb.web.service.impl.quartz;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.hsweb.commons.MD5;
import org.hsweb.commons.StringUtils;
import org.hsweb.expands.script.engine.DynamicScriptEngine;
import org.hsweb.expands.script.engine.DynamicScriptEngineFactory;
import org.hsweb.expands.script.engine.ExecuteResult;
import org.hsweb.expands.script.engine.ScriptContext;
import org.hsweb.web.bean.po.quartz.QuartzJob;
import org.hsweb.web.bean.po.quartz.QuartzJobHistory;
import org.hsweb.web.core.exception.BusinessException;
import org.hsweb.web.dao.quartz.QuartzJobHistoryMapper;
import org.hsweb.web.dao.quartz.QuartzJobMapper;
import org.hsweb.web.service.DeleteService;
import org.hsweb.web.service.GenericService;
import org.hsweb.web.service.impl.AbstractServiceImpl;
import org.hsweb.web.service.quartz.QuartzJobHistoryService;
import org.hsweb.web.service.quartz.QuartzJobService;
import org.joda.time.DateTime;
import org.quartz.*;
import org.quartz.impl.triggers.CronTriggerImpl;
import org.quartz.spi.MutableTrigger;
import org.quartz.spi.OperableTrigger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import javax.annotation.Resource;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import static org.hsweb.web.bean.po.quartz.QuartzJob.Property.enabled;
import static org.hsweb.web.bean.po.quartz.QuartzJob.Property.id;
import static org.hsweb.web.bean.po.quartz.QuartzJobHistory.Status.FAIL;
import static org.hsweb.web.bean.po.quartz.QuartzJobHistory.Status.SUCCESS;
/**
* 定时调度任务服务类
* Created by generator
*/
@Service("quartzJobService")
public class QuartzJobServiceImpl extends AbstractServiceImpl<QuartzJob, String> implements QuartzJobService {
private static final String CACHE_KEY = "quartz-job";
@Resource
protected QuartzJobMapper quartzJobMapper;
@Autowired
protected Scheduler scheduler;
@Resource
protected QuartzJobHistoryService quartzJobHistoryService;
@Resource
protected QuartzJobHistoryMapper quartzJobHistoryMapper;
@Override
protected QuartzJobMapper getMapper() {
return this.quartzJobMapper;
}
@Override
@Cacheable(value = CACHE_KEY, key = "'id:'+#id")
public QuartzJob selectByPk(String id) {
return super.selectByPk(id);
}
@Override
@CacheEvict(value = CACHE_KEY, key = "'id:'+#data.id")
public String insert(QuartzJob data) {
data.setEnabled(true);
String id = super.insert(data);
startJob(data);
return id;
}
@Override
@CacheEvict(value = CACHE_KEY, key = "'id:'+#data.id")
public int update(QuartzJob data) {
QuartzJob old = selectByPk(data.getId());
assertNotNull(old, "任务不存在");
int i = createUpdate(data).fromBean().excludes(enabled).where(id).exec();
if (old.isEnabled()) {
deleteJob(data.getId());
startJob(data);
}
return i;
}
@Override
public int saveOrUpdate(QuartzJob job) {
throw new UnsupportedOperationException();
}
@Override
@CacheEvict(value = CACHE_KEY, key = "'id:'+#id")
public void enable(String id) {
createUpdate().set(enabled, true).where(QuartzJob.Property.id, id).exec();
startJob(getMapper().selectByPk(id));
}
@Override
@CacheEvict(value = CACHE_KEY, key = "'id:'+#id")
public void disable(String id) {
createUpdate().set(enabled, false).where(QuartzJob.Property.id, id).exec();
deleteJob(id);
}
@Override
@CacheEvict(value = CACHE_KEY, key = "'id:'+#id")
public int delete(String id) {
deleteJob(id);
DeleteService.createDelete(quartzJobHistoryMapper).where(QuartzJobHistory.Property.jobId, id).exec();
return super.delete(id);
}
void deleteJob(String id) {
JobKey jobKey = createJobKey(id);
try {
if (scheduler.checkExists(jobKey)) {
scheduler.deleteJob(jobKey);
}
} catch (SchedulerException e) {
throw new BusinessException("更新任务失败", e, 500);
}
}
@Override
public List<Date> getExecTimes(String cron, int number) {
try {
CronTriggerImpl cronTriggerImpl = new CronTriggerImpl();
cronTriggerImpl.setCronExpression(cron);
return computeFireTimesBetween(cronTriggerImpl, null, new Date(), new DateTime().plusYears(5).toDate(), number);
} catch (Exception e) {
throw new BusinessException(e.getMessage(), e, 500);
}
}
@Override
@Transactional
public Object execute(String id, Map<String, Object> var) {
Assert.notNull(id, "定时任务ID错误");
QuartzJob job = selectByPk(id);
Assert.notNull(job, "任务不存在");
String hisId = quartzJobHistoryService.createAndInsertHistory(id);
String strRes = null;
QuartzJobHistory.Status status = FAIL;
try {
if (logger.isDebugEnabled())
logger.debug("start job [{}]", job.getName());
DynamicScriptEngine engine = DynamicScriptEngineFactory.getEngine(job.getLanguage());
String scriptId = "quartz.job.".concat(id);
try {
if (!engine.compiled(scriptId)) {
engine.compile(scriptId, job.getScript());
} else {
ScriptContext scriptContext = engine.getContext(scriptId);
//脚本发生了变化,自动重新编译
if (!MD5.defaultEncode(job.getScript()).equals(scriptContext.getMd5())) {
if (logger.isDebugEnabled())
logger.debug("script is changed,recompile....");
engine.compile(scriptId, job.getScript());
}
}
} catch (Exception e) {
throw new BusinessException("编译任务脚本失败");
}
if (logger.isDebugEnabled())
logger.debug("job running...");
ExecuteResult result = engine.execute(scriptId, var);
if (logger.isDebugEnabled())
logger.debug("job end...{} ", result.isSuccess() ? "success" : "fail");
if (result.isSuccess()) {
Object res = result.getResult();
if (res instanceof String)
strRes = ((String) res);
else strRes = JSON.toJSONString(res);
status = SUCCESS;
} else {
status = FAIL;
if (result.getException() != null) {
strRes = StringUtils.throwable2String(result.getException());
logger.error("job failed", result.getException());
if (result.getException() instanceof RuntimeException) {
throw ((RuntimeException) result.getException());
}
throw new RuntimeException(result.getException());
} else {
strRes = result.getMessage();
logger.error("job failed {}", strRes);
throw new RuntimeException(strRes);
}
}
} finally {
quartzJobHistoryService.endHistory(hisId, strRes, status);
}
return strRes;
}
public static List<Date> computeFireTimesBetween(OperableTrigger trigger,
org.quartz.Calendar cal, Date from, Date to, int num) {
LinkedList<Date> lst = new LinkedList<>();
OperableTrigger t = (OperableTrigger) trigger.clone();
if (t.getNextFireTime() == null) {
t.setStartTime(from);
t.setEndTime(to);
t.computeFirstFireTime(cal);
}
for (int i = 0; i < num; i++) {
Date d = t.getNextFireTime();
if (d != null) {
if (d.before(from)) {
t.triggered(cal);
continue;
}
if (d.after(to)) {
break;
}
lst.add(d);
t.triggered(cal);
} else {
break;
}
}
return java.util.Collections.unmodifiableList(lst);
}
void startJob(QuartzJob job) {
assertNotNull(job, "任务不存在");
JobKey key = createJobKey(job.getId());
JobDetail jobDetail = JobBuilder.newJob(SimpleJob.class)
.withIdentity(key)
.setJobData(createJobDataMap(job.getParameters()))
.usingJobData(SimpleJobFactory.QUARTZ_ID_KEY, job.getId())
.withDescription(job.getName() + (job.getRemark() == null ? "" : job.getRemark()))
.build();
MutableTrigger trigger = CronScheduleBuilder.cronSchedule(job.getCron()).build();
trigger.setKey(createTriggerKey(job.getId()));
try {
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
throw new BusinessException("创建定时任务失败!", e, 500);
}
}
JobDataMap createJobDataMap(String parameters) {
JobDataMap map = new JobDataMap();
if (!StringUtils.isNullOrEmpty(parameters)) {
JSONArray jsonArray = JSON.parseArray(parameters);
for (int i = 0; i < jsonArray.size(); i++) {
JSONObject o = jsonArray.getJSONObject(i);
map.put(o.getString("key"), o.get("value"));
}
}
return map;
}
JobKey createJobKey(String jobId) {
return new JobKey(jobId, "hsweb.scheduler");
}
TriggerKey createTriggerKey(String jobId) {
return new TriggerKey(jobId, "hsweb.scheduler");
}
}