Bladeren bron

- 1、自研调度组件,移除quartz依赖:一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
- 触发:单节点周期性触发,运行事件如delayqueue;
- 调度:集群竞争,负载方式协同处理,锁竞争-更新触发信息-推送时间轮-锁释放-锁竞争;
- 2、底层表结构重构:移除11张quartz相关表,并对现有表结构优化梳理;
- 3、底层线程模型重构:移除Quartz线程池,降低系统线程与内存开销;

xuxueli 6 jaren geleden
bovenliggende
commit
e01d2bc9b5
35 gewijzigde bestanden met toevoegingen van 2770 en 1130 verwijderingen
  1. 1 1
      README.md
  2. 33 61
      doc/XXL-JOB官方文档.md
  3. 0 168
      doc/db/tables_mysql(备份,请忽略).sql
  4. 18 155
      doc/db/tables_xxl_job.sql
  5. 0 1
      pom.xml
  6. 0 7
      xxl-job-admin/pom.xml
  7. 2 2
      xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
  8. 3 3
      xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
  9. 7 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
  10. 0 43
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
  11. 147 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
  12. 1668 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
  13. 0 33
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
  14. 26 10
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
  15. 32 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
  16. 413 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
  17. 58 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
  18. 0 58
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java
  19. 2 2
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
  20. 2 2
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
  21. 0 413
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java
  22. 218 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java
  23. 2 2
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
  24. 6 1
      xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java
  25. 6 6
      xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java
  26. 38 67
      xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java
  27. 6 6
      xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobGroupMapper.xml
  28. 44 12
      xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml
  29. 5 5
      xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogGlueMapper.xml
  30. 13 13
      xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogMapper.xml
  31. 5 5
      xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobRegistryMapper.xml
  32. 6 6
      xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobUserMapper.xml
  33. 0 29
      xxl-job-admin/src/main/resources/quartz.properties
  34. 8 18
      xxl-job-admin/src/main/resources/static/js/jobinfo.index.1.js
  35. 1 1
      xxl-job-admin/src/main/resources/templates/jobinfo/jobinfo.index.ftl

+ 1 - 1
README.md

@@ -52,7 +52,7 @@ XXL-JOB是一个轻量级分布式任务调度平台,其核心设计目标是
 ## Features
 - 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
 - 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
 - 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
 - 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
 - 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;

+ 33 - 61
doc/XXL-JOB官方文档.md

@@ -20,7 +20,7 @@ XXL-JOB是一个轻量级分布式任务调度平台,其核心设计目标是
 ### 1.3 特性
 - 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
 - 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
 - 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
 - 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
 - 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
@@ -776,18 +776,16 @@ try{
     - /xxl-job-executor-samples :执行器,Sample示例项目(大家可以在该项目上进行开发,也可以将现有项目改造生成执行器项目)
 
 ### 5.2 “调度数据库”配置
-XXL-JOB调度模块基于Quartz集群实现,其“调度数据库”是在Quartz的11张集群mysql表基础上扩展而成。
+XXL-JOB调度模块基于自研调度组件并支持集群部署,调度数据库表说明如下:
 
-XXL-JOB首先定制了Quartz原生表结构前缀(XXL_JOB_QRTZ_)。
+    - XXL_JOB_LOCK:任务调度锁表;
+    - XXL_JOB_GROUP:执行器信息表,维护任务执行器信息;
+    - XXL_JOB_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
+    - XXL_JOB_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
+    - XXL_JOB_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
+    - XXL_JOB_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
+    - XXL_JOB_USER:系统用户表;
 
-然后,在此基础上新增了几张张扩展表,如下:
-    - XXL_JOB_QRTZ_TRIGGER_GROUP:执行器信息表,维护任务执行器信息;
-    - XXL_JOB_QRTZ_TRIGGER_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
-    - XXL_JOB_QRTZ_TRIGGER_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
-    - XXL_JOB_QRTZ_TRIGGER_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
-    - XXL_JOB_QRTZ_TRIGGER_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
-
-因此,XXL-JOB调度数据库共计用于16张数据库表。
 
 ### 5.3 架构设计
 #### 5.3.1 设计思想
@@ -820,58 +818,30 @@ Quartz作为开源作业调度中的佼佼者,是作业调度的首选。但
 
 XXL-JOB弥补了quartz的上述不足之处。
 
-#### 5.4.2 RemoteHttpJobBean
-常规Quartz的开发,任务逻辑一般维护在QuartzJobBean中,耦合很严重。XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块中的所有调度任务使用同一个QuartzJobBean,即RemoteHttpJobBean。不同的调度任务将各自参数维护在各自扩展表数据中,当触发RemoteHttpJobBean执行时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。
+#### 5.4.2 自研调度模块
+XXL-JOB最终选择自研调度组件(早期调度组件基于Quartz);一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
 
-这种调用模型类似RPC调用,RemoteHttpJobBean提供调用代理的功能,而执行器提供远程服务的功能。
+XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块进行任务调度时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。这种调用模型类似RPC调用,调度中心提供调用代理的功能,而执行器提供远程服务的功能。
 
 #### 5.4.3 调度中心HA(集群)
-基于Quartz的集群方案,数据库选用Mysql;集群分布式并发环境中使用QUARTZ定时任务调度,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
-
-```
-# for cluster
-org.quartz.jobStore.tablePrefix = XXL_JOB_QRTZ_
-org.quartz.scheduler.instanceId: AUTO
-org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
-org.quartz.jobStore.isClustered: true
-org.quartz.jobStore.clusterCheckinInterval: 1000
-```
+基于数据库的集群方案,数据库选用Mysql;集群分布式并发环境中进行定时任务调度时,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
 
 #### 5.4.4 调度线程池
 调度采用线程池方式实现,避免单线程因阻塞而引起任务调度延迟。
 
-```
-org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
-org.quartz.threadPool.threadCount: 50
-org.quartz.threadPool.threadPriority: 5
-org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
-```
 
-#### 5.4.5 @DisallowConcurrentExecution
-XXL-JOB调度模块的“调度中心”默认不使用该注解,即默认开启并行机制,因为RemoteHttpJobBean为公共QuartzJobBean,这样在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
+#### 5.4.5 并行调度
+XXL-JOB调度模块默认采用并行机制,在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
 
 XXL-JOB的每个调度任务虽然在调度模块是并行调度执行的,但是任务调度传递到任务模块的“执行器”确实串行执行的,同时支持任务终止。
 
-#### 5.4.6 misfire
-错过了触发时间,处理规则。
-可能原因:服务重启;调度线程被QuartzJobBean阻塞,线程被耗尽;某个任务启用了@DisallowConcurrentExecution,上次调度持续阻塞,下次调度被错过;
+#### 5.4.6 过期处理策略
+任务调度错过了触发时间:
+- 可能原因:服务重启;调度线程被阻塞,线程被耗尽;上次调度持续阻塞,下次调度被错过;
+- 处理策略:
+    - 过期5s内:立即触发一次,并计算下次触发时间;
+    - 过期超过5s:忽略过期触发,计算下次触发时间;
 
-quartz.properties中关于misfire的阀值配置如下,单位毫秒:
-```
-org.quartz.jobStore.misfireThreshold: 60000
-```
-
-Misfire规则:
-    withMisfireHandlingInstructionDoNothing:不触发立即执行,等待下次调度;
-    withMisfireHandlingInstructionIgnoreMisfires:以错过的第一个频率时间立刻开始执行;
-    withMisfireHandlingInstructionFireAndProceed:以当前时间为触发频率立刻触发一次执行;
-
-XXL-JOB默认misfire规则为:withMisfireHandlingInstructionDoNothing
-
-```
-CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobInfo.getJobCron()).withMisfireHandlingInstructionDoNothing();
-CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-```
 
 #### 5.4.7 日志回调服务
 调度模块的“调度中心”作为Web服务部署时,一方面承担调度中心功能,另一方面也为执行器提供API服务。
@@ -926,7 +896,7 @@ xxl-job-admin#com.xxl.job.admin.controller.JobApiController.callback
 
 #### 5.4.11  全异步化 & 轻量级
 
-- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在quartz的QuartzJobBean中执行业务逻辑,极大的降低了调度线程占用时间;
+- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在调度中心内部执行业务逻辑,极大的降低了调度线程占用时间;
     - 异步调度:调度中心每次任务触发时仅发送一次调度请求,该调度请求首先推送“异步调度队列”,然后异步推送给远程执行器
     - 异步执行:执行器会将请求存入“异步执行队列”并且立即响应调度中心,异步运行。
 - 轻量级设计:XXL-JOB调度中心中每个JOB逻辑非常 “轻”,在全异步化的基础上,单个JOB一次运行平均耗时基本在 "10ms" 之内(基本为一次请求的网络开销);因此,可以保证使用有限的线程支撑大量的JOB并发运行;
@@ -988,7 +958,7 @@ XXL-JOB会为每次调度请求生成一个单独的日志文件,需要通过
 自v1.5版本之后, 任务取消了"任务执行机器"属性, 改为通过任务注册和自动发现的方式, 动态获取远程执行器地址并执行。
 
     AppName: 每个执行器机器集群的唯一标示, 任务注册以 "执行器" 为最小粒度进行注册; 每个任务通过其绑定的执行器可感知对应的执行器机器列表;
-    注册表: 见"XXL_JOB_QRTZ_TRIGGER_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
+    注册表: 见"XXL_JOB_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
     执行器注册: 任务注册Beat周期默认30s; 执行器以一倍Beat进行执行器注册, 调度中心以一倍Beat进行动态任务发现; 注册信息的失效时间被三倍Beat; 
     执行器注册摘除:执行器销毁时,将会主动上报调度中心并摘除对应的执行器机器信息,提高心跳注册的实时性;
     
@@ -1479,15 +1449,17 @@ Tips: 历史版本(V1.3.x)目前已经Release至稳定版本, 进入维护阶段
 
 
 ### 6.25 版本 v2.1.0 Release Notes[规划中]
-- 1、[规划中] 移除quartz:精简底层实现,优化已知问题
+- 1、自研调度组件,移除quartz依赖:一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性
     - 触发:单节点周期性触发,运行事件如delayqueue;
-    - 调度:集群竞争,负载方式协同处理,竞争-加入时间轮-释放-竞争;
-- 2、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
-- 3、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
-- 4、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
-- 5、调度线程池参数调优;
-- 6、升级xxl-rpc至较新版本,并清理冗余POM;
-- 7、注册表索引优化,缓解锁表问题;
+    - 调度:集群竞争,负载方式协同处理,锁竞争-更新触发信息-推送时间轮-锁释放-锁竞争;
+- 2、底层表结构重构:移除11张quartz相关表,并对现有表结构优化梳理;
+- 3、底层线程模型重构:移除Quartz线程池,降低系统线程与内存开销;
+- 4、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
+- 5、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
+- 6、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
+- 7、调度线程池参数调优;
+- 8、升级xxl-rpc至较新版本,并清理冗余POM;
+- 9、注册表索引优化,缓解锁表问题;
 
 
 ### TODO LIST

+ 0 - 168
doc/db/tables_mysql(备份,请忽略).sql

@@ -1,168 +0,0 @@
-#
-# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
-#
-# PLEASE consider using mysql with innodb tables to avoid locking issues
-#
-# In your Quartz properties file, you'll need to set
-# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
-#
-
-DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
-DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
-DROP TABLE IF EXISTS QRTZ_LOCKS;
-DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
-DROP TABLE IF EXISTS QRTZ_CALENDARS;
-
-
-CREATE TABLE QRTZ_JOB_DETAILS
-  (
-    SCHED_NAME VARCHAR(120) NOT NULL,
-    JOB_NAME  VARCHAR(200) NOT NULL,
-    JOB_GROUP VARCHAR(200) NOT NULL,
-    DESCRIPTION VARCHAR(250) NULL,
-    JOB_CLASS_NAME   VARCHAR(250) NOT NULL,
-    IS_DURABLE VARCHAR(1) NOT NULL,
-    IS_NONCONCURRENT VARCHAR(1) NOT NULL,
-    IS_UPDATE_DATA VARCHAR(1) NOT NULL,
-    REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
-    JOB_DATA BLOB NULL,
-    PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_TRIGGERS
-  (
-    SCHED_NAME VARCHAR(120) NOT NULL,
-    TRIGGER_NAME VARCHAR(200) NOT NULL,
-    TRIGGER_GROUP VARCHAR(200) NOT NULL,
-    JOB_NAME  VARCHAR(200) NOT NULL,
-    JOB_GROUP VARCHAR(200) NOT NULL,
-    DESCRIPTION VARCHAR(250) NULL,
-    NEXT_FIRE_TIME BIGINT(13) NULL,
-    PREV_FIRE_TIME BIGINT(13) NULL,
-    PRIORITY INTEGER NULL,
-    TRIGGER_STATE VARCHAR(16) NOT NULL,
-    TRIGGER_TYPE VARCHAR(8) NOT NULL,
-    START_TIME BIGINT(13) NOT NULL,
-    END_TIME BIGINT(13) NULL,
-    CALENDAR_NAME VARCHAR(200) NULL,
-    MISFIRE_INSTR SMALLINT(2) NULL,
-    JOB_DATA BLOB NULL,
-    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
-    FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-        REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPLE_TRIGGERS
-  (
-    SCHED_NAME VARCHAR(120) NOT NULL,
-    TRIGGER_NAME VARCHAR(200) NOT NULL,
-    TRIGGER_GROUP VARCHAR(200) NOT NULL,
-    REPEAT_COUNT BIGINT(7) NOT NULL,
-    REPEAT_INTERVAL BIGINT(12) NOT NULL,
-    TIMES_TRIGGERED BIGINT(10) NOT NULL,
-    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
-    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CRON_TRIGGERS
-  (
-    SCHED_NAME VARCHAR(120) NOT NULL,
-    TRIGGER_NAME VARCHAR(200) NOT NULL,
-    TRIGGER_GROUP VARCHAR(200) NOT NULL,
-    CRON_EXPRESSION VARCHAR(200) NOT NULL,
-    TIME_ZONE_ID VARCHAR(80),
-    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
-    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPROP_TRIGGERS
-  (
-    SCHED_NAME VARCHAR(120) NOT NULL,
-    TRIGGER_NAME VARCHAR(200) NOT NULL,
-    TRIGGER_GROUP VARCHAR(200) NOT NULL,
-    STR_PROP_1 VARCHAR(512) NULL,
-    STR_PROP_2 VARCHAR(512) NULL,
-    STR_PROP_3 VARCHAR(512) NULL,
-    INT_PROP_1 INT NULL,
-    INT_PROP_2 INT NULL,
-    LONG_PROP_1 BIGINT NULL,
-    LONG_PROP_2 BIGINT NULL,
-    DEC_PROP_1 NUMERIC(13,4) NULL,
-    DEC_PROP_2 NUMERIC(13,4) NULL,
-    BOOL_PROP_1 VARCHAR(1) NULL,
-    BOOL_PROP_2 VARCHAR(1) NULL,
-    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
-    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_BLOB_TRIGGERS
-  (
-    SCHED_NAME VARCHAR(120) NOT NULL,
-    TRIGGER_NAME VARCHAR(200) NOT NULL,
-    TRIGGER_GROUP VARCHAR(200) NOT NULL,
-    BLOB_DATA BLOB NULL,
-    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
-    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CALENDARS
-  (
-    SCHED_NAME VARCHAR(120) NOT NULL,
-    CALENDAR_NAME  VARCHAR(200) NOT NULL,
-    CALENDAR BLOB NOT NULL,
-    PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
-  (
-    SCHED_NAME VARCHAR(120) NOT NULL,
-    TRIGGER_GROUP  VARCHAR(200) NOT NULL,
-    PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_FIRED_TRIGGERS
-  (
-    SCHED_NAME VARCHAR(120) NOT NULL,
-    ENTRY_ID VARCHAR(95) NOT NULL,
-    TRIGGER_NAME VARCHAR(200) NOT NULL,
-    TRIGGER_GROUP VARCHAR(200) NOT NULL,
-    INSTANCE_NAME VARCHAR(200) NOT NULL,
-    FIRED_TIME BIGINT(13) NOT NULL,
-    SCHED_TIME BIGINT(13) NOT NULL,
-    PRIORITY INTEGER NOT NULL,
-    STATE VARCHAR(16) NOT NULL,
-    JOB_NAME VARCHAR(200) NULL,
-    JOB_GROUP VARCHAR(200) NULL,
-    IS_NONCONCURRENT VARCHAR(1) NULL,
-    REQUESTS_RECOVERY VARCHAR(1) NULL,
-    PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE QRTZ_SCHEDULER_STATE
-  (
-    SCHED_NAME VARCHAR(120) NOT NULL,
-    INSTANCE_NAME VARCHAR(200) NOT NULL,
-    LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
-    CHECKIN_INTERVAL BIGINT(13) NOT NULL,
-    PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE QRTZ_LOCKS
-  (
-    SCHED_NAME VARCHAR(120) NOT NULL,
-    LOCK_NAME  VARCHAR(40) NOT NULL,
-    PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-commit;

+ 18 - 155
doc/db/tables_xxl_job.sql

@@ -3,153 +3,7 @@ use `xxl-job`;
 
 
 
-CREATE TABLE XXL_JOB_QRTZ_JOB_DETAILS
-  (
-    SCHED_NAME VARCHAR(120) NOT NULL,
-    JOB_NAME  VARCHAR(200) NOT NULL,
-    JOB_GROUP VARCHAR(200) NOT NULL,
-    DESCRIPTION VARCHAR(250) NULL,
-    JOB_CLASS_NAME   VARCHAR(250) NOT NULL,
-    IS_DURABLE VARCHAR(1) NOT NULL,
-    IS_NONCONCURRENT VARCHAR(1) NOT NULL,
-    IS_UPDATE_DATA VARCHAR(1) NOT NULL,
-    REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
-    JOB_DATA BLOB NULL,
-    PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_TRIGGERS
-  (
-    SCHED_NAME VARCHAR(120) NOT NULL,
-    TRIGGER_NAME VARCHAR(200) NOT NULL,
-    TRIGGER_GROUP VARCHAR(200) NOT NULL,
-    JOB_NAME  VARCHAR(200) NOT NULL,
-    JOB_GROUP VARCHAR(200) NOT NULL,
-    DESCRIPTION VARCHAR(250) NULL,
-    NEXT_FIRE_TIME BIGINT(13) NULL,
-    PREV_FIRE_TIME BIGINT(13) NULL,
-    PRIORITY INTEGER NULL,
-    TRIGGER_STATE VARCHAR(16) NOT NULL,
-    TRIGGER_TYPE VARCHAR(8) NOT NULL,
-    START_TIME BIGINT(13) NOT NULL,
-    END_TIME BIGINT(13) NULL,
-    CALENDAR_NAME VARCHAR(200) NULL,
-    MISFIRE_INSTR SMALLINT(2) NULL,
-    JOB_DATA BLOB NULL,
-    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
-    FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-        REFERENCES XXL_JOB_QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPLE_TRIGGERS
-  (
-    SCHED_NAME VARCHAR(120) NOT NULL,
-    TRIGGER_NAME VARCHAR(200) NOT NULL,
-    TRIGGER_GROUP VARCHAR(200) NOT NULL,
-    REPEAT_COUNT BIGINT(7) NOT NULL,
-    REPEAT_INTERVAL BIGINT(12) NOT NULL,
-    TIMES_TRIGGERED BIGINT(10) NOT NULL,
-    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
-    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-        REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CRON_TRIGGERS
-  (
-    SCHED_NAME VARCHAR(120) NOT NULL,
-    TRIGGER_NAME VARCHAR(200) NOT NULL,
-    TRIGGER_GROUP VARCHAR(200) NOT NULL,
-    CRON_EXPRESSION VARCHAR(200) NOT NULL,
-    TIME_ZONE_ID VARCHAR(80),
-    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
-    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-        REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPROP_TRIGGERS
-  (          
-    SCHED_NAME VARCHAR(120) NOT NULL,
-    TRIGGER_NAME VARCHAR(200) NOT NULL,
-    TRIGGER_GROUP VARCHAR(200) NOT NULL,
-    STR_PROP_1 VARCHAR(512) NULL,
-    STR_PROP_2 VARCHAR(512) NULL,
-    STR_PROP_3 VARCHAR(512) NULL,
-    INT_PROP_1 INT NULL,
-    INT_PROP_2 INT NULL,
-    LONG_PROP_1 BIGINT NULL,
-    LONG_PROP_2 BIGINT NULL,
-    DEC_PROP_1 NUMERIC(13,4) NULL,
-    DEC_PROP_2 NUMERIC(13,4) NULL,
-    BOOL_PROP_1 VARCHAR(1) NULL,
-    BOOL_PROP_2 VARCHAR(1) NULL,
-    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
-    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) 
-    REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_BLOB_TRIGGERS
-  (
-    SCHED_NAME VARCHAR(120) NOT NULL,
-    TRIGGER_NAME VARCHAR(200) NOT NULL,
-    TRIGGER_GROUP VARCHAR(200) NOT NULL,
-    BLOB_DATA BLOB NULL,
-    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
-    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-        REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CALENDARS
-  (
-    SCHED_NAME VARCHAR(120) NOT NULL,
-    CALENDAR_NAME  VARCHAR(200) NOT NULL,
-    CALENDAR BLOB NOT NULL,
-    PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_PAUSED_TRIGGER_GRPS
-  (
-    SCHED_NAME VARCHAR(120) NOT NULL,
-    TRIGGER_GROUP  VARCHAR(200) NOT NULL, 
-    PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_FIRED_TRIGGERS
-  (
-    SCHED_NAME VARCHAR(120) NOT NULL,
-    ENTRY_ID VARCHAR(95) NOT NULL,
-    TRIGGER_NAME VARCHAR(200) NOT NULL,
-    TRIGGER_GROUP VARCHAR(200) NOT NULL,
-    INSTANCE_NAME VARCHAR(200) NOT NULL,
-    FIRED_TIME BIGINT(13) NOT NULL,
-    SCHED_TIME BIGINT(13) NOT NULL,
-    PRIORITY INTEGER NOT NULL,
-    STATE VARCHAR(16) NOT NULL,
-    JOB_NAME VARCHAR(200) NULL,
-    JOB_GROUP VARCHAR(200) NULL,
-    IS_NONCONCURRENT VARCHAR(1) NULL,
-    REQUESTS_RECOVERY VARCHAR(1) NULL,
-    PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SCHEDULER_STATE
-  (
-    SCHED_NAME VARCHAR(120) NOT NULL,
-    INSTANCE_NAME VARCHAR(200) NOT NULL,
-    LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
-    CHECKIN_INTERVAL BIGINT(13) NOT NULL,
-    PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_LOCKS
-  (
-    SCHED_NAME VARCHAR(120) NOT NULL,
-    LOCK_NAME  VARCHAR(40) NOT NULL, 
-    PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_INFO` (
+CREATE TABLE `XXL_JOB_INFO` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `job_group` int(11) NOT NULL COMMENT '执行器主键ID',
   `job_cron` varchar(128) NOT NULL COMMENT '任务执行CRON',
@@ -169,10 +23,13 @@ CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_INFO` (
   `glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注',
   `glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新时间',
   `child_jobid` varchar(255) DEFAULT NULL COMMENT '子任务ID,多个逗号分隔',
+  `trigger_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '调度状态:0-停止,1-运行',
+  `trigger_last_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '上次调度时间',
+  `trigger_next_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '下次调度时间',
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOG` (
+CREATE TABLE `XXL_JOB_LOG` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `job_group` int(11) NOT NULL COMMENT '执行器主键ID',
   `job_id` int(11) NOT NULL COMMENT '任务,主键ID',
@@ -193,7 +50,7 @@ CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOG` (
   KEY `I_handle_code` (`handle_code`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOGGLUE` (
+CREATE TABLE `XXL_JOB_LOGGLUE` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `job_id` int(11) NOT NULL COMMENT '任务,主键ID',
   `glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
@@ -204,7 +61,7 @@ CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOGGLUE` (
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_REGISTRY` (
+CREATE TABLE `XXL_JOB_REGISTRY` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `registry_group` varchar(255) NOT NULL,
   `registry_key` varchar(255) NOT NULL,
@@ -214,7 +71,7 @@ CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_REGISTRY` (
   KEY `i_g_k_v` (`registry_group`,`registry_key`,`registry_value`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_GROUP` (
+CREATE TABLE `XXL_JOB_GROUP` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
   `title` varchar(12) NOT NULL COMMENT '执行器名称',
@@ -224,7 +81,7 @@ CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_GROUP` (
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
-CREATE TABLE `XXL_JOB_QRTZ_USER` (
+CREATE TABLE `XXL_JOB_USER` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `username` varchar(50) NOT NULL COMMENT '账号',
   `password` varchar(50) NOT NULL COMMENT '密码',
@@ -234,10 +91,16 @@ CREATE TABLE `XXL_JOB_QRTZ_USER` (
   UNIQUE KEY `i_username` (`username`) USING BTREE
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
+CREATE TABLE `XXL_JOB_LOCK` (
+  `lock_name` varchar(50) NOT NULL COMMENT '锁名称',
+  PRIMARY KEY (`lock_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
 
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
-INSERT INTO `XXL_JOB_QRTZ_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+INSERT INTO `XXL_JOB_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
+INSERT INTO `XXL_JOB_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
+INSERT INTO `XXL_JOB_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+INSERT INTO `XXL_JOB_LOCK` ( `lock_name`) VALUES ( 'schedule_lock');
 
 commit;
 

+ 0 - 1
pom.xml

@@ -37,7 +37,6 @@
 		<commons-exec.version>1.3</commons-exec.version>
 
 		<groovy.version>2.5.6</groovy.version>
-		<quartz.version>2.3.1</quartz.version>
 
 		<maven-source-plugin.version>3.0.1</maven-source-plugin.version>
 		<maven-javadoc-plugin.version>3.1.0</maven-javadoc-plugin.version>

+ 0 - 7
xxl-job-admin/pom.xml

@@ -60,13 +60,6 @@
 			<version>${mysql-connector-java.version}</version>
 		</dependency>
 
-		<!-- quartz :quartz-2.2.3/c3p0-0.9.1.1/slf4j-api-1.6.6 -->
-		<dependency>
-			<groupId>org.quartz-scheduler</groupId>
-			<artifactId>quartz</artifactId>
-			<version>${quartz.version}</version>
-		</dependency>
-
 		<!-- xxl-job-core -->
 		<dependency>
 			<groupId>com.xuxueli</groupId>

+ 2 - 2
xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java

@@ -1,7 +1,7 @@
 package com.xxl.job.admin.controller;
 
 import com.xxl.job.admin.controller.annotation.PermissionLimit;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
 import com.xxl.job.core.biz.AdminBiz;
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.stereotype.Controller;
@@ -27,7 +27,7 @@ public class JobApiController implements InitializingBean {
     @RequestMapping(AdminBiz.MAPPING)
     @PermissionLimit(limit=false)
     public void api(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
-        XxlJobDynamicScheduler.invokeAdminService(request, response);
+        XxlJobScheduler.invokeAdminService(request, response);
     }
 
 

+ 3 - 3
xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java

@@ -1,10 +1,10 @@
 package com.xxl.job.admin.controller;
 
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
 import com.xxl.job.admin.core.exception.XxlJobException;
 import com.xxl.job.admin.core.model.XxlJobGroup;
 import com.xxl.job.admin.core.model.XxlJobInfo;
 import com.xxl.job.admin.core.model.XxlJobLog;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
 import com.xxl.job.admin.core.util.I18nUtil;
 import com.xxl.job.admin.dao.XxlJobGroupDao;
 import com.xxl.job.admin.dao.XxlJobInfoDao;
@@ -136,7 +136,7 @@ public class JobLogController {
 	@ResponseBody
 	public ReturnT<LogResult> logDetailCat(String executorAddress, long triggerTime, int logId, int fromLineNum){
 		try {
-			ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(executorAddress);
+			ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(executorAddress);
 			ReturnT<LogResult> logResult = executorBiz.log(triggerTime, logId, fromLineNum);
 
 			// is end
@@ -170,7 +170,7 @@ public class JobLogController {
 		// request of kill
 		ReturnT<String> runResult = null;
 		try {
-			ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(log.getExecutorAddress());
+			ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(log.getExecutorAddress());
 			runResult = executorBiz.kill(jobInfo.getId());
 		} catch (Exception e) {
 			logger.error(e.getMessage(), e);

+ 7 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java

@@ -11,6 +11,7 @@ import org.springframework.context.annotation.Configuration;
 import org.springframework.mail.javamail.JavaMailSender;
 
 import javax.annotation.Resource;
+import javax.sql.DataSource;
 
 /**
  * xxl-job config
@@ -53,6 +54,8 @@ public class XxlJobAdminConfig implements InitializingBean{
     private AdminBiz adminBiz;
     @Resource
     private JavaMailSender mailSender;
+    @Resource
+    private DataSource dataSource;
 
 
     public String getI18n() {
@@ -91,4 +94,8 @@ public class XxlJobAdminConfig implements InitializingBean{
         return mailSender;
     }
 
+    public DataSource getDataSource() {
+        return dataSource;
+    }
+
 }

+ 0 - 43
xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java

@@ -1,43 +0,0 @@
-package com.xxl.job.admin.core.conf;
-
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
-import org.quartz.Scheduler;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.scheduling.quartz.SchedulerFactoryBean;
-
-import javax.sql.DataSource;
-
-/**
- * @author xuxueli 2018-10-28 00:18:17
- */
-@Configuration
-public class XxlJobDynamicSchedulerConfig {
-
-    @Bean
-    public SchedulerFactoryBean getSchedulerFactoryBean(DataSource dataSource){
-
-        SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
-        schedulerFactory.setDataSource(dataSource);
-        schedulerFactory.setAutoStartup(true);                  // 自动启动
-        schedulerFactory.setStartupDelay(20);                   // 延时启动,应用启动成功后在启动
-        schedulerFactory.setOverwriteExistingJobs(true);        // 覆盖DB中JOB:true、以数据库中已经存在的为准:false
-        schedulerFactory.setApplicationContextSchedulerContextKey("applicationContext");
-        schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));
-
-        return schedulerFactory;
-    }
-
-    @Bean(initMethod = "start", destroyMethod = "destroy")
-    public XxlJobDynamicScheduler getXxlJobDynamicScheduler(SchedulerFactoryBean schedulerFactory){
-
-        Scheduler scheduler = schedulerFactory.getScheduler();
-
-        XxlJobDynamicScheduler xxlJobDynamicScheduler = new XxlJobDynamicScheduler();
-        xxlJobDynamicScheduler.setScheduler(scheduler);
-
-        return xxlJobDynamicScheduler;
-    }
-
-}

+ 147 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java

@@ -0,0 +1,147 @@
+package com.xxl.job.admin.core.conf;
+
+import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+import com.xxl.job.admin.core.thread.JobScheduleHelper;
+import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.core.biz.AdminBiz;
+import com.xxl.job.core.biz.ExecutorBiz;
+import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+import com.xxl.rpc.remoting.invoker.call.CallType;
+import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+import com.xxl.rpc.remoting.net.NetEnum;
+import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+import com.xxl.rpc.serialize.Serializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author xuxueli 2018-10-28 00:18:17
+ */
+@Configuration
+public class XxlJobScheduler implements InitializingBean, DisposableBean {
+    private static final Logger logger = LoggerFactory.getLogger(XxlJobScheduler.class);
+
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        // init i18n
+        initI18n();
+
+        // admin registry monitor run
+        JobRegistryMonitorHelper.getInstance().start();
+
+        // admin monitor run
+        JobFailMonitorHelper.getInstance().start();
+
+        // admin-server
+        initRpcProvider();
+
+        // start-schedule
+        JobScheduleHelper.getInstance().start();
+
+        logger.info(">>>>>>>>> init xxl-job admin success.");
+    }
+
+    @Override
+    public void destroy() throws Exception {
+
+        // stop-schedule
+        JobScheduleHelper.getInstance().toStop();
+
+        // admin trigger pool stop
+        JobTriggerPoolHelper.toStop();
+
+        // admin registry stop
+        JobRegistryMonitorHelper.getInstance().toStop();
+
+        // admin monitor stop
+        JobFailMonitorHelper.getInstance().toStop();
+
+        // admin-server
+        stopRpcProvider();
+    }
+
+    // ---------------------- I18n ----------------------
+
+    private void initI18n(){
+        for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+            item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+        }
+    }
+
+    // ---------------------- admin rpc provider (no server version) ----------------------
+    private static ServletServerHandler servletServerHandler;
+    private void initRpcProvider(){
+        // init
+        XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+        xxlRpcProviderFactory.initConfig(
+                NetEnum.NETTY_HTTP,
+                Serializer.SerializeEnum.HESSIAN.getSerializer(),
+                null,
+                0,
+                XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+                null,
+                null);
+
+        // add services
+        xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+
+        // servlet handler
+        servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+    }
+    private void stopRpcProvider() throws Exception {
+        XxlRpcInvokerFactory.getInstance().stop();
+    }
+    public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+        servletServerHandler.handle(null, request, response);
+    }
+
+
+    // ---------------------- executor-client ----------------------
+    private static ConcurrentHashMap<String, ExecutorBiz> executorBizRepository = new ConcurrentHashMap<String, ExecutorBiz>();
+    public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+        // valid
+        if (address==null || address.trim().length()==0) {
+            return null;
+        }
+
+        // load-cache
+        address = address.trim();
+        ExecutorBiz executorBiz = executorBizRepository.get(address);
+        if (executorBiz != null) {
+            return executorBiz;
+        }
+
+        // set-cache
+        executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+                NetEnum.NETTY_HTTP,
+                Serializer.SerializeEnum.HESSIAN.getSerializer(),
+                CallType.SYNC,
+                LoadBalance.ROUND,
+                ExecutorBiz.class,
+                null,
+                5000,
+                address,
+                XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+                null,
+                null).getObject();
+
+        executorBizRepository.put(address, executorBiz);
+        return executorBiz;
+    }
+
+}

+ 1668 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java

@@ -0,0 +1,1668 @@
+/*
+ * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
+ * 
+ * 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 com.xxl.job.admin.core.cron;
+
+import java.io.Serializable;
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+/**
+ * Provides a parser and evaluator for unix-like cron expressions. Cron 
+ * expressions provide the ability to specify complex time combinations such as
+ * &quot;At 8:00am every Monday through Friday&quot; or &quot;At 1:30am every 
+ * last Friday of the month&quot;. 
+ * <P>
+ * Cron expressions are comprised of 6 required fields and one optional field
+ * separated by white space. The fields respectively are described as follows:
+ * 
+ * <table cellspacing="8">
+ * <tr>
+ * <th align="left">Field Name</th>
+ * <th align="left">&nbsp;</th>
+ * <th align="left">Allowed Values</th>
+ * <th align="left">&nbsp;</th>
+ * <th align="left">Allowed Special Characters</th>
+ * </tr>
+ * <tr>
+ * <td align="left"><code>Seconds</code></td>
+ * <td align="left">&nbsp;</th>
+ * <td align="left"><code>0-59</code></td>
+ * <td align="left">&nbsp;</th>
+ * <td align="left"><code>, - * /</code></td>
+ * </tr>
+ * <tr>
+ * <td align="left"><code>Minutes</code></td>
+ * <td align="left">&nbsp;</th>
+ * <td align="left"><code>0-59</code></td>
+ * <td align="left">&nbsp;</th>
+ * <td align="left"><code>, - * /</code></td>
+ * </tr>
+ * <tr>
+ * <td align="left"><code>Hours</code></td>
+ * <td align="left">&nbsp;</th>
+ * <td align="left"><code>0-23</code></td>
+ * <td align="left">&nbsp;</th>
+ * <td align="left"><code>, - * /</code></td>
+ * </tr>
+ * <tr>
+ * <td align="left"><code>Day-of-month</code></td>
+ * <td align="left">&nbsp;</th>
+ * <td align="left"><code>1-31</code></td>
+ * <td align="left">&nbsp;</th>
+ * <td align="left"><code>, - * ? / L W</code></td>
+ * </tr>
+ * <tr>
+ * <td align="left"><code>Month</code></td>
+ * <td align="left">&nbsp;</th>
+ * <td align="left"><code>0-11 or JAN-DEC</code></td>
+ * <td align="left">&nbsp;</th>
+ * <td align="left"><code>, - * /</code></td>
+ * </tr>
+ * <tr>
+ * <td align="left"><code>Day-of-Week</code></td>
+ * <td align="left">&nbsp;</th>
+ * <td align="left"><code>1-7 or SUN-SAT</code></td>
+ * <td align="left">&nbsp;</th>
+ * <td align="left"><code>, - * ? / L #</code></td>
+ * </tr>
+ * <tr>
+ * <td align="left"><code>Year (Optional)</code></td>
+ * <td align="left">&nbsp;</th>
+ * <td align="left"><code>empty, 1970-2199</code></td>
+ * <td align="left">&nbsp;</th>
+ * <td align="left"><code>, - * /</code></td>
+ * </tr>
+ * </table>
+ * <P>
+ * The '*' character is used to specify all values. For example, &quot;*&quot; 
+ * in the minute field means &quot;every minute&quot;.
+ * <P>
+ * The '?' character is allowed for the day-of-month and day-of-week fields. It
+ * is used to specify 'no specific value'. This is useful when you need to
+ * specify something in one of the two fields, but not the other.
+ * <P>
+ * The '-' character is used to specify ranges For example &quot;10-12&quot; in
+ * the hour field means &quot;the hours 10, 11 and 12&quot;.
+ * <P>
+ * The ',' character is used to specify additional values. For example
+ * &quot;MON,WED,FRI&quot; in the day-of-week field means &quot;the days Monday,
+ * Wednesday, and Friday&quot;.
+ * <P>
+ * The '/' character is used to specify increments. For example &quot;0/15&quot;
+ * in the seconds field means &quot;the seconds 0, 15, 30, and 45&quot;. And 
+ * &quot;5/15&quot; in the seconds field means &quot;the seconds 5, 20, 35, and
+ * 50&quot;.  Specifying '*' before the  '/' is equivalent to specifying 0 is
+ * the value to start with. Essentially, for each field in the expression, there
+ * is a set of numbers that can be turned on or off. For seconds and minutes, 
+ * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
+ * 31, and for months 0 to 11 (JAN to DEC). The &quot;/&quot; character simply helps you turn
+ * on every &quot;nth&quot; value in the given set. Thus &quot;7/6&quot; in the
+ * month field only turns on month &quot;7&quot;, it does NOT mean every 6th 
+ * month, please note that subtlety.  
+ * <P>
+ * The 'L' character is allowed for the day-of-month and day-of-week fields.
+ * This character is short-hand for &quot;last&quot;, but it has different 
+ * meaning in each of the two fields. For example, the value &quot;L&quot; in 
+ * the day-of-month field means &quot;the last day of the month&quot; - day 31 
+ * for January, day 28 for February on non-leap years. If used in the 
+ * day-of-week field by itself, it simply means &quot;7&quot; or 
+ * &quot;SAT&quot;. But if used in the day-of-week field after another value, it
+ * means &quot;the last xxx day of the month&quot; - for example &quot;6L&quot;
+ * means &quot;the last friday of the month&quot;. You can also specify an offset 
+ * from the last day of the month, such as "L-3" which would mean the third-to-last 
+ * day of the calendar month. <i>When using the 'L' option, it is important not to 
+ * specify lists, or ranges of values, as you'll get confusing/unexpected results.</i>
+ * <P>
+ * The 'W' character is allowed for the day-of-month field.  This character 
+ * is used to specify the weekday (Monday-Friday) nearest the given day.  As an 
+ * example, if you were to specify &quot;15W&quot; as the value for the 
+ * day-of-month field, the meaning is: &quot;the nearest weekday to the 15th of
+ * the month&quot;. So if the 15th is a Saturday, the trigger will fire on 
+ * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
+ * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th. 
+ * However if you specify &quot;1W&quot; as the value for day-of-month, and the
+ * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not 
+ * 'jump' over the boundary of a month's days.  The 'W' character can only be 
+ * specified when the day-of-month is a single day, not a range or list of days.
+ * <P>
+ * The 'L' and 'W' characters can also be combined for the day-of-month 
+ * expression to yield 'LW', which translates to &quot;last weekday of the 
+ * month&quot;.
+ * <P>
+ * The '#' character is allowed for the day-of-week field. This character is
+ * used to specify &quot;the nth&quot; XXX day of the month. For example, the 
+ * value of &quot;6#3&quot; in the day-of-week field means the third Friday of 
+ * the month (day 6 = Friday and &quot;#3&quot; = the 3rd one in the month). 
+ * Other examples: &quot;2#1&quot; = the first Monday of the month and 
+ * &quot;4#5&quot; = the fifth Wednesday of the month. Note that if you specify
+ * &quot;#5&quot; and there is not 5 of the given day-of-week in the month, then
+ * no firing will occur that month.  If the '#' character is used, there can
+ * only be one expression in the day-of-week field (&quot;3#1,6#3&quot; is 
+ * not valid, since there are two expressions).
+ * <P>
+ * <!--The 'C' character is allowed for the day-of-month and day-of-week fields.
+ * This character is short-hand for "calendar". This means values are
+ * calculated against the associated calendar, if any. If no calendar is
+ * associated, then it is equivalent to having an all-inclusive calendar. A
+ * value of "5C" in the day-of-month field means "the first day included by the
+ * calendar on or after the 5th". A value of "1C" in the day-of-week field
+ * means "the first day included by the calendar on or after Sunday".-->
+ * <P>
+ * The legal characters and the names of months and days of the week are not
+ * case sensitive.
+ * 
+ * <p>
+ * <b>NOTES:</b>
+ * <ul>
+ * <li>Support for specifying both a day-of-week and a day-of-month value is
+ * not complete (you'll need to use the '?' character in one of these fields).
+ * </li>
+ * <li>Overflowing ranges is supported - that is, having a larger number on 
+ * the left hand side than the right. You might do 22-2 to catch 10 o'clock 
+ * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is 
+ * very important to note that overuse of overflowing ranges creates ranges 
+ * that don't make sense and no effort has been made to determine which 
+ * interpretation CronExpression chooses. An example would be 
+ * "0 0 14-6 ? * FRI-MON". </li>
+ * </ul>
+ * </p>
+ * 
+ * 
+ * @author Sharada Jambula, James House
+ * @author Contributions from Mads Henderson
+ * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
+ *
+ * Borrowed from quartz v2.3.1
+ *
+ */
+public final class CronExpression implements Serializable, Cloneable {
+
+    private static final long serialVersionUID = 12423409423L;
+    
+    protected static final int SECOND = 0;
+    protected static final int MINUTE = 1;
+    protected static final int HOUR = 2;
+    protected static final int DAY_OF_MONTH = 3;
+    protected static final int MONTH = 4;
+    protected static final int DAY_OF_WEEK = 5;
+    protected static final int YEAR = 6;
+    protected static final int ALL_SPEC_INT = 99; // '*'
+    protected static final int NO_SPEC_INT = 98; // '?'
+    protected static final Integer ALL_SPEC = ALL_SPEC_INT;
+    protected static final Integer NO_SPEC = NO_SPEC_INT;
+    
+    protected static final Map<String, Integer> monthMap = new HashMap<String, Integer>(20);
+    protected static final Map<String, Integer> dayMap = new HashMap<String, Integer>(60);
+    static {
+        monthMap.put("JAN", 0);
+        monthMap.put("FEB", 1);
+        monthMap.put("MAR", 2);
+        monthMap.put("APR", 3);
+        monthMap.put("MAY", 4);
+        monthMap.put("JUN", 5);
+        monthMap.put("JUL", 6);
+        monthMap.put("AUG", 7);
+        monthMap.put("SEP", 8);
+        monthMap.put("OCT", 9);
+        monthMap.put("NOV", 10);
+        monthMap.put("DEC", 11);
+
+        dayMap.put("SUN", 1);
+        dayMap.put("MON", 2);
+        dayMap.put("TUE", 3);
+        dayMap.put("WED", 4);
+        dayMap.put("THU", 5);
+        dayMap.put("FRI", 6);
+        dayMap.put("SAT", 7);
+    }
+
+    private final String cronExpression;
+    private TimeZone timeZone = null;
+    protected transient TreeSet<Integer> seconds;
+    protected transient TreeSet<Integer> minutes;
+    protected transient TreeSet<Integer> hours;
+    protected transient TreeSet<Integer> daysOfMonth;
+    protected transient TreeSet<Integer> months;
+    protected transient TreeSet<Integer> daysOfWeek;
+    protected transient TreeSet<Integer> years;
+
+    protected transient boolean lastdayOfWeek = false;
+    protected transient int nthdayOfWeek = 0;
+    protected transient boolean lastdayOfMonth = false;
+    protected transient boolean nearestWeekday = false;
+    protected transient int lastdayOffset = 0;
+    protected transient boolean expressionParsed = false;
+    
+    public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
+
+    /**
+     * Constructs a new <CODE>CronExpression</CODE> based on the specified 
+     * parameter.
+     * 
+     * @param cronExpression String representation of the cron expression the
+     *                       new object should represent
+     * @throws java.text.ParseException
+     *         if the string expression cannot be parsed into a valid 
+     *         <CODE>CronExpression</CODE>
+     */
+    public CronExpression(String cronExpression) throws ParseException {
+        if (cronExpression == null) {
+            throw new IllegalArgumentException("cronExpression cannot be null");
+        }
+        
+        this.cronExpression = cronExpression.toUpperCase(Locale.US);
+        
+        buildExpression(this.cronExpression);
+    }
+    
+    /**
+     * Constructs a new {@code CronExpression} as a copy of an existing
+     * instance.
+     * 
+     * @param expression
+     *            The existing cron expression to be copied
+     */
+    public CronExpression(CronExpression expression) {
+        /*
+         * We don't call the other constructor here since we need to swallow the
+         * ParseException. We also elide some of the sanity checking as it is
+         * not logically trippable.
+         */
+        this.cronExpression = expression.getCronExpression();
+        try {
+            buildExpression(cronExpression);
+        } catch (ParseException ex) {
+            throw new AssertionError();
+        }
+        if (expression.getTimeZone() != null) {
+            setTimeZone((TimeZone) expression.getTimeZone().clone());
+        }
+    }
+
+    /**
+     * Indicates whether the given date satisfies the cron expression. Note that
+     * milliseconds are ignored, so two Dates falling on different milliseconds
+     * of the same second will always have the same result here.
+     * 
+     * @param date the date to evaluate
+     * @return a boolean indicating whether the given date satisfies the cron
+     *         expression
+     */
+    public boolean isSatisfiedBy(Date date) {
+        Calendar testDateCal = Calendar.getInstance(getTimeZone());
+        testDateCal.setTime(date);
+        testDateCal.set(Calendar.MILLISECOND, 0);
+        Date originalDate = testDateCal.getTime();
+        
+        testDateCal.add(Calendar.SECOND, -1);
+        
+        Date timeAfter = getTimeAfter(testDateCal.getTime());
+
+        return ((timeAfter != null) && (timeAfter.equals(originalDate)));
+    }
+    
+    /**
+     * Returns the next date/time <I>after</I> the given date/time which
+     * satisfies the cron expression.
+     * 
+     * @param date the date/time at which to begin the search for the next valid
+     *             date/time
+     * @return the next valid date/time
+     */
+    public Date getNextValidTimeAfter(Date date) {
+        return getTimeAfter(date);
+    }
+    
+    /**
+     * Returns the next date/time <I>after</I> the given date/time which does
+     * <I>not</I> satisfy the expression
+     * 
+     * @param date the date/time at which to begin the search for the next 
+     *             invalid date/time
+     * @return the next valid date/time
+     */
+    public Date getNextInvalidTimeAfter(Date date) {
+        long difference = 1000;
+        
+        //move back to the nearest second so differences will be accurate
+        Calendar adjustCal = Calendar.getInstance(getTimeZone());
+        adjustCal.setTime(date);
+        adjustCal.set(Calendar.MILLISECOND, 0);
+        Date lastDate = adjustCal.getTime();
+        
+        Date newDate;
+        
+        //FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution.
+        
+        //keep getting the next included time until it's farther than one second
+        // apart. At that point, lastDate is the last valid fire time. We return
+        // the second immediately following it.
+        while (difference == 1000) {
+            newDate = getTimeAfter(lastDate);
+            if(newDate == null)
+                break;
+            
+            difference = newDate.getTime() - lastDate.getTime();
+            
+            if (difference == 1000) {
+                lastDate = newDate;
+            }
+        }
+        
+        return new Date(lastDate.getTime() + 1000);
+    }
+    
+    /**
+     * Returns the time zone for which this <code>CronExpression</code> 
+     * will be resolved.
+     */
+    public TimeZone getTimeZone() {
+        if (timeZone == null) {
+            timeZone = TimeZone.getDefault();
+        }
+
+        return timeZone;
+    }
+
+    /**
+     * Sets the time zone for which  this <code>CronExpression</code> 
+     * will be resolved.
+     */
+    public void setTimeZone(TimeZone timeZone) {
+        this.timeZone = timeZone;
+    }
+    
+    /**
+     * Returns the string representation of the <CODE>CronExpression</CODE>
+     * 
+     * @return a string representation of the <CODE>CronExpression</CODE>
+     */
+    @Override
+    public String toString() {
+        return cronExpression;
+    }
+
+    /**
+     * Indicates whether the specified cron expression can be parsed into a 
+     * valid cron expression
+     * 
+     * @param cronExpression the expression to evaluate
+     * @return a boolean indicating whether the given expression is a valid cron
+     *         expression
+     */
+    public static boolean isValidExpression(String cronExpression) {
+        
+        try {
+            new CronExpression(cronExpression);
+        } catch (ParseException pe) {
+            return false;
+        }
+        
+        return true;
+    }
+
+    public static void validateExpression(String cronExpression) throws ParseException {
+        
+        new CronExpression(cronExpression);
+    }
+    
+    
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    // Expression Parsing Functions
+    //
+    ////////////////////////////////////////////////////////////////////////////
+
+    protected void buildExpression(String expression) throws ParseException {
+        expressionParsed = true;
+
+        try {
+
+            if (seconds == null) {
+                seconds = new TreeSet<Integer>();
+            }
+            if (minutes == null) {
+                minutes = new TreeSet<Integer>();
+            }
+            if (hours == null) {
+                hours = new TreeSet<Integer>();
+            }
+            if (daysOfMonth == null) {
+                daysOfMonth = new TreeSet<Integer>();
+            }
+            if (months == null) {
+                months = new TreeSet<Integer>();
+            }
+            if (daysOfWeek == null) {
+                daysOfWeek = new TreeSet<Integer>();
+            }
+            if (years == null) {
+                years = new TreeSet<Integer>();
+            }
+
+            int exprOn = SECOND;
+
+            StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
+                    false);
+
+            while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
+                String expr = exprsTok.nextToken().trim();
+
+                // throw an exception if L is used with other days of the month
+                if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+                    throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
+                }
+                // throw an exception if L is used with other days of the week
+                if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1  && expr.contains(",")) {
+                    throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
+                }
+                if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) {
+                    throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1);
+                }
+                
+                StringTokenizer vTok = new StringTokenizer(expr, ",");
+                while (vTok.hasMoreTokens()) {
+                    String v = vTok.nextToken();
+                    storeExpressionVals(0, v, exprOn);
+                }
+
+                exprOn++;
+            }
+
+            if (exprOn <= DAY_OF_WEEK) {
+                throw new ParseException("Unexpected end of expression.",
+                            expression.length());
+            }
+
+            if (exprOn <= YEAR) {
+                storeExpressionVals(0, "*", YEAR);
+            }
+
+            TreeSet<Integer> dow = getSet(DAY_OF_WEEK);
+            TreeSet<Integer> dom = getSet(DAY_OF_MONTH);
+
+            // Copying the logic from the UnsupportedOperationException below
+            boolean dayOfMSpec = !dom.contains(NO_SPEC);
+            boolean dayOfWSpec = !dow.contains(NO_SPEC);
+
+            if (!dayOfMSpec || dayOfWSpec) {
+                if (!dayOfWSpec || dayOfMSpec) {
+                    throw new ParseException(
+                            "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
+                }
+            }
+        } catch (ParseException pe) {
+            throw pe;
+        } catch (Exception e) {
+            throw new ParseException("Illegal cron expression format ("
+                    + e.toString() + ")", 0);
+        }
+    }
+
+    protected int storeExpressionVals(int pos, String s, int type)
+        throws ParseException {
+
+        int incr = 0;
+        int i = skipWhiteSpace(pos, s);
+        if (i >= s.length()) {
+            return i;
+        }
+        char c = s.charAt(i);
+        if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
+            String sub = s.substring(i, i + 3);
+            int sval = -1;
+            int eval = -1;
+            if (type == MONTH) {
+                sval = getMonthNumber(sub) + 1;
+                if (sval <= 0) {
+                    throw new ParseException("Invalid Month value: '" + sub + "'", i);
+                }
+                if (s.length() > i + 3) {
+                    c = s.charAt(i + 3);
+                    if (c == '-') {
+                        i += 4;
+                        sub = s.substring(i, i + 3);
+                        eval = getMonthNumber(sub) + 1;
+                        if (eval <= 0) {
+                            throw new ParseException("Invalid Month value: '" + sub + "'", i);
+                        }
+                    }
+                }
+            } else if (type == DAY_OF_WEEK) {
+                sval = getDayOfWeekNumber(sub);
+                if (sval < 0) {
+                    throw new ParseException("Invalid Day-of-Week value: '"
+                                + sub + "'", i);
+                }
+                if (s.length() > i + 3) {
+                    c = s.charAt(i + 3);
+                    if (c == '-') {
+                        i += 4;
+                        sub = s.substring(i, i + 3);
+                        eval = getDayOfWeekNumber(sub);
+                        if (eval < 0) {
+                            throw new ParseException(
+                                    "Invalid Day-of-Week value: '" + sub
+                                        + "'", i);
+                        }
+                    } else if (c == '#') {
+                        try {
+                            i += 4;
+                            nthdayOfWeek = Integer.parseInt(s.substring(i));
+                            if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+                                throw new Exception();
+                            }
+                        } catch (Exception e) {
+                            throw new ParseException(
+                                    "A numeric value between 1 and 5 must follow the '#' option",
+                                    i);
+                        }
+                    } else if (c == 'L') {
+                        lastdayOfWeek = true;
+                        i++;
+                    }
+                }
+
+            } else {
+                throw new ParseException(
+                        "Illegal characters for this position: '" + sub + "'",
+                        i);
+            }
+            if (eval != -1) {
+                incr = 1;
+            }
+            addToSet(sval, eval, incr, type);
+            return (i + 3);
+        }
+
+        if (c == '?') {
+            i++;
+            if ((i + 1) < s.length() 
+                    && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
+                throw new ParseException("Illegal character after '?': "
+                            + s.charAt(i), i);
+            }
+            if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
+                throw new ParseException(
+                            "'?' can only be specified for Day-of-Month or Day-of-Week.",
+                            i);
+            }
+            if (type == DAY_OF_WEEK && !lastdayOfMonth) {
+                int val = daysOfMonth.last();
+                if (val == NO_SPEC_INT) {
+                    throw new ParseException(
+                                "'?' can only be specified for Day-of-Month -OR- Day-of-Week.",
+                                i);
+                }
+            }
+
+            addToSet(NO_SPEC_INT, -1, 0, type);
+            return i;
+        }
+
+        if (c == '*' || c == '/') {
+            if (c == '*' && (i + 1) >= s.length()) {
+                addToSet(ALL_SPEC_INT, -1, incr, type);
+                return i + 1;
+            } else if (c == '/'
+                    && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
+                            .charAt(i + 1) == '\t')) { 
+                throw new ParseException("'/' must be followed by an integer.", i);
+            } else if (c == '*') {
+                i++;
+            }
+            c = s.charAt(i);
+            if (c == '/') { // is an increment specified?
+                i++;
+                if (i >= s.length()) {
+                    throw new ParseException("Unexpected end of string.", i);
+                }
+
+                incr = getNumericValue(s, i);
+
+                i++;
+                if (incr > 10) {
+                    i++;
+                }
+                checkIncrementRange(incr, type, i);
+            } else {
+                incr = 1;
+            }
+
+            addToSet(ALL_SPEC_INT, -1, incr, type);
+            return i;
+        } else if (c == 'L') {
+            i++;
+            if (type == DAY_OF_MONTH) {
+                lastdayOfMonth = true;
+            }
+            if (type == DAY_OF_WEEK) {
+                addToSet(7, 7, 0, type);
+            }
+            if(type == DAY_OF_MONTH && s.length() > i) {
+                c = s.charAt(i);
+                if(c == '-') {
+                    ValueSet vs = getValue(0, s, i+1);
+                    lastdayOffset = vs.value;
+                    if(lastdayOffset > 30)
+                        throw new ParseException("Offset from last day must be <= 30", i+1);
+                    i = vs.pos;
+                }                        
+                if(s.length() > i) {
+                    c = s.charAt(i);
+                    if(c == 'W') {
+                        nearestWeekday = true;
+                        i++;
+                    }
+                }
+            }
+            return i;
+        } else if (c >= '0' && c <= '9') {
+            int val = Integer.parseInt(String.valueOf(c));
+            i++;
+            if (i >= s.length()) {
+                addToSet(val, -1, -1, type);
+            } else {
+                c = s.charAt(i);
+                if (c >= '0' && c <= '9') {
+                    ValueSet vs = getValue(val, s, i);
+                    val = vs.value;
+                    i = vs.pos;
+                }
+                i = checkNext(i, s, val, type);
+                return i;
+            }
+        } else {
+            throw new ParseException("Unexpected character: " + c, i);
+        }
+
+        return i;
+    }
+
+    private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException {
+        if (incr > 59 && (type == SECOND || type == MINUTE)) {
+            throw new ParseException("Increment > 60 : " + incr, idxPos);
+        } else if (incr > 23 && (type == HOUR)) {
+            throw new ParseException("Increment > 24 : " + incr, idxPos);
+        } else if (incr > 31 && (type == DAY_OF_MONTH)) {
+            throw new ParseException("Increment > 31 : " + incr, idxPos);
+        } else if (incr > 7 && (type == DAY_OF_WEEK)) {
+            throw new ParseException("Increment > 7 : " + incr, idxPos);
+        } else if (incr > 12 && (type == MONTH)) {
+            throw new ParseException("Increment > 12 : " + incr, idxPos);
+        }
+    }
+
+    protected int checkNext(int pos, String s, int val, int type)
+        throws ParseException {
+        
+        int end = -1;
+        int i = pos;
+
+        if (i >= s.length()) {
+            addToSet(val, end, -1, type);
+            return i;
+        }
+
+        char c = s.charAt(pos);
+
+        if (c == 'L') {
+            if (type == DAY_OF_WEEK) {
+                if(val < 1 || val > 7)
+                    throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
+                lastdayOfWeek = true;
+            } else {
+                throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
+            }
+            TreeSet<Integer> set = getSet(type);
+            set.add(val);
+            i++;
+            return i;
+        }
+        
+        if (c == 'W') {
+            if (type == DAY_OF_MONTH) {
+                nearestWeekday = true;
+            } else {
+                throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
+            }
+            if(val > 31)
+                throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i); 
+            TreeSet<Integer> set = getSet(type);
+            set.add(val);
+            i++;
+            return i;
+        }
+
+        if (c == '#') {
+            if (type != DAY_OF_WEEK) {
+                throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
+            }
+            i++;
+            try {
+                nthdayOfWeek = Integer.parseInt(s.substring(i));
+                if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+                    throw new Exception();
+                }
+            } catch (Exception e) {
+                throw new ParseException(
+                        "A numeric value between 1 and 5 must follow the '#' option",
+                        i);
+            }
+
+            TreeSet<Integer> set = getSet(type);
+            set.add(val);
+            i++;
+            return i;
+        }
+
+        if (c == '-') {
+            i++;
+            c = s.charAt(i);
+            int v = Integer.parseInt(String.valueOf(c));
+            end = v;
+            i++;
+            if (i >= s.length()) {
+                addToSet(val, end, 1, type);
+                return i;
+            }
+            c = s.charAt(i);
+            if (c >= '0' && c <= '9') {
+                ValueSet vs = getValue(v, s, i);
+                end = vs.value;
+                i = vs.pos;
+            }
+            if (i < s.length() && ((c = s.charAt(i)) == '/')) {
+                i++;
+                c = s.charAt(i);
+                int v2 = Integer.parseInt(String.valueOf(c));
+                i++;
+                if (i >= s.length()) {
+                    addToSet(val, end, v2, type);
+                    return i;
+                }
+                c = s.charAt(i);
+                if (c >= '0' && c <= '9') {
+                    ValueSet vs = getValue(v2, s, i);
+                    int v3 = vs.value;
+                    addToSet(val, end, v3, type);
+                    i = vs.pos;
+                    return i;
+                } else {
+                    addToSet(val, end, v2, type);
+                    return i;
+                }
+            } else {
+                addToSet(val, end, 1, type);
+                return i;
+            }
+        }
+
+        if (c == '/') {
+            if ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t') {
+                throw new ParseException("'/' must be followed by an integer.", i);
+            }
+
+            i++;
+            c = s.charAt(i);
+            int v2 = Integer.parseInt(String.valueOf(c));
+            i++;
+            if (i >= s.length()) {
+                checkIncrementRange(v2, type, i);
+                addToSet(val, end, v2, type);
+                return i;
+            }
+            c = s.charAt(i);
+            if (c >= '0' && c <= '9') {
+                ValueSet vs = getValue(v2, s, i);
+                int v3 = vs.value;
+                checkIncrementRange(v3, type, i);
+                addToSet(val, end, v3, type);
+                i = vs.pos;
+                return i;
+            } else {
+                throw new ParseException("Unexpected character '" + c + "' after '/'", i);
+            }
+        }
+
+        addToSet(val, end, 0, type);
+        i++;
+        return i;
+    }
+
+    public String getCronExpression() {
+        return cronExpression;
+    }
+    
+    public String getExpressionSummary() {
+        StringBuilder buf = new StringBuilder();
+
+        buf.append("seconds: ");
+        buf.append(getExpressionSetSummary(seconds));
+        buf.append("\n");
+        buf.append("minutes: ");
+        buf.append(getExpressionSetSummary(minutes));
+        buf.append("\n");
+        buf.append("hours: ");
+        buf.append(getExpressionSetSummary(hours));
+        buf.append("\n");
+        buf.append("daysOfMonth: ");
+        buf.append(getExpressionSetSummary(daysOfMonth));
+        buf.append("\n");
+        buf.append("months: ");
+        buf.append(getExpressionSetSummary(months));
+        buf.append("\n");
+        buf.append("daysOfWeek: ");
+        buf.append(getExpressionSetSummary(daysOfWeek));
+        buf.append("\n");
+        buf.append("lastdayOfWeek: ");
+        buf.append(lastdayOfWeek);
+        buf.append("\n");
+        buf.append("nearestWeekday: ");
+        buf.append(nearestWeekday);
+        buf.append("\n");
+        buf.append("NthDayOfWeek: ");
+        buf.append(nthdayOfWeek);
+        buf.append("\n");
+        buf.append("lastdayOfMonth: ");
+        buf.append(lastdayOfMonth);
+        buf.append("\n");
+        buf.append("years: ");
+        buf.append(getExpressionSetSummary(years));
+        buf.append("\n");
+
+        return buf.toString();
+    }
+
+    protected String getExpressionSetSummary(java.util.Set<Integer> set) {
+
+        if (set.contains(NO_SPEC)) {
+            return "?";
+        }
+        if (set.contains(ALL_SPEC)) {
+            return "*";
+        }
+
+        StringBuilder buf = new StringBuilder();
+
+        Iterator<Integer> itr = set.iterator();
+        boolean first = true;
+        while (itr.hasNext()) {
+            Integer iVal = itr.next();
+            String val = iVal.toString();
+            if (!first) {
+                buf.append(",");
+            }
+            buf.append(val);
+            first = false;
+        }
+
+        return buf.toString();
+    }
+
+    protected String getExpressionSetSummary(java.util.ArrayList<Integer> list) {
+
+        if (list.contains(NO_SPEC)) {
+            return "?";
+        }
+        if (list.contains(ALL_SPEC)) {
+            return "*";
+        }
+
+        StringBuilder buf = new StringBuilder();
+
+        Iterator<Integer> itr = list.iterator();
+        boolean first = true;
+        while (itr.hasNext()) {
+            Integer iVal = itr.next();
+            String val = iVal.toString();
+            if (!first) {
+                buf.append(",");
+            }
+            buf.append(val);
+            first = false;
+        }
+
+        return buf.toString();
+    }
+
+    protected int skipWhiteSpace(int i, String s) {
+        for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
+            ;
+        }
+
+        return i;
+    }
+
+    protected int findNextWhiteSpace(int i, String s) {
+        for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
+            ;
+        }
+
+        return i;
+    }
+
+    protected void addToSet(int val, int end, int incr, int type)
+        throws ParseException {
+        
+        TreeSet<Integer> set = getSet(type);
+
+        if (type == SECOND || type == MINUTE) {
+            if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
+                throw new ParseException(
+                        "Minute and Second values must be between 0 and 59",
+                        -1);
+            }
+        } else if (type == HOUR) {
+            if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
+                throw new ParseException(
+                        "Hour values must be between 0 and 23", -1);
+            }
+        } else if (type == DAY_OF_MONTH) {
+            if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT) 
+                    && (val != NO_SPEC_INT)) {
+                throw new ParseException(
+                        "Day of month values must be between 1 and 31", -1);
+            }
+        } else if (type == MONTH) {
+            if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
+                throw new ParseException(
+                        "Month values must be between 1 and 12", -1);
+            }
+        } else if (type == DAY_OF_WEEK) {
+            if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
+                    && (val != NO_SPEC_INT)) {
+                throw new ParseException(
+                        "Day-of-Week values must be between 1 and 7", -1);
+            }
+        }
+
+        if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
+            if (val != -1) {
+                set.add(val);
+            } else {
+                set.add(NO_SPEC);
+            }
+            
+            return;
+        }
+
+        int startAt = val;
+        int stopAt = end;
+
+        if (val == ALL_SPEC_INT && incr <= 0) {
+            incr = 1;
+            set.add(ALL_SPEC); // put in a marker, but also fill values
+        }
+
+        if (type == SECOND || type == MINUTE) {
+            if (stopAt == -1) {
+                stopAt = 59;
+            }
+            if (startAt == -1 || startAt == ALL_SPEC_INT) {
+                startAt = 0;
+            }
+        } else if (type == HOUR) {
+            if (stopAt == -1) {
+                stopAt = 23;
+            }
+            if (startAt == -1 || startAt == ALL_SPEC_INT) {
+                startAt = 0;
+            }
+        } else if (type == DAY_OF_MONTH) {
+            if (stopAt == -1) {
+                stopAt = 31;
+            }
+            if (startAt == -1 || startAt == ALL_SPEC_INT) {
+                startAt = 1;
+            }
+        } else if (type == MONTH) {
+            if (stopAt == -1) {
+                stopAt = 12;
+            }
+            if (startAt == -1 || startAt == ALL_SPEC_INT) {
+                startAt = 1;
+            }
+        } else if (type == DAY_OF_WEEK) {
+            if (stopAt == -1) {
+                stopAt = 7;
+            }
+            if (startAt == -1 || startAt == ALL_SPEC_INT) {
+                startAt = 1;
+            }
+        } else if (type == YEAR) {
+            if (stopAt == -1) {
+                stopAt = MAX_YEAR;
+            }
+            if (startAt == -1 || startAt == ALL_SPEC_INT) {
+                startAt = 1970;
+            }
+        }
+
+        // if the end of the range is before the start, then we need to overflow into 
+        // the next day, month etc. This is done by adding the maximum amount for that 
+        // type, and using modulus max to determine the value being added.
+        int max = -1;
+        if (stopAt < startAt) {
+            switch (type) {
+              case       SECOND : max = 60; break;
+              case       MINUTE : max = 60; break;
+              case         HOUR : max = 24; break;
+              case        MONTH : max = 12; break;
+              case  DAY_OF_WEEK : max = 7;  break;
+              case DAY_OF_MONTH : max = 31; break;
+              case         YEAR : throw new IllegalArgumentException("Start year must be less than stop year");
+              default           : throw new IllegalArgumentException("Unexpected type encountered");
+            }
+            stopAt += max;
+        }
+
+        for (int i = startAt; i <= stopAt; i += incr) {
+            if (max == -1) {
+                // ie: there's no max to overflow over
+                set.add(i);
+            } else {
+                // take the modulus to get the real value
+                int i2 = i % max;
+
+                // 1-indexed ranges should not include 0, and should include their max
+                if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) {
+                    i2 = max;
+                }
+
+                set.add(i2);
+            }
+        }
+    }
+
+    TreeSet<Integer> getSet(int type) {
+        switch (type) {
+            case SECOND:
+                return seconds;
+            case MINUTE:
+                return minutes;
+            case HOUR:
+                return hours;
+            case DAY_OF_MONTH:
+                return daysOfMonth;
+            case MONTH:
+                return months;
+            case DAY_OF_WEEK:
+                return daysOfWeek;
+            case YEAR:
+                return years;
+            default:
+                return null;
+        }
+    }
+
+    protected ValueSet getValue(int v, String s, int i) {
+        char c = s.charAt(i);
+        StringBuilder s1 = new StringBuilder(String.valueOf(v));
+        while (c >= '0' && c <= '9') {
+            s1.append(c);
+            i++;
+            if (i >= s.length()) {
+                break;
+            }
+            c = s.charAt(i);
+        }
+        ValueSet val = new ValueSet();
+        
+        val.pos = (i < s.length()) ? i : i + 1;
+        val.value = Integer.parseInt(s1.toString());
+        return val;
+    }
+
+    protected int getNumericValue(String s, int i) {
+        int endOfVal = findNextWhiteSpace(i, s);
+        String val = s.substring(i, endOfVal);
+        return Integer.parseInt(val);
+    }
+
+    protected int getMonthNumber(String s) {
+        Integer integer = monthMap.get(s);
+
+        if (integer == null) {
+            return -1;
+        }
+
+        return integer;
+    }
+
+    protected int getDayOfWeekNumber(String s) {
+        Integer integer = dayMap.get(s);
+
+        if (integer == null) {
+            return -1;
+        }
+
+        return integer;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    // Computation Functions
+    //
+    ////////////////////////////////////////////////////////////////////////////
+
+    public Date getTimeAfter(Date afterTime) {
+
+        // Computation is based on Gregorian year only.
+        Calendar cl = new java.util.GregorianCalendar(getTimeZone()); 
+
+        // move ahead one second, since we're computing the time *after* the
+        // given time
+        afterTime = new Date(afterTime.getTime() + 1000);
+        // CronTrigger does not deal with milliseconds
+        cl.setTime(afterTime);
+        cl.set(Calendar.MILLISECOND, 0);
+
+        boolean gotOne = false;
+        // loop until we've computed the next time, or we've past the endTime
+        while (!gotOne) {
+
+            //if (endTime != null && cl.getTime().after(endTime)) return null;
+            if(cl.get(Calendar.YEAR) > 2999) { // prevent endless loop...
+                return null;
+            }
+
+            SortedSet<Integer> st = null;
+            int t = 0;
+
+            int sec = cl.get(Calendar.SECOND);
+            int min = cl.get(Calendar.MINUTE);
+
+            // get second.................................................
+            st = seconds.tailSet(sec);
+            if (st != null && st.size() != 0) {
+                sec = st.first();
+            } else {
+                sec = seconds.first();
+                min++;
+                cl.set(Calendar.MINUTE, min);
+            }
+            cl.set(Calendar.SECOND, sec);
+
+            min = cl.get(Calendar.MINUTE);
+            int hr = cl.get(Calendar.HOUR_OF_DAY);
+            t = -1;
+
+            // get minute.................................................
+            st = minutes.tailSet(min);
+            if (st != null && st.size() != 0) {
+                t = min;
+                min = st.first();
+            } else {
+                min = minutes.first();
+                hr++;
+            }
+            if (min != t) {
+                cl.set(Calendar.SECOND, 0);
+                cl.set(Calendar.MINUTE, min);
+                setCalendarHour(cl, hr);
+                continue;
+            }
+            cl.set(Calendar.MINUTE, min);
+
+            hr = cl.get(Calendar.HOUR_OF_DAY);
+            int day = cl.get(Calendar.DAY_OF_MONTH);
+            t = -1;
+
+            // get hour...................................................
+            st = hours.tailSet(hr);
+            if (st != null && st.size() != 0) {
+                t = hr;
+                hr = st.first();
+            } else {
+                hr = hours.first();
+                day++;
+            }
+            if (hr != t) {
+                cl.set(Calendar.SECOND, 0);
+                cl.set(Calendar.MINUTE, 0);
+                cl.set(Calendar.DAY_OF_MONTH, day);
+                setCalendarHour(cl, hr);
+                continue;
+            }
+            cl.set(Calendar.HOUR_OF_DAY, hr);
+
+            day = cl.get(Calendar.DAY_OF_MONTH);
+            int mon = cl.get(Calendar.MONTH) + 1;
+            // '+ 1' because calendar is 0-based for this field, and we are
+            // 1-based
+            t = -1;
+            int tmon = mon;
+            
+            // get day...................................................
+            boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
+            boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
+            if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
+                st = daysOfMonth.tailSet(day);
+                if (lastdayOfMonth) {
+                    if(!nearestWeekday) {
+                        t = day;
+                        day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+                        day -= lastdayOffset;
+                        if(t > day) {
+                            mon++;
+                            if(mon > 12) { 
+                                mon = 1;
+                                tmon = 3333; // ensure test of mon != tmon further below fails
+                                cl.add(Calendar.YEAR, 1);
+                            }
+                            day = 1;
+                        }
+                    } else {
+                        t = day;
+                        day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+                        day -= lastdayOffset;
+                        
+                        java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+                        tcal.set(Calendar.SECOND, 0);
+                        tcal.set(Calendar.MINUTE, 0);
+                        tcal.set(Calendar.HOUR_OF_DAY, 0);
+                        tcal.set(Calendar.DAY_OF_MONTH, day);
+                        tcal.set(Calendar.MONTH, mon - 1);
+                        tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+                        
+                        int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+                        int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+                        if(dow == Calendar.SATURDAY && day == 1) {
+                            day += 2;
+                        } else if(dow == Calendar.SATURDAY) {
+                            day -= 1;
+                        } else if(dow == Calendar.SUNDAY && day == ldom) { 
+                            day -= 2;
+                        } else if(dow == Calendar.SUNDAY) { 
+                            day += 1;
+                        }
+                    
+                        tcal.set(Calendar.SECOND, sec);
+                        tcal.set(Calendar.MINUTE, min);
+                        tcal.set(Calendar.HOUR_OF_DAY, hr);
+                        tcal.set(Calendar.DAY_OF_MONTH, day);
+                        tcal.set(Calendar.MONTH, mon - 1);
+                        Date nTime = tcal.getTime();
+                        if(nTime.before(afterTime)) {
+                            day = 1;
+                            mon++;
+                        }
+                    }
+                } else if(nearestWeekday) {
+                    t = day;
+                    day = daysOfMonth.first();
+
+                    java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+                    tcal.set(Calendar.SECOND, 0);
+                    tcal.set(Calendar.MINUTE, 0);
+                    tcal.set(Calendar.HOUR_OF_DAY, 0);
+                    tcal.set(Calendar.DAY_OF_MONTH, day);
+                    tcal.set(Calendar.MONTH, mon - 1);
+                    tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+                    
+                    int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+                    int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+                    if(dow == Calendar.SATURDAY && day == 1) {
+                        day += 2;
+                    } else if(dow == Calendar.SATURDAY) {
+                        day -= 1;
+                    } else if(dow == Calendar.SUNDAY && day == ldom) { 
+                        day -= 2;
+                    } else if(dow == Calendar.SUNDAY) { 
+                        day += 1;
+                    }
+                        
+                
+                    tcal.set(Calendar.SECOND, sec);
+                    tcal.set(Calendar.MINUTE, min);
+                    tcal.set(Calendar.HOUR_OF_DAY, hr);
+                    tcal.set(Calendar.DAY_OF_MONTH, day);
+                    tcal.set(Calendar.MONTH, mon - 1);
+                    Date nTime = tcal.getTime();
+                    if(nTime.before(afterTime)) {
+                        day = daysOfMonth.first();
+                        mon++;
+                    }
+                } else if (st != null && st.size() != 0) {
+                    t = day;
+                    day = st.first();
+                    // make sure we don't over-run a short month, such as february
+                    int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+                    if (day > lastDay) {
+                        day = daysOfMonth.first();
+                        mon++;
+                    }
+                } else {
+                    day = daysOfMonth.first();
+                    mon++;
+                }
+                
+                if (day != t || mon != tmon) {
+                    cl.set(Calendar.SECOND, 0);
+                    cl.set(Calendar.MINUTE, 0);
+                    cl.set(Calendar.HOUR_OF_DAY, 0);
+                    cl.set(Calendar.DAY_OF_MONTH, day);
+                    cl.set(Calendar.MONTH, mon - 1);
+                    // '- 1' because calendar is 0-based for this field, and we
+                    // are 1-based
+                    continue;
+                }
+            } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
+                if (lastdayOfWeek) { // are we looking for the last XXX day of
+                    // the month?
+                    int dow = daysOfWeek.first(); // desired
+                    // d-o-w
+                    int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+                    int daysToAdd = 0;
+                    if (cDow < dow) {
+                        daysToAdd = dow - cDow;
+                    }
+                    if (cDow > dow) {
+                        daysToAdd = dow + (7 - cDow);
+                    }
+
+                    int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+                    if (day + daysToAdd > lDay) { // did we already miss the
+                        // last one?
+                        cl.set(Calendar.SECOND, 0);
+                        cl.set(Calendar.MINUTE, 0);
+                        cl.set(Calendar.HOUR_OF_DAY, 0);
+                        cl.set(Calendar.DAY_OF_MONTH, 1);
+                        cl.set(Calendar.MONTH, mon);
+                        // no '- 1' here because we are promoting the month
+                        continue;
+                    }
+
+                    // find date of last occurrence of this day in this month...
+                    while ((day + daysToAdd + 7) <= lDay) {
+                        daysToAdd += 7;
+                    }
+
+                    day += daysToAdd;
+
+                    if (daysToAdd > 0) {
+                        cl.set(Calendar.SECOND, 0);
+                        cl.set(Calendar.MINUTE, 0);
+                        cl.set(Calendar.HOUR_OF_DAY, 0);
+                        cl.set(Calendar.DAY_OF_MONTH, day);
+                        cl.set(Calendar.MONTH, mon - 1);
+                        // '- 1' here because we are not promoting the month
+                        continue;
+                    }
+
+                } else if (nthdayOfWeek != 0) {
+                    // are we looking for the Nth XXX day in the month?
+                    int dow = daysOfWeek.first(); // desired
+                    // d-o-w
+                    int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+                    int daysToAdd = 0;
+                    if (cDow < dow) {
+                        daysToAdd = dow - cDow;
+                    } else if (cDow > dow) {
+                        daysToAdd = dow + (7 - cDow);
+                    }
+
+                    boolean dayShifted = false;
+                    if (daysToAdd > 0) {
+                        dayShifted = true;
+                    }
+
+                    day += daysToAdd;
+                    int weekOfMonth = day / 7;
+                    if (day % 7 > 0) {
+                        weekOfMonth++;
+                    }
+
+                    daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
+                    day += daysToAdd;
+                    if (daysToAdd < 0
+                            || day > getLastDayOfMonth(mon, cl
+                                    .get(Calendar.YEAR))) {
+                        cl.set(Calendar.SECOND, 0);
+                        cl.set(Calendar.MINUTE, 0);
+                        cl.set(Calendar.HOUR_OF_DAY, 0);
+                        cl.set(Calendar.DAY_OF_MONTH, 1);
+                        cl.set(Calendar.MONTH, mon);
+                        // no '- 1' here because we are promoting the month
+                        continue;
+                    } else if (daysToAdd > 0 || dayShifted) {
+                        cl.set(Calendar.SECOND, 0);
+                        cl.set(Calendar.MINUTE, 0);
+                        cl.set(Calendar.HOUR_OF_DAY, 0);
+                        cl.set(Calendar.DAY_OF_MONTH, day);
+                        cl.set(Calendar.MONTH, mon - 1);
+                        // '- 1' here because we are NOT promoting the month
+                        continue;
+                    }
+                } else {
+                    int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+                    int dow = daysOfWeek.first(); // desired
+                    // d-o-w
+                    st = daysOfWeek.tailSet(cDow);
+                    if (st != null && st.size() > 0) {
+                        dow = st.first();
+                    }
+
+                    int daysToAdd = 0;
+                    if (cDow < dow) {
+                        daysToAdd = dow - cDow;
+                    }
+                    if (cDow > dow) {
+                        daysToAdd = dow + (7 - cDow);
+                    }
+
+                    int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+                    if (day + daysToAdd > lDay) { // will we pass the end of
+                        // the month?
+                        cl.set(Calendar.SECOND, 0);
+                        cl.set(Calendar.MINUTE, 0);
+                        cl.set(Calendar.HOUR_OF_DAY, 0);
+                        cl.set(Calendar.DAY_OF_MONTH, 1);
+                        cl.set(Calendar.MONTH, mon);
+                        // no '- 1' here because we are promoting the month
+                        continue;
+                    } else if (daysToAdd > 0) { // are we swithing days?
+                        cl.set(Calendar.SECOND, 0);
+                        cl.set(Calendar.MINUTE, 0);
+                        cl.set(Calendar.HOUR_OF_DAY, 0);
+                        cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
+                        cl.set(Calendar.MONTH, mon - 1);
+                        // '- 1' because calendar is 0-based for this field,
+                        // and we are 1-based
+                        continue;
+                    }
+                }
+            } else { // dayOfWSpec && !dayOfMSpec
+                throw new UnsupportedOperationException(
+                        "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
+            }
+            cl.set(Calendar.DAY_OF_MONTH, day);
+
+            mon = cl.get(Calendar.MONTH) + 1;
+            // '+ 1' because calendar is 0-based for this field, and we are
+            // 1-based
+            int year = cl.get(Calendar.YEAR);
+            t = -1;
+
+            // test for expressions that never generate a valid fire date,
+            // but keep looping...
+            if (year > MAX_YEAR) {
+                return null;
+            }
+
+            // get month...................................................
+            st = months.tailSet(mon);
+            if (st != null && st.size() != 0) {
+                t = mon;
+                mon = st.first();
+            } else {
+                mon = months.first();
+                year++;
+            }
+            if (mon != t) {
+                cl.set(Calendar.SECOND, 0);
+                cl.set(Calendar.MINUTE, 0);
+                cl.set(Calendar.HOUR_OF_DAY, 0);
+                cl.set(Calendar.DAY_OF_MONTH, 1);
+                cl.set(Calendar.MONTH, mon - 1);
+                // '- 1' because calendar is 0-based for this field, and we are
+                // 1-based
+                cl.set(Calendar.YEAR, year);
+                continue;
+            }
+            cl.set(Calendar.MONTH, mon - 1);
+            // '- 1' because calendar is 0-based for this field, and we are
+            // 1-based
+
+            year = cl.get(Calendar.YEAR);
+            t = -1;
+
+            // get year...................................................
+            st = years.tailSet(year);
+            if (st != null && st.size() != 0) {
+                t = year;
+                year = st.first();
+            } else {
+                return null; // ran out of years...
+            }
+
+            if (year != t) {
+                cl.set(Calendar.SECOND, 0);
+                cl.set(Calendar.MINUTE, 0);
+                cl.set(Calendar.HOUR_OF_DAY, 0);
+                cl.set(Calendar.DAY_OF_MONTH, 1);
+                cl.set(Calendar.MONTH, 0);
+                // '- 1' because calendar is 0-based for this field, and we are
+                // 1-based
+                cl.set(Calendar.YEAR, year);
+                continue;
+            }
+            cl.set(Calendar.YEAR, year);
+
+            gotOne = true;
+        } // while( !done )
+
+        return cl.getTime();
+    }
+
+    /**
+     * Advance the calendar to the particular hour paying particular attention
+     * to daylight saving problems.
+     * 
+     * @param cal the calendar to operate on
+     * @param hour the hour to set
+     */
+    protected void setCalendarHour(Calendar cal, int hour) {
+        cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
+        if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
+            cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
+        }
+    }
+
+    /**
+     * NOT YET IMPLEMENTED: Returns the time before the given time
+     * that the <code>CronExpression</code> matches.
+     */ 
+    public Date getTimeBefore(Date endTime) { 
+        // FUTURE_TODO: implement QUARTZ-423
+        return null;
+    }
+
+    /**
+     * NOT YET IMPLEMENTED: Returns the final time that the 
+     * <code>CronExpression</code> will match.
+     */
+    public Date getFinalFireTime() {
+        // FUTURE_TODO: implement QUARTZ-423
+        return null;
+    }
+    
+    protected boolean isLeapYear(int year) {
+        return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
+    }
+
+    protected int getLastDayOfMonth(int monthNum, int year) {
+
+        switch (monthNum) {
+            case 1:
+                return 31;
+            case 2:
+                return (isLeapYear(year)) ? 29 : 28;
+            case 3:
+                return 31;
+            case 4:
+                return 30;
+            case 5:
+                return 31;
+            case 6:
+                return 30;
+            case 7:
+                return 31;
+            case 8:
+                return 31;
+            case 9:
+                return 30;
+            case 10:
+                return 31;
+            case 11:
+                return 30;
+            case 12:
+                return 31;
+            default:
+                throw new IllegalArgumentException("Illegal month number: "
+                        + monthNum);
+        }
+    }
+    
+
+    private void readObject(java.io.ObjectInputStream stream)
+        throws java.io.IOException, ClassNotFoundException {
+        
+        stream.defaultReadObject();
+        try {
+            buildExpression(cronExpression);
+        } catch (Exception ignore) {
+        } // never happens
+    }    
+    
+    @Override
+    @Deprecated
+    public Object clone() {
+        return new CronExpression(this);
+    }
+}
+
+class ValueSet {
+    public int value;
+
+    public int pos;
+}

+ 0 - 33
xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java

@@ -1,33 +0,0 @@
-package com.xxl.job.admin.core.jobbean;
-
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
-import org.quartz.JobExecutionContext;
-import org.quartz.JobExecutionException;
-import org.quartz.JobKey;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.scheduling.quartz.QuartzJobBean;
-
-/**
- * http job bean
- * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
- * @author xuxueli 2015-12-17 18:20:34
- */
-//@DisallowConcurrentExecution
-public class RemoteHttpJobBean extends QuartzJobBean {
-	private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
-
-	@Override
-	protected void executeInternal(JobExecutionContext context)
-			throws JobExecutionException {
-
-		// load jobId
-		JobKey jobKey = context.getTrigger().getJobKey();
-		Integer jobId = Integer.valueOf(jobKey.getName());
-
-		// trigger
-		JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
-	}
-
-}

+ 26 - 10
xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java

@@ -9,10 +9,10 @@ import java.util.Date;
  */
 public class XxlJobInfo {
 	
-	private int id;				// 主键ID	    (JobKey.name)
+	private int id;				// 主键ID
 	
-	private int jobGroup;		// 执行器主键ID	(JobKey.group)
-	private String jobCron;		// 任务执行CRON表达式 【base on quartz】
+	private int jobGroup;		// 执行器主键ID
+	private String jobCron;		// 任务执行CRON表达式
 	private String jobDesc;
 	
 	private Date addTime;
@@ -34,9 +34,10 @@ public class XxlJobInfo {
 	private Date glueUpdatetime;	// GLUE更新时间
 
 	private String childJobId;		// 子任务ID,多个逗号分隔
-	
-	// copy from quartz
-	private String jobStatus;		// 任务状态 【base on quartz】
+
+	private int triggerStatus;		// 调度状态:0-停止,1-运行
+	private long triggerLastTime;	// 上次调度时间
+	private long triggerNextTime;	// 下次调度时间
 
 
 	public int getId() {
@@ -191,12 +192,27 @@ public class XxlJobInfo {
 		this.childJobId = childJobId;
 	}
 
-	public String getJobStatus() {
-		return jobStatus;
+	public int getTriggerStatus() {
+		return triggerStatus;
 	}
 
-	public void setJobStatus(String jobStatus) {
-		this.jobStatus = jobStatus;
+	public void setTriggerStatus(int triggerStatus) {
+		this.triggerStatus = triggerStatus;
 	}
 
+	public long getTriggerLastTime() {
+		return triggerLastTime;
+	}
+
+	public void setTriggerLastTime(long triggerLastTime) {
+		this.triggerLastTime = triggerLastTime;
+	}
+
+	public long getTriggerNextTime() {
+		return triggerNextTime;
+	}
+
+	public void setTriggerNextTime(long triggerNextTime) {
+		this.triggerNextTime = triggerNextTime;
+	}
 }

+ 32 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java

@@ -0,0 +1,32 @@
+//package com.xxl.job.admin.core.jobbean;
+//
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+//import org.quartz.JobExecutionContext;
+//import org.quartz.JobExecutionException;
+//import org.quartz.JobKey;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.scheduling.quartz.QuartzJobBean;
+//
+///**
+// * http job bean
+// * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
+// * @author xuxueli 2015-12-17 18:20:34
+// */
+////@DisallowConcurrentExecution
+//public class RemoteHttpJobBean extends QuartzJobBean {
+//	private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
+//
+//	@Override
+//	protected void executeInternal(JobExecutionContext context)
+//			throws JobExecutionException {
+//
+//		// load jobId
+//		JobKey jobKey = context.getTrigger().getJobKey();
+//		Integer jobId = Integer.valueOf(jobKey.getName());
+//
+//
+//	}
+//
+//}

+ 413 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java

@@ -0,0 +1,413 @@
+//package com.xxl.job.admin.core.schedule;
+//
+//import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+//import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
+//import com.xxl.job.admin.core.model.XxlJobInfo;
+//import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.util.I18nUtil;
+//import com.xxl.job.core.biz.AdminBiz;
+//import com.xxl.job.core.biz.ExecutorBiz;
+//import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+//import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+//import com.xxl.rpc.remoting.invoker.call.CallType;
+//import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+//import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+//import com.xxl.rpc.remoting.net.NetEnum;
+//import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+//import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+//import com.xxl.rpc.serialize.Serializer;
+//import org.quartz.*;
+//import org.quartz.Trigger.TriggerState;
+//import org.quartz.impl.triggers.CronTriggerImpl;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.util.Assert;
+//
+//import javax.servlet.ServletException;
+//import javax.servlet.http.HttpServletRequest;
+//import javax.servlet.http.HttpServletResponse;
+//import java.io.IOException;
+//import java.util.Date;
+//import java.util.concurrent.ConcurrentHashMap;
+//
+///**
+// * base quartz scheduler util
+// * @author xuxueli 2015-12-19 16:13:53
+// */
+//public final class XxlJobDynamicScheduler {
+//    private static final Logger logger = LoggerFactory.getLogger(XxlJobDynamicScheduler_old.class);
+//
+//    // ---------------------- param ----------------------
+//
+//    // scheduler
+//    private static Scheduler scheduler;
+//    public void setScheduler(Scheduler scheduler) {
+//		XxlJobDynamicScheduler_old.scheduler = scheduler;
+//	}
+//
+//
+//    // ---------------------- init + destroy ----------------------
+//    public void start() throws Exception {
+//        // valid
+//        Assert.notNull(scheduler, "quartz scheduler is null");
+//
+//        // init i18n
+//        initI18n();
+//
+//        // admin registry monitor run
+//        JobRegistryMonitorHelper.getInstance().start();
+//
+//        // admin monitor run
+//        JobFailMonitorHelper.getInstance().start();
+//
+//        // admin-server
+//        initRpcProvider();
+//
+//        logger.info(">>>>>>>>> init xxl-job admin success.");
+//    }
+//
+//
+//    public void destroy() throws Exception {
+//        // admin trigger pool stop
+//        JobTriggerPoolHelper.toStop();
+//
+//        // admin registry stop
+//        JobRegistryMonitorHelper.getInstance().toStop();
+//
+//        // admin monitor stop
+//        JobFailMonitorHelper.getInstance().toStop();
+//
+//        // admin-server
+//        stopRpcProvider();
+//    }
+//
+//
+//    // ---------------------- I18n ----------------------
+//
+//    private void initI18n(){
+//        for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+//            item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+//        }
+//    }
+//
+//
+//    // ---------------------- admin rpc provider (no server version) ----------------------
+//    private static ServletServerHandler servletServerHandler;
+//    private void initRpcProvider(){
+//        // init
+//        XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+//        xxlRpcProviderFactory.initConfig(
+//                NetEnum.NETTY_HTTP,
+//                Serializer.SerializeEnum.HESSIAN.getSerializer(),
+//                null,
+//                0,
+//                XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+//                null,
+//                null);
+//
+//        // add services
+//        xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+//
+//        // servlet handler
+//        servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+//    }
+//    private void stopRpcProvider() throws Exception {
+//        XxlRpcInvokerFactory.getInstance().stop();
+//    }
+//    public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+//        servletServerHandler.handle(null, request, response);
+//    }
+//
+//
+//    // ---------------------- executor-client ----------------------
+//    private static ConcurrentHashMap<String, ExecutorBiz> executorBizRepository = new ConcurrentHashMap<String, ExecutorBiz>();
+//    public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+//        // valid
+//        if (address==null || address.trim().length()==0) {
+//            return null;
+//        }
+//
+//        // load-cache
+//        address = address.trim();
+//        ExecutorBiz executorBiz = executorBizRepository.get(address);
+//        if (executorBiz != null) {
+//            return executorBiz;
+//        }
+//
+//        // set-cache
+//        executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+//                NetEnum.NETTY_HTTP,
+//                Serializer.SerializeEnum.HESSIAN.getSerializer(),
+//                CallType.SYNC,
+//                LoadBalance.ROUND,
+//                ExecutorBiz.class,
+//                null,
+//                5000,
+//                address,
+//                XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+//                null,
+//                null).getObject();
+//
+//        executorBizRepository.put(address, executorBiz);
+//        return executorBiz;
+//    }
+//
+//
+//    // ---------------------- schedule util ----------------------
+//
+//    /**
+//     * fill job info
+//     *
+//     * @param jobInfo
+//     */
+//	public static void fillJobInfo(XxlJobInfo jobInfo) {
+//
+//        String name = String.valueOf(jobInfo.getId());
+//
+//        // trigger key
+//        TriggerKey triggerKey = TriggerKey.triggerKey(name);
+//        try {
+//
+//            // trigger cron
+//			Trigger trigger = scheduler.getTrigger(triggerKey);
+//			if (trigger!=null && trigger instanceof CronTriggerImpl) {
+//				String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
+//				jobInfo.setJobCron(cronExpression);
+//			}
+//
+//            // trigger state
+//            TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+//			if (triggerState!=null) {
+//				jobInfo.setJobStatus(triggerState.name());
+//			}
+//
+//            //JobKey jobKey = new JobKey(jobInfo.getJobName(), String.valueOf(jobInfo.getJobGroup()));
+//            //JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+//            //String jobClass = jobDetail.getJobClass().getName();
+//
+//		} catch (SchedulerException e) {
+//			logger.error(e.getMessage(), e);
+//		}
+//	}
+//
+//
+//    /**
+//     * add trigger + job
+//     *
+//     * @param jobName
+//     * @param cronExpression
+//     * @return
+//     * @throws SchedulerException
+//     */
+//	public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
+//    	// 1、job key
+//        TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//        JobKey jobKey = new JobKey(jobName);
+//
+//        // 2、valid
+//        if (scheduler.checkExists(triggerKey)) {
+//            return true;    // PASS
+//        }
+//
+//        // 3、corn trigger
+//        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();   // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
+//        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+//        // 4、job detail
+//		Class<? extends Job> jobClass_ = RemoteHttpJobBean.class;   // Class.forName(jobInfo.getJobClass());
+//		JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
+//
+//        /*if (jobInfo.getJobData()!=null) {
+//        	JobDataMap jobDataMap = jobDetail.getJobDataMap();
+//        	jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class));
+//        	// JobExecutionContext context.getMergedJobDataMap().get("mailGuid");
+//		}*/
+//
+//        // 5、schedule job
+//        Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
+//
+//        logger.info(">>>>>>>>>>> addJob success(quartz), jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
+//        return true;
+//    }
+//
+//
+//    /**
+//     * remove trigger + job
+//     *
+//     * @param jobName
+//     * @return
+//     * @throws SchedulerException
+//     */
+//    public static boolean removeJob(String jobName) throws SchedulerException {
+//
+//        JobKey jobKey = new JobKey(jobName);
+//        scheduler.deleteJob(jobKey);
+//
+//        /*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//        if (scheduler.checkExists(triggerKey)) {
+//            scheduler.unscheduleJob(triggerKey);    // trigger + job
+//        }*/
+//
+//        logger.info(">>>>>>>>>>> removeJob success(quartz), jobKey:{}", jobKey);
+//        return true;
+//    }
+//
+//
+//    /**
+//     * updateJobCron
+//     *
+//     * @param jobName
+//     * @param cronExpression
+//     * @return
+//     * @throws SchedulerException
+//     */
+//	public static boolean updateJobCron(String jobName, String cronExpression) throws SchedulerException {
+//
+//        // 1、job key
+//        TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+//        // 2、valid
+//        if (!scheduler.checkExists(triggerKey)) {
+//            return true;    // PASS
+//        }
+//
+//        CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
+//
+//        // 3、avoid repeat cron
+//        String oldCron = oldTrigger.getCronExpression();
+//        if (oldCron.equals(cronExpression)){
+//            return true;    // PASS
+//        }
+//
+//        // 4、new cron trigger
+//        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
+//        oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+//        // 5、rescheduleJob
+//        scheduler.rescheduleJob(triggerKey, oldTrigger);
+//
+//        /*
+//        JobKey jobKey = new JobKey(jobName);
+//
+//        // old job detail
+//        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+//
+//        // new trigger
+//        HashSet<Trigger> triggerSet = new HashSet<Trigger>();
+//        triggerSet.add(cronTrigger);
+//        // cover trigger of job detail
+//        scheduler.scheduleJob(jobDetail, triggerSet, true);*/
+//
+//        logger.info(">>>>>>>>>>> resumeJob success, JobName:{}", jobName);
+//        return true;
+//    }
+//
+//
+//    /**
+//     * pause
+//     *
+//     * @param jobName
+//     * @return
+//     * @throws SchedulerException
+//     */
+//    /*public static boolean pauseJob(String jobName) throws SchedulerException {
+//
+//    	TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+//        boolean result = false;
+//        if (scheduler.checkExists(triggerKey)) {
+//            scheduler.pauseTrigger(triggerKey);
+//            result =  true;
+//        }
+//
+//        logger.info(">>>>>>>>>>> pauseJob {}, triggerKey:{}", (result?"success":"fail"),triggerKey);
+//        return result;
+//    }*/
+//
+//
+//    /**
+//     * resume
+//     *
+//     * @param jobName
+//     * @return
+//     * @throws SchedulerException
+//     */
+//    /*public static boolean resumeJob(String jobName) throws SchedulerException {
+//
+//        TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+//        boolean result = false;
+//        if (scheduler.checkExists(triggerKey)) {
+//            scheduler.resumeTrigger(triggerKey);
+//            result = true;
+//        }
+//
+//        logger.info(">>>>>>>>>>> resumeJob {}, triggerKey:{}", (result?"success":"fail"), triggerKey);
+//        return result;
+//    }*/
+//
+//
+//    /**
+//     * run
+//     *
+//     * @param jobName
+//     * @return
+//     * @throws SchedulerException
+//     */
+//    /*public static boolean triggerJob(String jobName) throws SchedulerException {
+//    	// TriggerKey : name + group
+//    	JobKey jobKey = new JobKey(jobName);
+//        TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+//        boolean result = false;
+//        if (scheduler.checkExists(triggerKey)) {
+//            scheduler.triggerJob(jobKey);
+//            result = true;
+//            logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
+//        } else {
+//        	logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
+//        }
+//        return result;
+//    }*/
+//
+//
+//    /**
+//     * finaAllJobList
+//     *
+//     * @return
+//     *//*
+//    @Deprecated
+//    public static List<Map<String, Object>> finaAllJobList(){
+//        List<Map<String, Object>> jobList = new ArrayList<Map<String,Object>>();
+//
+//        try {
+//            if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) {
+//                return null;
+//            }
+//            String groupName = scheduler.getJobGroupNames().get(0);
+//            Set<JobKey> jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
+//            if (jobKeys!=null && jobKeys.size()>0) {
+//                for (JobKey jobKey : jobKeys) {
+//                    TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), Scheduler.DEFAULT_GROUP);
+//                    Trigger trigger = scheduler.getTrigger(triggerKey);
+//                    JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+//                    TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+//                    Map<String, Object> jobMap = new HashMap<String, Object>();
+//                    jobMap.put("TriggerKey", triggerKey);
+//                    jobMap.put("Trigger", trigger);
+//                    jobMap.put("JobDetail", jobDetail);
+//                    jobMap.put("TriggerState", triggerState);
+//                    jobList.add(jobMap);
+//                }
+//            }
+//
+//        } catch (SchedulerException e) {
+//            logger.error(e.getMessage(), e);
+//            return null;
+//        }
+//        return jobList;
+//    }*/
+//
+//}

+ 58 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java

@@ -0,0 +1,58 @@
+//package com.xxl.job.admin.core.quartz;
+//
+//import org.quartz.SchedulerConfigException;
+//import org.quartz.spi.ThreadPool;
+//
+///**
+// * single thread pool, for async trigger
+// *
+// * @author xuxueli 2019-03-06
+// */
+//public class XxlJobThreadPool implements ThreadPool {
+//
+//    @Override
+//    public boolean runInThread(Runnable runnable) {
+//
+//        // async run
+//        runnable.run();
+//        return true;
+//
+//        //return false;
+//    }
+//
+//    @Override
+//    public int blockForAvailableThreads() {
+//        return 1;
+//    }
+//
+//    @Override
+//    public void initialize() throws SchedulerConfigException {
+//
+//    }
+//
+//    @Override
+//    public void shutdown(boolean waitForJobsToComplete) {
+//
+//    }
+//
+//    @Override
+//    public int getPoolSize() {
+//        return 1;
+//    }
+//
+//    @Override
+//    public void setInstanceId(String schedInstId) {
+//
+//    }
+//
+//    @Override
+//    public void setInstanceName(String schedName) {
+//
+//    }
+//
+//    // support
+//    public void setThreadCount(int count) {
+//        //
+//    }
+//
+//}

+ 0 - 58
xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java

@@ -1,58 +0,0 @@
-package com.xxl.job.admin.core.quartz;
-
-import org.quartz.SchedulerConfigException;
-import org.quartz.spi.ThreadPool;
-
-/**
- * single thread pool, for async trigger
- *
- * @author xuxueli 2019-03-06
- */
-public class XxlJobThreadPool implements ThreadPool {
-
-    @Override
-    public boolean runInThread(Runnable runnable) {
-
-        // async run
-        runnable.run();
-        return true;
-
-        //return false;
-    }
-
-    @Override
-    public int blockForAvailableThreads() {
-        return 1;
-    }
-
-    @Override
-    public void initialize() throws SchedulerConfigException {
-
-    }
-
-    @Override
-    public void shutdown(boolean waitForJobsToComplete) {
-
-    }
-
-    @Override
-    public int getPoolSize() {
-        return 1;
-    }
-
-    @Override
-    public void setInstanceId(String schedInstId) {
-
-    }
-
-    @Override
-    public void setInstanceName(String schedName) {
-
-    }
-
-    // support
-    public void setThreadCount(int count) {
-        //
-    }
-
-}

+ 2 - 2
xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java

@@ -1,7 +1,7 @@
 package com.xxl.job.admin.core.route.strategy;
 
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
 import com.xxl.job.admin.core.route.ExecutorRouter;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
 import com.xxl.job.admin.core.util.I18nUtil;
 import com.xxl.job.core.biz.ExecutorBiz;
 import com.xxl.job.core.biz.model.ReturnT;
@@ -21,7 +21,7 @@ public class ExecutorRouteBusyover extends ExecutorRouter {
             // beat
             ReturnT<String> idleBeatResult = null;
             try {
-                ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+                ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
                 idleBeatResult = executorBiz.idleBeat(triggerParam.getJobId());
             } catch (Exception e) {
                 logger.error(e.getMessage(), e);

+ 2 - 2
xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java

@@ -1,7 +1,7 @@
 package com.xxl.job.admin.core.route.strategy;
 
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
 import com.xxl.job.admin.core.route.ExecutorRouter;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
 import com.xxl.job.admin.core.util.I18nUtil;
 import com.xxl.job.core.biz.ExecutorBiz;
 import com.xxl.job.core.biz.model.ReturnT;
@@ -22,7 +22,7 @@ public class ExecutorRouteFailover extends ExecutorRouter {
             // beat
             ReturnT<String> beatResult = null;
             try {
-                ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+                ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
                 beatResult = executorBiz.beat();
             } catch (Exception e) {
                 logger.error(e.getMessage(), e);

+ 0 - 413
xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java

@@ -1,413 +0,0 @@
-package com.xxl.job.admin.core.schedule;
-
-import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
-import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
-import com.xxl.job.admin.core.model.XxlJobInfo;
-import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
-import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.util.I18nUtil;
-import com.xxl.job.core.biz.AdminBiz;
-import com.xxl.job.core.biz.ExecutorBiz;
-import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
-import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
-import com.xxl.rpc.remoting.invoker.call.CallType;
-import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
-import com.xxl.rpc.remoting.invoker.route.LoadBalance;
-import com.xxl.rpc.remoting.net.NetEnum;
-import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
-import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
-import com.xxl.rpc.serialize.Serializer;
-import org.quartz.*;
-import org.quartz.Trigger.TriggerState;
-import org.quartz.impl.triggers.CronTriggerImpl;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.util.Assert;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.util.Date;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * base quartz scheduler util
- * @author xuxueli 2015-12-19 16:13:53
- */
-public final class XxlJobDynamicScheduler {
-    private static final Logger logger = LoggerFactory.getLogger(XxlJobDynamicScheduler.class);
-
-    // ---------------------- param ----------------------
-
-    // scheduler
-    private static Scheduler scheduler;
-    public void setScheduler(Scheduler scheduler) {
-		XxlJobDynamicScheduler.scheduler = scheduler;
-	}
-
-
-    // ---------------------- init + destroy ----------------------
-    public void start() throws Exception {
-        // valid
-        Assert.notNull(scheduler, "quartz scheduler is null");
-
-        // init i18n
-        initI18n();
-
-        // admin registry monitor run
-        JobRegistryMonitorHelper.getInstance().start();
-
-        // admin monitor run
-        JobFailMonitorHelper.getInstance().start();
-
-        // admin-server
-        initRpcProvider();
-
-        logger.info(">>>>>>>>> init xxl-job admin success.");
-    }
-
-
-    public void destroy() throws Exception {
-        // admin trigger pool stop
-        JobTriggerPoolHelper.toStop();
-
-        // admin registry stop
-        JobRegistryMonitorHelper.getInstance().toStop();
-
-        // admin monitor stop
-        JobFailMonitorHelper.getInstance().toStop();
-
-        // admin-server
-        stopRpcProvider();
-    }
-
-
-    // ---------------------- I18n ----------------------
-
-    private void initI18n(){
-        for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
-            item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
-        }
-    }
-
-
-    // ---------------------- admin rpc provider (no server version) ----------------------
-    private static ServletServerHandler servletServerHandler;
-    private void initRpcProvider(){
-        // init
-        XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
-        xxlRpcProviderFactory.initConfig(
-                NetEnum.NETTY_HTTP,
-                Serializer.SerializeEnum.HESSIAN.getSerializer(),
-                null,
-                0,
-                XxlJobAdminConfig.getAdminConfig().getAccessToken(),
-                null,
-                null);
-
-        // add services
-        xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
-
-        // servlet handler
-        servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
-    }
-    private void stopRpcProvider() throws Exception {
-        XxlRpcInvokerFactory.getInstance().stop();
-    }
-    public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
-        servletServerHandler.handle(null, request, response);
-    }
-
-
-    // ---------------------- executor-client ----------------------
-    private static ConcurrentHashMap<String, ExecutorBiz> executorBizRepository = new ConcurrentHashMap<String, ExecutorBiz>();
-    public static ExecutorBiz getExecutorBiz(String address) throws Exception {
-        // valid
-        if (address==null || address.trim().length()==0) {
-            return null;
-        }
-
-        // load-cache
-        address = address.trim();
-        ExecutorBiz executorBiz = executorBizRepository.get(address);
-        if (executorBiz != null) {
-            return executorBiz;
-        }
-
-        // set-cache
-        executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
-                NetEnum.NETTY_HTTP,
-                Serializer.SerializeEnum.HESSIAN.getSerializer(),
-                CallType.SYNC,
-                LoadBalance.ROUND,
-                ExecutorBiz.class,
-                null,
-                5000,
-                address,
-                XxlJobAdminConfig.getAdminConfig().getAccessToken(),
-                null,
-                null).getObject();
-
-        executorBizRepository.put(address, executorBiz);
-        return executorBiz;
-    }
-
-
-    // ---------------------- schedule util ----------------------
-
-    /**
-     * fill job info
-     *
-     * @param jobInfo
-     */
-	public static void fillJobInfo(XxlJobInfo jobInfo) {
-
-        String name = String.valueOf(jobInfo.getId());
-
-        // trigger key
-        TriggerKey triggerKey = TriggerKey.triggerKey(name);
-        try {
-
-            // trigger cron
-			Trigger trigger = scheduler.getTrigger(triggerKey);
-			if (trigger!=null && trigger instanceof CronTriggerImpl) {
-				String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
-				jobInfo.setJobCron(cronExpression);
-			}
-
-            // trigger state
-            TriggerState triggerState = scheduler.getTriggerState(triggerKey);
-			if (triggerState!=null) {
-				jobInfo.setJobStatus(triggerState.name());
-			}
-
-            //JobKey jobKey = new JobKey(jobInfo.getJobName(), String.valueOf(jobInfo.getJobGroup()));
-            //JobDetail jobDetail = scheduler.getJobDetail(jobKey);
-            //String jobClass = jobDetail.getJobClass().getName();
-			
-		} catch (SchedulerException e) {
-			logger.error(e.getMessage(), e);
-		}
-	}
-
-
-    /**
-     * add trigger + job
-     *
-     * @param jobName
-     * @param cronExpression
-     * @return
-     * @throws SchedulerException
-     */
-	public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
-    	// 1、job key
-        TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-        JobKey jobKey = new JobKey(jobName);
-
-        // 2、valid
-        if (scheduler.checkExists(triggerKey)) {
-            return true;    // PASS
-        }
-
-        // 3、corn trigger
-        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();   // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
-        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-
-        // 4、job detail
-		Class<? extends Job> jobClass_ = RemoteHttpJobBean.class;   // Class.forName(jobInfo.getJobClass());
-		JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
-
-        /*if (jobInfo.getJobData()!=null) {
-        	JobDataMap jobDataMap = jobDetail.getJobDataMap();
-        	jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class));	
-        	// JobExecutionContext context.getMergedJobDataMap().get("mailGuid");
-		}*/
-        
-        // 5、schedule job
-        Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
-
-        logger.info(">>>>>>>>>>> addJob success(quartz), jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
-        return true;
-    }
-
-
-    /**
-     * remove trigger + job
-     *
-     * @param jobName
-     * @return
-     * @throws SchedulerException
-     */
-    public static boolean removeJob(String jobName) throws SchedulerException {
-
-        JobKey jobKey = new JobKey(jobName);
-        scheduler.deleteJob(jobKey);
-
-        /*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-        if (scheduler.checkExists(triggerKey)) {
-            scheduler.unscheduleJob(triggerKey);    // trigger + job
-        }*/
-
-        logger.info(">>>>>>>>>>> removeJob success(quartz), jobKey:{}", jobKey);
-        return true;
-    }
-
-
-    /**
-     * updateJobCron
-     *
-     * @param jobName
-     * @param cronExpression
-     * @return
-     * @throws SchedulerException
-     */
-	public static boolean updateJobCron(String jobName, String cronExpression) throws SchedulerException {
-
-        // 1、job key
-        TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
-        // 2、valid
-        if (!scheduler.checkExists(triggerKey)) {
-            return true;    // PASS
-        }
-
-        CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
-
-        // 3、avoid repeat cron
-        String oldCron = oldTrigger.getCronExpression();
-        if (oldCron.equals(cronExpression)){
-            return true;    // PASS
-        }
-
-        // 4、new cron trigger
-        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
-        oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-
-        // 5、rescheduleJob
-        scheduler.rescheduleJob(triggerKey, oldTrigger);
-
-        /*
-        JobKey jobKey = new JobKey(jobName);
-
-        // old job detail
-        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
-
-        // new trigger
-        HashSet<Trigger> triggerSet = new HashSet<Trigger>();
-        triggerSet.add(cronTrigger);
-        // cover trigger of job detail
-        scheduler.scheduleJob(jobDetail, triggerSet, true);*/
-
-        logger.info(">>>>>>>>>>> resumeJob success, JobName:{}", jobName);
-        return true;
-    }
-
-
-    /**
-     * pause
-     *
-     * @param jobName
-     * @return
-     * @throws SchedulerException
-     */
-    /*public static boolean pauseJob(String jobName) throws SchedulerException {
-
-    	TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
-        boolean result = false;
-        if (scheduler.checkExists(triggerKey)) {
-            scheduler.pauseTrigger(triggerKey);
-            result =  true;
-        }
-
-        logger.info(">>>>>>>>>>> pauseJob {}, triggerKey:{}", (result?"success":"fail"),triggerKey);
-        return result;
-    }*/
-
-
-    /**
-     * resume
-     *
-     * @param jobName
-     * @return
-     * @throws SchedulerException
-     */
-    /*public static boolean resumeJob(String jobName) throws SchedulerException {
-
-        TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-        
-        boolean result = false;
-        if (scheduler.checkExists(triggerKey)) {
-            scheduler.resumeTrigger(triggerKey);
-            result = true;
-        }
-
-        logger.info(">>>>>>>>>>> resumeJob {}, triggerKey:{}", (result?"success":"fail"), triggerKey);
-        return result;
-    }*/
-
-
-    /**
-     * run
-     *
-     * @param jobName
-     * @return
-     * @throws SchedulerException
-     */
-    /*public static boolean triggerJob(String jobName) throws SchedulerException {
-    	// TriggerKey : name + group
-    	JobKey jobKey = new JobKey(jobName);
-        TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
-        boolean result = false;
-        if (scheduler.checkExists(triggerKey)) {
-            scheduler.triggerJob(jobKey);
-            result = true;
-            logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
-        } else {
-        	logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
-        }
-        return result;
-    }*/
-
-
-    /**
-     * finaAllJobList
-     *
-     * @return
-     *//*
-    @Deprecated
-    public static List<Map<String, Object>> finaAllJobList(){
-        List<Map<String, Object>> jobList = new ArrayList<Map<String,Object>>();
-
-        try {
-            if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) {
-                return null;
-            }
-            String groupName = scheduler.getJobGroupNames().get(0);
-            Set<JobKey> jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
-            if (jobKeys!=null && jobKeys.size()>0) {
-                for (JobKey jobKey : jobKeys) {
-                    TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), Scheduler.DEFAULT_GROUP);
-                    Trigger trigger = scheduler.getTrigger(triggerKey);
-                    JobDetail jobDetail = scheduler.getJobDetail(jobKey);
-                    TriggerState triggerState = scheduler.getTriggerState(triggerKey);
-                    Map<String, Object> jobMap = new HashMap<String, Object>();
-                    jobMap.put("TriggerKey", triggerKey);
-                    jobMap.put("Trigger", trigger);
-                    jobMap.put("JobDetail", jobDetail);
-                    jobMap.put("TriggerState", triggerState);
-                    jobList.add(jobMap);
-                }
-            }
-
-        } catch (SchedulerException e) {
-            logger.error(e.getMessage(), e);
-            return null;
-        }
-        return jobList;
-    }*/
-
-}

+ 218 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java

@@ -0,0 +1,218 @@
+package com.xxl.job.admin.core.thread;
+
+import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.model.XxlJobInfo;
+import com.xxl.job.admin.core.cron.CronExpression;
+import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author xuxueli 2019-05-21
+ */
+public class JobScheduleHelper {
+    private static Logger logger = LoggerFactory.getLogger(JobScheduleHelper.class);
+
+    private static JobScheduleHelper instance = new JobScheduleHelper();
+    public static JobScheduleHelper getInstance(){
+        return instance;
+    }
+
+    private Thread scheduleThread;
+    private Thread ringThread;
+    private volatile boolean toStop = false;
+    private volatile static Map<Integer, List<Integer>> ringData = new ConcurrentHashMap<>();
+
+    public void start(){
+
+        // schedule thread
+        scheduleThread = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                while (!toStop) {
+                    // 随机休眠1s内
+                    try {
+                        TimeUnit.MILLISECONDS.sleep(500+new Random().nextInt(500));
+                    } catch (InterruptedException e) {
+                        logger.error(e.getMessage(), e);
+                    }
+
+                    // 匹配任务
+                    Connection conn = null;
+                    PreparedStatement preparedStatement = null;
+                    try {
+                        if (conn==null || conn.isClosed()) {
+                            conn = XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection();
+                        }
+                        conn.setAutoCommit(false);
+
+                        preparedStatement = conn.prepareStatement(  "select * from XXL_JOB_LOCK where lock_name = 'schedule_lock' for update" );
+                        preparedStatement.execute();
+
+                        // tx start
+
+                        // 1、查询JOB:"下次调度30s内"  ( ...... -5 ... now ...... +30 ...... )
+                        long maxNextTime = System.currentTimeMillis() + 30000;
+                        long nowTime = System.currentTimeMillis();
+                        List<XxlJobInfo> scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleJobQuery(maxNextTime);
+                        if (scheduleList!=null && scheduleList.size()>0) {
+                            // 2、推送时间轮
+                            for (XxlJobInfo jobInfo: scheduleList) {
+
+                                // 过期策略:过期=更新下次触发时间为当前、过期是否5s内=立即出发,否则忽略;
+                                if (jobInfo.getTriggerNextTime() < nowTime) {
+                                    jobInfo.setTriggerNextTime(nowTime);
+                                    if (jobInfo.getTriggerNextTime() < nowTime-10000) {
+                                        continue;
+                                    }
+                                }
+
+                                // push async ring
+                                int second = (int)((jobInfo.getTriggerNextTime()/1000)%60);
+                                List<Integer> ringItemData = ringData.get(second);
+                                if (ringItemData == null) {
+                                    ringItemData = new ArrayList<Integer>();
+                                    ringData.put(second, ringItemData);
+                                }
+                                ringItemData.add(jobInfo.getId());
+
+                                logger.info(">>>>>>>>>>> xxl-job, push time-ring : " + second + " = " + Arrays.asList(ringItemData) );
+                            }
+
+                            // 3、更新trigger信息
+                            for (XxlJobInfo jobInfo: scheduleList) {
+                                // update
+                                jobInfo.setTriggerLastTime(jobInfo.getTriggerNextTime());
+                                jobInfo.setTriggerNextTime(
+                                        new CronExpression(jobInfo.getJobCron())
+                                                .getNextValidTimeAfter(new Date(jobInfo.getTriggerNextTime()))
+                                                .getTime()
+                                );
+                                XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleUpdate(jobInfo);
+                            }
+
+                        }
+
+                        // tx stop
+
+                        conn.commit();
+                    } catch (Exception e) {
+                        if (!toStop) {
+                            logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread error:{}", e);
+                        }
+                    } finally {
+                        if (conn != null) {
+                            try {
+                                conn.close();
+                            } catch (SQLException e) {
+                            }
+                        }
+                        if (null != preparedStatement) {
+                            try {
+                                preparedStatement.close();
+                            } catch (SQLException ignore) {
+                            }
+                        }
+                    }
+                }
+                logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread stop");
+            }
+        });
+        scheduleThread.setDaemon(true);
+        scheduleThread.setName("xxl-job, admin JobScheduleHelper#scheduleThread");
+        scheduleThread.start();
+
+
+        // ring thread
+        ringThread = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                int lastSecond = -1;
+                while (!toStop) {
+                    try {
+                        // second data
+                        List<Integer> ringItemData = new ArrayList<>();
+                        int nowSecond = (int)((System.currentTimeMillis()/1000)%60);   // 避免处理耗时太长,跨过刻度;
+                        if (lastSecond == -1) {
+                            if (ringData.containsKey(nowSecond)) {
+                                List<Integer> tmpData = ringData.remove(nowSecond);
+                                if (tmpData != null) {
+                                    ringItemData.addAll(tmpData);
+                                }
+                            }
+                            lastSecond = nowSecond;
+                        } else {
+                            for (int i = 1; i <=60; i++) {
+                                int secondItem = (lastSecond+i)%60;
+
+                                List<Integer> tmpData = ringData.remove(secondItem);
+                                if (tmpData != null) {
+                                    ringItemData.addAll(tmpData);
+                                }
+
+                                if (secondItem == nowSecond) {
+                                    break;
+                                }
+                            }
+                            lastSecond = nowSecond;
+                        }
+
+                        logger.info(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = " + Arrays.asList(ringItemData) );
+                        if (ringItemData!=null && ringItemData.size()>0) {
+                            // do trigger
+                            for (int jobId: ringItemData) {
+                                // do trigger
+                                JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
+                            }
+
+                            // clear
+                            ringItemData.clear();
+                        }
+                    } catch (Exception e) {
+                        if (!toStop) {
+                            logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread error:{}", e);
+                        }
+                    }
+
+                    try {
+                        TimeUnit.SECONDS.sleep(1);
+                    } catch (InterruptedException e) {
+                        logger.error(e.getMessage(), e);
+                    }
+                }
+                logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread stop");
+            }
+        });
+        ringThread.setDaemon(true);
+        ringThread.setName("xxl-job, admin JobScheduleHelper#ringThread");
+        ringThread.start();
+    }
+
+    public void toStop(){
+        toStop = true;
+
+        // interrupt and wait
+        scheduleThread.interrupt();
+        try {
+            scheduleThread.join();
+        } catch (InterruptedException e) {
+            logger.error(e.getMessage(), e);
+        }
+
+        // interrupt and wait
+        ringThread.interrupt();
+        try {
+            ringThread.join();
+        } catch (InterruptedException e) {
+            logger.error(e.getMessage(), e);
+        }
+    }
+
+}

+ 2 - 2
xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java

@@ -1,11 +1,11 @@
 package com.xxl.job.admin.core.trigger;
 
 import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
 import com.xxl.job.admin.core.model.XxlJobGroup;
 import com.xxl.job.admin.core.model.XxlJobInfo;
 import com.xxl.job.admin.core.model.XxlJobLog;
 import com.xxl.job.admin.core.route.ExecutorRouteStrategyEnum;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
 import com.xxl.job.admin.core.util.I18nUtil;
 import com.xxl.job.core.biz.ExecutorBiz;
 import com.xxl.job.core.biz.model.ReturnT;
@@ -192,7 +192,7 @@ public class XxlJobTrigger {
     public static ReturnT<String> runExecutor(TriggerParam triggerParam, String address){
         ReturnT<String> runResult = null;
         try {
-            ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+            ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
             runResult = executorBiz.run(triggerParam);
         } catch (Exception e) {
             logger.error(">>>>>>>>>>> xxl-job trigger error, please check if the executor[{}] is running.", address, e);

+ 6 - 1
xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java

@@ -29,7 +29,7 @@ public interface XxlJobInfoDao {
 
 	public XxlJobInfo loadById(@Param("id") int id);
 	
-	public int update(XxlJobInfo item);
+	public int update(XxlJobInfo xxlJobInfo);
 	
 	public int delete(@Param("id") int id);
 
@@ -37,4 +37,9 @@ public interface XxlJobInfoDao {
 
 	public int findAllCount();
 
+	public List<XxlJobInfo> scheduleJobQuery(@Param("maxNextTime") long maxNextTime);
+
+	public int scheduleUpdate(XxlJobInfo xxlJobInfo);
+
+
 }

+ 6 - 6
xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java

@@ -28,7 +28,7 @@ public interface XxlJobService {
 	public Map<String, Object> pageList(int start, int length, int jobGroup, String jobDesc, String executorHandler, String filterTime);
 
 	/**
-	 * add job, default quartz stop
+	 * add job
 	 *
 	 * @param jobInfo
 	 * @return
@@ -36,7 +36,7 @@ public interface XxlJobService {
 	public ReturnT<String> add(XxlJobInfo jobInfo);
 
 	/**
-	 * update job, update quartz-cron if started
+	 * update job
 	 *
 	 * @param jobInfo
 	 * @return
@@ -44,15 +44,15 @@ public interface XxlJobService {
 	public ReturnT<String> update(XxlJobInfo jobInfo);
 
 	/**
-	 * remove job, unbind quartz
-	 *
+	 * remove job
+	 * 	 *
 	 * @param id
 	 * @return
 	 */
 	public ReturnT<String> remove(int id);
 
 	/**
-	 * start job, bind quartz
+	 * start job
 	 *
 	 * @param id
 	 * @return
@@ -60,7 +60,7 @@ public interface XxlJobService {
 	public ReturnT<String> start(int id);
 
 	/**
-	 * stop job, unbind quartz
+	 * stop job
 	 *
 	 * @param id
 	 * @return

+ 38 - 67
xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java

@@ -2,8 +2,8 @@ package com.xxl.job.admin.service.impl;
 
 import com.xxl.job.admin.core.model.XxlJobGroup;
 import com.xxl.job.admin.core.model.XxlJobInfo;
+import com.xxl.job.admin.core.cron.CronExpression;
 import com.xxl.job.admin.core.route.ExecutorRouteStrategyEnum;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
 import com.xxl.job.admin.core.util.I18nUtil;
 import com.xxl.job.admin.dao.XxlJobGroupDao;
 import com.xxl.job.admin.dao.XxlJobInfoDao;
@@ -14,14 +14,13 @@ import com.xxl.job.core.biz.model.ReturnT;
 import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
 import com.xxl.job.core.glue.GlueTypeEnum;
 import com.xxl.job.core.util.DateUtil;
-import org.quartz.CronExpression;
-import org.quartz.SchedulerException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
 import java.text.MessageFormat;
+import java.text.ParseException;
 import java.util.*;
 
 /**
@@ -48,13 +47,6 @@ public class XxlJobServiceImpl implements XxlJobService {
 		List<XxlJobInfo> list = xxlJobInfoDao.pageList(start, length, jobGroup, jobDesc, executorHandler);
 		int list_count = xxlJobInfoDao.pageListCount(start, length, jobGroup, jobDesc, executorHandler);
 		
-		// fill job info
-		if (list!=null && list.size()>0) {
-			for (XxlJobInfo jobInfo : list) {
-				XxlJobDynamicScheduler.fillJobInfo(jobInfo);
-			}
-		}
-		
 		// package result
 		Map<String, Object> maps = new HashMap<String, Object>();
 	    maps.put("recordsTotal", list_count);		// 总记录数
@@ -197,6 +189,15 @@ public class XxlJobServiceImpl implements XxlJobService {
 			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_id")+I18nUtil.getString("system_not_found")) );
 		}
 
+		// next trigger time
+		long nextTriggerTime = 0;
+		try {
+			nextTriggerTime = new CronExpression(jobInfo.getJobCron()).getNextValidTimeAfter(new Date()).getTime();
+		} catch (ParseException e) {
+			logger.error(e.getMessage(), e);
+			return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid")+" | "+ e.getMessage());
+		}
+
 		exists_jobInfo.setJobGroup(jobInfo.getJobGroup());
 		exists_jobInfo.setJobCron(jobInfo.getJobCron());
 		exists_jobInfo.setJobDesc(jobInfo.getJobDesc());
@@ -209,18 +210,10 @@ public class XxlJobServiceImpl implements XxlJobService {
 		exists_jobInfo.setExecutorTimeout(jobInfo.getExecutorTimeout());
 		exists_jobInfo.setExecutorFailRetryCount(jobInfo.getExecutorFailRetryCount());
 		exists_jobInfo.setChildJobId(jobInfo.getChildJobId());
+		exists_jobInfo.setTriggerNextTime(nextTriggerTime);
         xxlJobInfoDao.update(exists_jobInfo);
 
 
-		// update quartz-cron if started
-        try {
-			String qz_name = String.valueOf(exists_jobInfo.getId());
-            XxlJobDynamicScheduler.updateJobCron(qz_name, exists_jobInfo.getJobCron());
-        } catch (SchedulerException e) {
-            logger.error(e.getMessage(), e);
-			return ReturnT.FAIL;
-        }
-
 		return ReturnT.SUCCESS;
 	}
 
@@ -230,76 +223,54 @@ public class XxlJobServiceImpl implements XxlJobService {
 		if (xxlJobInfo == null) {
 			return ReturnT.SUCCESS;
 		}
-		String name = String.valueOf(xxlJobInfo.getId());
-
-		try {
-			// unbind quartz
-			XxlJobDynamicScheduler.removeJob(name);
-
-			xxlJobInfoDao.delete(id);
-			xxlJobLogDao.delete(id);
-			xxlJobLogGlueDao.deleteByJobId(id);
-			return ReturnT.SUCCESS;
-		} catch (SchedulerException e) {
-			logger.error(e.getMessage(), e);
-			return ReturnT.FAIL;
-		}
 
+		xxlJobInfoDao.delete(id);
+		xxlJobLogDao.delete(id);
+		xxlJobLogGlueDao.deleteByJobId(id);
+		return ReturnT.SUCCESS;
 	}
 
 	@Override
 	public ReturnT<String> start(int id) {
 		XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);
-		String name = String.valueOf(xxlJobInfo.getId());
-		String cronExpression = xxlJobInfo.getJobCron();
 
+		// next trigger time
+		long nextTriggerTime = 0;
 		try {
-			boolean ret = XxlJobDynamicScheduler.addJob(name, cronExpression);
-			return ret?ReturnT.SUCCESS:ReturnT.FAIL;
-		} catch (SchedulerException e) {
+			nextTriggerTime = new CronExpression(xxlJobInfo.getJobCron()).getNextValidTimeAfter(new Date()).getTime();
+		} catch (ParseException e) {
 			logger.error(e.getMessage(), e);
-			return ReturnT.FAIL;
+			return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid")+" | "+ e.getMessage());
 		}
+
+		xxlJobInfo.setTriggerStatus(1);
+		xxlJobInfo.setTriggerLastTime(0);
+		xxlJobInfo.setTriggerNextTime(nextTriggerTime);
+
+		xxlJobInfoDao.update(xxlJobInfo);
+		return ReturnT.SUCCESS;
 	}
 
 	@Override
 	public ReturnT<String> stop(int id) {
         XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);
-        String name = String.valueOf(xxlJobInfo.getId());
 
+		// next trigger time
+		long nextTriggerTime = 0;
 		try {
-			// bind quartz
-            boolean ret = XxlJobDynamicScheduler.removeJob(name);
-            return ret?ReturnT.SUCCESS:ReturnT.FAIL;
-		} catch (SchedulerException e) {
+			nextTriggerTime = new CronExpression(xxlJobInfo.getJobCron()).getNextValidTimeAfter(new Date()).getTime();
+		} catch (ParseException e) {
 			logger.error(e.getMessage(), e);
-			return ReturnT.FAIL;
+			return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid")+" | "+ e.getMessage());
 		}
-	}
 
-	/*@Override
-    public ReturnT<String> triggerJob(int id, int failRetryCount) {
+		xxlJobInfo.setTriggerStatus(0);
+		xxlJobInfo.setTriggerLastTime(0);
+		xxlJobInfo.setTriggerNextTime(nextTriggerTime);
 
-		JobTriggerPoolHelper.trigger(id, failRetryCount);
+		xxlJobInfoDao.update(xxlJobInfo);
 		return ReturnT.SUCCESS;
-
-        *//*XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);
-        if (xxlJobInfo == null) {
-        	return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_id")+I18nUtil.getString("system_unvalid")) );
-		}
-
-        String group = String.valueOf(xxlJobInfo.getJobGroup());
-        String name = String.valueOf(xxlJobInfo.getId());
-
-		try {
-			XxlJobDynamicScheduler.triggerJob(name, group);
-			return ReturnT.SUCCESS;
-		} catch (SchedulerException e) {
-			logger.error(e.getMessage(), e);
-			return new ReturnT<String>(ReturnT.FAIL_CODE, e.getMessage());
-		}*//*
-
-	}*/
+	}
 
 	@Override
 	public Map<String, Object> dashboardInfo() {

+ 6 - 6
xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobGroupMapper.xml

@@ -23,24 +23,24 @@
 
 	<select id="findAll" resultMap="XxlJobGroup">
 		SELECT <include refid="Base_Column_List" />
-		FROM XXL_JOB_QRTZ_TRIGGER_GROUP AS t
+		FROM XXL_JOB_GROUP AS t
 		ORDER BY t.order ASC
 	</select>
 
 	<select id="findByAddressType" parameterType="java.lang.Integer" resultMap="XxlJobGroup">
 		SELECT <include refid="Base_Column_List" />
-		FROM XXL_JOB_QRTZ_TRIGGER_GROUP AS t
+		FROM XXL_JOB_GROUP AS t
 		WHERE t.address_type = #{addressType}
 		ORDER BY t.order ASC
 	</select>
 
 	<insert id="save" parameterType="com.xxl.job.admin.core.model.XxlJobGroup" useGeneratedKeys="true" keyProperty="id" >
-		INSERT INTO XXL_JOB_QRTZ_TRIGGER_GROUP ( `app_name`, `title`, `order`, `address_type`, `address_list`)
+		INSERT INTO XXL_JOB_GROUP ( `app_name`, `title`, `order`, `address_type`, `address_list`)
 		values ( #{appName}, #{title}, #{order}, #{addressType}, #{addressList});
 	</insert>
 
 	<update id="update" parameterType="com.xxl.job.admin.core.model.XxlJobGroup" >
-		UPDATE XXL_JOB_QRTZ_TRIGGER_GROUP
+		UPDATE XXL_JOB_GROUP
 		SET `app_name` = #{appName},
 			`title` = #{title},
 			`order` = #{order},
@@ -50,13 +50,13 @@
 	</update>
 
 	<delete id="remove" parameterType="java.lang.Integer" >
-		DELETE FROM XXL_JOB_QRTZ_TRIGGER_GROUP
+		DELETE FROM XXL_JOB_GROUP
 		WHERE id = #{id}
 	</delete>
 
 	<select id="load" parameterType="java.lang.Integer" resultMap="XxlJobGroup">
 		SELECT <include refid="Base_Column_List" />
-		FROM XXL_JOB_QRTZ_TRIGGER_GROUP AS t
+		FROM XXL_JOB_GROUP AS t
 		WHERE t.id = #{id}
 	</select>
 

+ 44 - 12
xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml

@@ -29,6 +29,10 @@
 		<result column="glue_updatetime" property="glueUpdatetime" />
 
 		<result column="child_jobid" property="childJobId" />
+
+		<result column="trigger_status" property="triggerStatus" />
+		<result column="trigger_last_time" property="triggerLastTime" />
+		<result column="trigger_next_time" property="triggerNextTime" />
 	</resultMap>
 
 	<sql id="Base_Column_List">
@@ -50,12 +54,15 @@
 		t.glue_source,
 		t.glue_remark,
 		t.glue_updatetime,
-		t.child_jobid
+		t.child_jobid,
+		t.trigger_status,
+		t.trigger_last_time,
+		t.trigger_next_time
 	</sql>
 
 	<select id="pageList" parameterType="java.util.HashMap" resultMap="XxlJobInfo">
 		SELECT <include refid="Base_Column_List" />
-		FROM XXL_JOB_QRTZ_TRIGGER_INFO AS t
+		FROM XXL_JOB_INFO AS t
 		<trim prefix="WHERE" prefixOverrides="AND | OR" >
 			<if test="jobGroup gt 0">
 				AND t.job_group = #{jobGroup}
@@ -73,7 +80,7 @@
 
 	<select id="pageListCount" parameterType="java.util.HashMap" resultType="int">
 		SELECT count(1)
-		FROM XXL_JOB_QRTZ_TRIGGER_INFO AS t
+		FROM XXL_JOB_INFO AS t
 		<trim prefix="WHERE" prefixOverrides="AND | OR" >
 			<if test="jobGroup gt 0">
 				AND t.job_group = #{jobGroup}
@@ -88,7 +95,7 @@
 	</select>
 
 	<insert id="save" parameterType="com.xxl.job.admin.core.model.XxlJobInfo" useGeneratedKeys="true" keyProperty="id" >
-		INSERT INTO XXL_JOB_QRTZ_TRIGGER_INFO (
+		INSERT INTO XXL_JOB_INFO (
 			job_group,
 			job_cron,
 			job_desc,
@@ -106,7 +113,10 @@
 			glue_source,
 			glue_remark,
 			glue_updatetime,
-			child_jobid
+			child_jobid,
+			trigger_status,
+			trigger_last_time,
+			trigger_next_time
 		) VALUES (
 			#{jobGroup},
 			#{jobCron},
@@ -125,7 +135,10 @@
 			#{glueSource},
 			#{glueRemark},
 			NOW(),
-			#{childJobId}
+			#{childJobId},
+			#{triggerStatus},
+			#{triggerLastTime},
+			#{triggerNextTime}
 		);
 		<!--<selectKey resultType="java.lang.Integer" order="AFTER" keyProperty="id">
 			SELECT LAST_INSERT_ID()
@@ -135,12 +148,12 @@
 
 	<select id="loadById" parameterType="java.util.HashMap" resultMap="XxlJobInfo">
 		SELECT <include refid="Base_Column_List" />
-		FROM XXL_JOB_QRTZ_TRIGGER_INFO AS t
+		FROM XXL_JOB_INFO AS t
 		WHERE t.id = #{id}
 	</select>
 
 	<update id="update" parameterType="com.xxl.job.admin.core.model.XxlJobInfo" >
-		UPDATE XXL_JOB_QRTZ_TRIGGER_INFO
+		UPDATE XXL_JOB_INFO
 		SET
 			job_group = #{jobGroup},
 			job_cron = #{jobCron},
@@ -158,25 +171,44 @@
 			glue_source = #{glueSource},
 			glue_remark = #{glueRemark},
 			glue_updatetime = #{glueUpdatetime},
-			child_jobid = #{childJobId}
+			child_jobid = #{childJobId},
+			trigger_status = #{triggerStatus},
+			trigger_last_time = #{triggerLastTime},
+			trigger_next_time = #{triggerNextTime}
 		WHERE id = #{id}
 	</update>
 
 	<delete id="delete" parameterType="java.util.HashMap">
 		DELETE
-		FROM XXL_JOB_QRTZ_TRIGGER_INFO
+		FROM XXL_JOB_INFO
 		WHERE id = #{id}
 	</delete>
 
 	<select id="getJobsByGroup" parameterType="java.util.HashMap" resultMap="XxlJobInfo">
 		SELECT <include refid="Base_Column_List" />
-		FROM XXL_JOB_QRTZ_TRIGGER_INFO AS t
+		FROM XXL_JOB_INFO AS t
 		WHERE t.job_group = #{jobGroup}
 	</select>
 
 	<select id="findAllCount" resultType="int">
 		SELECT count(1)
-		FROM XXL_JOB_QRTZ_TRIGGER_INFO
+		FROM XXL_JOB_INFO
+	</select>
+
+
+	<select id="scheduleJobQuery" parameterType="java.util.HashMap" resultMap="XxlJobInfo">
+		SELECT <include refid="Base_Column_List" />
+		FROM XXL_JOB_INFO AS t
+		WHERE t.trigger_status = 1
+			and t.trigger_next_time<![CDATA[ < ]]> #{maxNextTime}
 	</select>
 
+	<update id="scheduleUpdate" parameterType="com.xxl.job.admin.core.model.XxlJobInfo"  >
+		UPDATE XXL_JOB_INFO
+		SET
+			trigger_last_time = #{triggerLastTime},
+			trigger_next_time = #{triggerNextTime}
+		WHERE id = #{id}
+	</update>
+
 </mapper>

+ 5 - 5
xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogGlueMapper.xml

@@ -24,7 +24,7 @@
 	</sql>
 	
 	<insert id="save" parameterType="com.xxl.job.admin.core.model.XxlJobLogGlue" useGeneratedKeys="true" keyProperty="id" >
-		INSERT INTO XXL_JOB_QRTZ_TRIGGER_LOGGLUE (
+		INSERT INTO XXL_JOB_LOGGLUE (
 			`job_id`,
 			`glue_type`,
 			`glue_source`,
@@ -46,16 +46,16 @@
 	
 	<select id="findByJobId" parameterType="java.lang.Integer" resultMap="XxlJobLogGlue">
 		SELECT <include refid="Base_Column_List" />
-		FROM XXL_JOB_QRTZ_TRIGGER_LOGGLUE AS t
+		FROM XXL_JOB_LOGGLUE AS t
 		WHERE t.job_id = #{jobId}
 		ORDER BY id DESC
 	</select>
 	
 	<delete id="removeOld" >
-		DELETE FROM XXL_JOB_QRTZ_TRIGGER_LOGGLUE
+		DELETE FROM XXL_JOB_LOGGLUE
 		WHERE id NOT in(
 			SELECT id FROM(
-				SELECT id FROM XXL_JOB_QRTZ_TRIGGER_LOGGLUE
+				SELECT id FROM XXL_JOB_LOGGLUE
 				WHERE `job_id` = #{jobId}
 				ORDER BY update_time desc
 				LIMIT 0, #{limit}
@@ -64,7 +64,7 @@
 	</delete>
 	
 	<delete id="deleteByJobId" parameterType="java.lang.Integer" >
-		DELETE FROM XXL_JOB_QRTZ_TRIGGER_LOGGLUE
+		DELETE FROM XXL_JOB_LOGGLUE
 		WHERE `job_id` = #{jobId}
 	</delete>
 	

+ 13 - 13
xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogMapper.xml

@@ -46,7 +46,7 @@
 	
 	<select id="pageList" resultMap="XxlJobLog">
 		SELECT <include refid="Base_Column_List" />
-		FROM XXL_JOB_QRTZ_TRIGGER_LOG AS t
+		FROM XXL_JOB_LOG AS t
 		<trim prefix="WHERE" prefixOverrides="AND | OR" >
 			<if test="jobId==0 and jobGroup gt 0">
 				AND t.job_group = #{jobGroup}
@@ -80,7 +80,7 @@
 	
 	<select id="pageListCount" resultType="int">
 		SELECT count(1)
-		FROM XXL_JOB_QRTZ_TRIGGER_LOG AS t
+		FROM XXL_JOB_LOG AS t
 		<trim prefix="WHERE" prefixOverrides="AND | OR" >
 			<if test="jobId==0 and jobGroup gt 0">
 				AND t.job_group = #{jobGroup}
@@ -112,13 +112,13 @@
 	
 	<select id="load" parameterType="java.lang.Integer" resultMap="XxlJobLog">
 		SELECT <include refid="Base_Column_List" />
-		FROM XXL_JOB_QRTZ_TRIGGER_LOG AS t
+		FROM XXL_JOB_LOG AS t
 		WHERE t.id = #{id}
 	</select>
 
 	
 	<insert id="save" parameterType="com.xxl.job.admin.core.model.XxlJobLog" useGeneratedKeys="true" keyProperty="id" >
-		INSERT INTO XXL_JOB_QRTZ_TRIGGER_LOG (
+		INSERT INTO XXL_JOB_LOG (
 			`job_group`,
 			`job_id`,
 			`trigger_time`,
@@ -137,7 +137,7 @@
 	</insert>
 
 	<update id="updateTriggerInfo" >
-		UPDATE XXL_JOB_QRTZ_TRIGGER_LOG
+		UPDATE XXL_JOB_LOG
 		SET
 			`trigger_time`= #{triggerTime},
 			`trigger_code`= #{triggerCode},
@@ -151,7 +151,7 @@
 	</update>
 
 	<update id="updateHandleInfo">
-		UPDATE XXL_JOB_QRTZ_TRIGGER_LOG
+		UPDATE XXL_JOB_LOG
 		SET 
 			`handle_time`= #{handleTime}, 
 			`handle_code`= #{handleCode},
@@ -160,13 +160,13 @@
 	</update>
 	
 	<delete id="delete" >
-		delete from XXL_JOB_QRTZ_TRIGGER_LOG
+		delete from XXL_JOB_LOG
 		WHERE job_id = #{jobId}
 	</delete>
 
 	<select id="triggerCountByHandleCode" resultType="int" >
 		SELECT count(1)
-		FROM XXL_JOB_QRTZ_TRIGGER_LOG AS t
+		FROM XXL_JOB_LOG AS t
 		<trim prefix="WHERE" prefixOverrides="AND | OR" >
 			<if test="handleCode gt 0">
 				AND t.handle_code = #{handleCode}
@@ -180,13 +180,13 @@
 			COUNT(handle_code) triggerDayCount,
 			SUM(CASE WHEN (trigger_code in (0, 200) and handle_code = 0) then 1 else 0 end) as triggerDayCountRunning,
 			SUM(CASE WHEN handle_code = 200 then 1 else 0 end) as triggerDayCountSuc
-		FROM XXL_JOB_QRTZ_TRIGGER_LOG
+		FROM XXL_JOB_LOG
 		WHERE trigger_time BETWEEN #{from} and #{to}
 		GROUP BY triggerDay;
     </select>
 
 	<delete id="clearLog" >
-		delete from XXL_JOB_QRTZ_TRIGGER_LOG
+		delete from XXL_JOB_LOG
 		<trim prefix="WHERE" prefixOverrides="AND | OR" >
 			<if test="jobGroup gt 0">
 				AND job_group = #{jobGroup}
@@ -200,7 +200,7 @@
 			<if test="clearBeforeNum gt 0">
 				AND id NOT in(
 					SELECT id FROM(
-						SELECT id FROM XXL_JOB_QRTZ_TRIGGER_LOG AS t
+						SELECT id FROM XXL_JOB_LOG AS t
 						<trim prefix="WHERE" prefixOverrides="AND | OR" >
 							<if test="jobGroup gt 0">
 								AND t.job_group = #{jobGroup}
@@ -218,7 +218,7 @@
 	</delete>
 
 	<select id="findFailJobLogIds" resultType="int" >
-		SELECT id FROM `XXL_JOB_QRTZ_TRIGGER_LOG`
+		SELECT id FROM `XXL_JOB_LOG`
 		WHERE !(
 			(trigger_code in (0, 200) and handle_code = 0)
 			OR
@@ -229,7 +229,7 @@
 	</select>
 
 	<update id="updateAlarmStatus" >
-		UPDATE XXL_JOB_QRTZ_TRIGGER_LOG
+		UPDATE XXL_JOB_LOG
 		SET
 			`alarm_status` = #{newAlarmStatus}
 		WHERE `id`= #{logId} AND `alarm_status` = #{oldAlarmStatus}

+ 5 - 5
xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobRegistryMapper.xml

@@ -20,18 +20,18 @@
 	</sql>
 	
 	<delete id="removeDead" parameterType="java.lang.Integer" >
-		DELETE FROM XXL_JOB_QRTZ_TRIGGER_REGISTRY
+		DELETE FROM XXL_JOB_REGISTRY
 		WHERE update_time <![CDATA[ < ]]> DATE_ADD(NOW(),INTERVAL -#{timeout} SECOND)
 	</delete>
 
 	<select id="findAll" parameterType="java.lang.Integer" resultMap="XxlJobRegistry">
 		SELECT <include refid="Base_Column_List" />
-		FROM XXL_JOB_QRTZ_TRIGGER_REGISTRY AS t
+		FROM XXL_JOB_REGISTRY AS t
 		WHERE t.update_time <![CDATA[ > ]]> DATE_ADD(NOW(),INTERVAL -#{timeout} SECOND)
 	</select>
 
     <update id="registryUpdate" >
-        UPDATE XXL_JOB_QRTZ_TRIGGER_REGISTRY
+        UPDATE XXL_JOB_REGISTRY
         SET `update_time` = NOW()
         WHERE `registry_group` = #{registryGroup}
           AND `registry_key` = #{registryKey}
@@ -39,12 +39,12 @@
     </update>
 
     <insert id="registrySave" >
-        INSERT INTO XXL_JOB_QRTZ_TRIGGER_REGISTRY( `registry_group` , `registry_key` , `registry_value`, `update_time`)
+        INSERT INTO XXL_JOB_REGISTRY( `registry_group` , `registry_key` , `registry_value`, `update_time`)
         VALUES( #{registryGroup}  , #{registryKey} , #{registryValue}, NOW())
     </insert>
 
 	<delete id="registryDelete" >
-		DELETE FROM XXL_JOB_QRTZ_TRIGGER_REGISTRY
+		DELETE FROM XXL_JOB_REGISTRY
 		WHERE registry_group = #{registryGroup}
 			AND registry_key = #{registryKey}
 			AND registry_value = #{registryValue}

+ 6 - 6
xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobUserMapper.xml

@@ -21,7 +21,7 @@
 
 	<select id="pageList" parameterType="java.util.HashMap" resultMap="XxlJobUser">
 		SELECT <include refid="Base_Column_List" />
-		FROM XXL_JOB_QRTZ_USER AS t
+		FROM XXL_JOB_USER AS t
 		<trim prefix="WHERE" prefixOverrides="AND | OR" >
 			<if test="username != null and username != ''">
 				AND t.username like CONCAT(CONCAT('%', #{username}), '%')
@@ -36,7 +36,7 @@
 
 	<select id="pageListCount" parameterType="java.util.HashMap" resultType="int">
 		SELECT count(1)
-		FROM XXL_JOB_QRTZ_USER AS t
+		FROM XXL_JOB_USER AS t
 		<trim prefix="WHERE" prefixOverrides="AND | OR" >
 			<if test="username != null and username != ''">
 				AND t.username like CONCAT(CONCAT('%', #{username}), '%')
@@ -49,12 +49,12 @@
 
 	<select id="loadByUserName" parameterType="java.util.HashMap" resultMap="XxlJobUser">
 		SELECT <include refid="Base_Column_List" />
-		FROM XXL_JOB_QRTZ_USER AS t
+		FROM XXL_JOB_USER AS t
 		WHERE t.username = #{username}
 	</select>
 
 	<insert id="save" parameterType="com.xxl.job.admin.core.model.XxlJobUser" useGeneratedKeys="true" keyProperty="id" >
-		INSERT INTO XXL_JOB_QRTZ_USER (
+		INSERT INTO XXL_JOB_USER (
 			username,
 			password,
 			role,
@@ -68,7 +68,7 @@
 	</insert>
 
 	<update id="update" parameterType="com.xxl.job.admin.core.model.XxlJobUser" >
-		UPDATE XXL_JOB_QRTZ_USER
+		UPDATE XXL_JOB_USER
 		SET
 			<if test="password != null and password != ''">
 				password = #{password},
@@ -80,7 +80,7 @@
 
 	<delete id="delete" parameterType="java.util.HashMap">
 		DELETE
-		FROM XXL_JOB_QRTZ_USER
+		FROM XXL_JOB_USER
 		WHERE id = #{id}
 	</delete>
 

+ 0 - 29
xxl-job-admin/src/main/resources/quartz.properties

@@ -1,29 +0,0 @@
-# Default Properties file for use by StdSchedulerFactory
-# to create a Quartz Scheduler Instance, if a different
-# properties file is not explicitly specified.
-#
-
-org.quartz.scheduler.instanceName: DefaultQuartzScheduler
-org.quartz.scheduler.instanceId: AUTO
-org.quartz.scheduler.rmi.export: false
-org.quartz.scheduler.rmi.proxy: false
-org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
-
-#org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
-#org.quartz.threadPool.threadCount: 5
-#org.quartz.threadPool.threadPriority: 5
-#org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
-
-org.quartz.jobStore.misfireThreshold: 60000
-org.quartz.jobStore.maxMisfiresToHandleAtATime: 1
-
-#org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
-
-# for async trigger
-org.quartz.threadPool.class: com.xxl.job.admin.core.quartz.XxlJobThreadPool
-
-# for cluster
-org.quartz.jobStore.tablePrefix: XXL_JOB_QRTZ_
-org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
-org.quartz.jobStore.isClustered: true
-org.quartz.jobStore.clusterCheckinInterval: 5000

+ 8 - 18
xxl-job-admin/src/main/resources/static/js/jobinfo.index.1.js

@@ -83,22 +83,16 @@ $(function() {
 	                { "data": 'author', "visible" : true, "width":'10%'},
 	                { "data": 'alarmEmail', "visible" : false},
 	                { 
-	                	"data": 'jobStatus',
+	                	"data": 'triggerStatus',
 						"width":'10%',
 	                	"visible" : true,
 	                	"render": function ( data, type, row ) {
-
                             // status
-	                		if (data && data != 'NONE') {
-                                if ('NORMAL' == data) {
-                                    return '<small class="label label-success" ><i class="fa fa-clock-o"></i>RUNNING</small>';
-                                } else {
-                                    return '<small class="label label-warning" >ERROR('+ data +')</small>';
-                                }
-							} else {
+                            if (1 == data) {
+                                return '<small class="label label-success" ><i class="fa fa-clock-o"></i>RUNNING</small>';
+                            } else {
                                 return '<small class="label label-default" ><i class="fa fa-clock-o"></i>STOP</small>';
-							}
-
+                            }
 	                		return data;
 	                	}
 	                },
@@ -109,15 +103,11 @@ $(function() {
 	                		return function(){
 	                			// status
 	                			var start_stop = "";
-                                if (row.jobStatus && row.jobStatus != 'NONE') {
-                                    if ('NORMAL' == row.jobStatus) {
-                                        start_stop = '<button class="btn btn-primary btn-xs job_operate" _type="job_pause" type="button">'+ I18n.jobinfo_opt_stop +'</button>  ';
-                                    } else {
-                                        start_stop = '<button class="btn btn-primary btn-xs job_operate" _type="job_pause" type="button">'+ I18n.jobinfo_opt_stop +'</button>  ';
-                                    }
+                                if (1 == row.triggerStatus ) {
+                                    start_stop = '<button class="btn btn-primary btn-xs job_operate" _type="job_pause" type="button">'+ I18n.jobinfo_opt_stop +'</button>  ';
                                 } else {
                                     start_stop = '<button class="btn btn-primary btn-xs job_operate" _type="job_resume" type="button">'+ I18n.jobinfo_opt_start +'</button>  ';
-                                }
+								}
 
 	                			// log url
 	                			var logUrl = base_url +'/joblog?jobId='+ row.id;

+ 1 - 1
xxl-job-admin/src/main/resources/templates/jobinfo/jobinfo.index.ftl

@@ -75,7 +75,7 @@
 					                  	<th name="updateTime" >updateTime</th>
 					                  	<th name="author" >${I18n.jobinfo_field_author}</th>
 					                  	<th name="alarmEmail" >${I18n.jobinfo_field_alarmemail}</th>
-					                  	<th name="jobStatus" >${I18n.system_status}</th>
+					                  	<th name="triggerStatus" >${I18n.system_status}</th>
 					                  	<th>${I18n.system_opt}</th>
 					                </tr>
 				                </thead>