From e01d2bc9b5096245b76828ab8ab198f242a38659 Mon Sep 17 00:00:00 2001 From: xuxueli <931591021@qq.com> Date: Wed, 22 May 2019 12:01:48 +0800 Subject: [PATCH] =?UTF-8?q?-=201=E3=80=81=E8=87=AA=E7=A0=94=E8=B0=83?= =?UTF-8?q?=E5=BA=A6=E7=BB=84=E4=BB=B6=EF=BC=8C=E7=A7=BB=E9=99=A4quartz?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=EF=BC=9A=E4=B8=80=E6=96=B9=E9=9D=A2=E6=98=AF?= =?UTF-8?q?=E4=B8=BA=E4=BA=86=E7=B2=BE=E7=AE=80=E7=B3=BB=E7=BB=9F=E9=99=8D?= =?UTF-8?q?=E4=BD=8E=E5=86=97=E4=BD=99=E4=BE=9D=E8=B5=96=EF=BC=8C=E5=8F=A6?= =?UTF-8?q?=E4=B8=80=E6=96=B9=E9=9D=A2=E6=98=AF=E4=B8=BA=E4=BA=86=E6=8F=90?= =?UTF-8?q?=E4=BE=9B=E7=B3=BB=E7=BB=9F=E7=9A=84=E5=8F=AF=E6=8E=A7=E5=BA=A6?= =?UTF-8?q?=E4=B8=8E=E7=A8=B3=E5=AE=9A=E6=80=A7=EF=BC=9B=20=20=20=20=20-?= =?UTF-8?q?=20=E8=A7=A6=E5=8F=91=EF=BC=9A=E5=8D=95=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E5=91=A8=E6=9C=9F=E6=80=A7=E8=A7=A6=E5=8F=91=EF=BC=8C=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E4=BA=8B=E4=BB=B6=E5=A6=82delayqueue=EF=BC=9B=20=20?= =?UTF-8?q?=20=20=20-=20=E8=B0=83=E5=BA=A6=EF=BC=9A=E9=9B=86=E7=BE=A4?= =?UTF-8?q?=E7=AB=9E=E4=BA=89=EF=BC=8C=E8=B4=9F=E8=BD=BD=E6=96=B9=E5=BC=8F?= =?UTF-8?q?=E5=8D=8F=E5=90=8C=E5=A4=84=E7=90=86=EF=BC=8C=E9=94=81=E7=AB=9E?= =?UTF-8?q?=E4=BA=89-=E6=9B=B4=E6=96=B0=E8=A7=A6=E5=8F=91=E4=BF=A1?= =?UTF-8?q?=E6=81=AF-=E6=8E=A8=E9=80=81=E6=97=B6=E9=97=B4=E8=BD=AE-?= =?UTF-8?q?=E9=94=81=E9=87=8A=E6=94=BE-=E9=94=81=E7=AB=9E=E4=BA=89?= =?UTF-8?q?=EF=BC=9B=20-=202=E3=80=81=E5=BA=95=E5=B1=82=E8=A1=A8=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E9=87=8D=E6=9E=84=EF=BC=9A=E7=A7=BB=E9=99=A411?= =?UTF-8?q?=E5=BC=A0quartz=E7=9B=B8=E5=85=B3=E8=A1=A8=EF=BC=8C=E5=B9=B6?= =?UTF-8?q?=E5=AF=B9=E7=8E=B0=E6=9C=89=E8=A1=A8=E7=BB=93=E6=9E=84=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=A2=B3=E7=90=86=EF=BC=9B=20-=203=E3=80=81=E5=BA=95?= =?UTF-8?q?=E5=B1=82=E7=BA=BF=E7=A8=8B=E6=A8=A1=E5=9E=8B=E9=87=8D=E6=9E=84?= =?UTF-8?q?=EF=BC=9A=E7=A7=BB=E9=99=A4Quartz=E7=BA=BF=E7=A8=8B=E6=B1=A0?= =?UTF-8?q?=EF=BC=8C=E9=99=8D=E4=BD=8E=E7=B3=BB=E7=BB=9F=E7=BA=BF=E7=A8=8B?= =?UTF-8?q?=E4=B8=8E=E5=86=85=E5=AD=98=E5=BC=80=E9=94=80=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- doc/XXL-JOB官方文档.md | 94 +- doc/db/tables_mysql(备份,请忽略).sql | 168 -- doc/db/tables_xxl_job.sql | 173 +- pom.xml | 1 - xxl-job-admin/pom.xml | 7 - .../admin/controller/JobApiController.java | 4 +- .../admin/controller/JobLogController.java | 6 +- .../admin/core/conf/XxlJobAdminConfig.java | 7 + .../conf/XxlJobDynamicSchedulerConfig.java | 43 - .../job/admin/core/conf/XxlJobScheduler.java | 147 ++ .../job/admin/core/cron/CronExpression.java | 1668 +++++++++++++++++ .../admin/core/jobbean/RemoteHttpJobBean.java | 33 - .../xxl/job/admin/core/model/XxlJobInfo.java | 36 +- .../job/admin/core/old/RemoteHttpJobBean.java | 32 + .../core/old/XxlJobDynamicScheduler.java | 413 ++++ .../job/admin/core/old/XxlJobThreadPool.java | 58 + .../admin/core/quartz/XxlJobThreadPool.java | 58 - .../route/strategy/ExecutorRouteBusyover.java | 4 +- .../route/strategy/ExecutorRouteFailover.java | 4 +- .../core/schedule/XxlJobDynamicScheduler.java | 413 ---- .../admin/core/thread/JobScheduleHelper.java | 218 +++ .../job/admin/core/trigger/XxlJobTrigger.java | 4 +- .../com/xxl/job/admin/dao/XxlJobInfoDao.java | 7 +- .../xxl/job/admin/service/XxlJobService.java | 12 +- .../admin/service/impl/XxlJobServiceImpl.java | 105 +- .../mybatis-mapper/XxlJobGroupMapper.xml | 12 +- .../mybatis-mapper/XxlJobInfoMapper.xml | 56 +- .../mybatis-mapper/XxlJobLogGlueMapper.xml | 10 +- .../mybatis-mapper/XxlJobLogMapper.xml | 26 +- .../mybatis-mapper/XxlJobRegistryMapper.xml | 10 +- .../mybatis-mapper/XxlJobUserMapper.xml | 12 +- .../src/main/resources/quartz.properties | 29 - .../resources/static/js/jobinfo.index.1.js | 26 +- .../templates/jobinfo/jobinfo.index.ftl | 2 +- 35 files changed, 2770 insertions(+), 1130 deletions(-) delete mode 100644 doc/db/tables_mysql(备份,请忽略).sql delete mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java delete mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java delete mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java delete mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java create mode 100644 xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java delete mode 100644 xxl-job-admin/src/main/resources/quartz.properties diff --git a/README.md b/README.md index f7ae0739..fff6cf8d 100644 --- a/README.md +++ b/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、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务; diff --git a/doc/XXL-JOB官方文档.md b/doc/XXL-JOB官方文档.md index 5b544d7d..35da6ac8 100644 --- a/doc/XXL-JOB官方文档.md +++ b/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 diff --git a/doc/db/tables_mysql(备份,请忽略).sql b/doc/db/tables_mysql(备份,请忽略).sql deleted file mode 100644 index e45e435d..00000000 --- a/doc/db/tables_mysql(备份,请忽略).sql +++ /dev/null @@ -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; diff --git a/doc/db/tables_xxl_job.sql b/doc/db/tables_xxl_job.sql index 40be5918..a3363778 100644 --- a/doc/db/tables_xxl_job.sql +++ b/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; diff --git a/pom.xml b/pom.xml index 28970d85..d07a1aac 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,6 @@ 1.3 2.5.6 - 2.3.1 3.0.1 3.1.0 diff --git a/xxl-job-admin/pom.xml b/xxl-job-admin/pom.xml index 7c201ab5..78341a6e 100644 --- a/xxl-job-admin/pom.xml +++ b/xxl-job-admin/pom.xml @@ -60,13 +60,6 @@ ${mysql-connector-java.version} - - - org.quartz-scheduler - quartz - ${quartz.version} - - com.xuxueli diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java index ee301810..2a6c738f 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java +++ b/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); } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java index b6e40b07..97b21ddd 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java +++ b/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 logDetailCat(String executorAddress, long triggerTime, int logId, int fromLineNum){ try { - ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(executorAddress); + ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(executorAddress); ReturnT logResult = executorBiz.log(triggerTime, logId, fromLineNum); // is end @@ -170,7 +170,7 @@ public class JobLogController { // request of kill ReturnT 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); diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java index 8557233e..b9547718 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java +++ b/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; + } + } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java deleted file mode 100644 index 0d802bca..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java +++ /dev/null @@ -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; - } - -} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java new file mode 100644 index 00000000..e4ef3403 --- /dev/null +++ b/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 executorBizRepository = new ConcurrentHashMap(); + 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; + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java new file mode 100644 index 00000000..d0f7f38d --- /dev/null +++ b/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 + * "At 8:00am every Monday through Friday" or "At 1:30am every + * last Friday of the month". + *

+ * Cron expressions are comprised of 6 required fields and one optional field + * separated by white space. The fields respectively are described as follows: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Field Name Allowed Values Allowed Special Characters
Seconds  + * 0-59  + * , - * /
Minutes  + * 0-59  + * , - * /
Hours  + * 0-23  + * , - * /
Day-of-month  + * 1-31  + * , - * ? / L W
Month  + * 0-11 or JAN-DEC  + * , - * /
Day-of-Week  + * 1-7 or SUN-SAT  + * , - * ? / L #
Year (Optional)  + * empty, 1970-2199  + * , - * /
+ *

+ * The '*' character is used to specify all values. For example, "*" + * in the minute field means "every minute". + *

+ * 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. + *

+ * The '-' character is used to specify ranges For example "10-12" in + * the hour field means "the hours 10, 11 and 12". + *

+ * The ',' character is used to specify additional values. For example + * "MON,WED,FRI" in the day-of-week field means "the days Monday, + * Wednesday, and Friday". + *

+ * The '/' character is used to specify increments. For example "0/15" + * in the seconds field means "the seconds 0, 15, 30, and 45". And + * "5/15" in the seconds field means "the seconds 5, 20, 35, and + * 50". 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 "/" character simply helps you turn + * on every "nth" value in the given set. Thus "7/6" in the + * month field only turns on month "7", it does NOT mean every 6th + * month, please note that subtlety. + *

+ * The 'L' character is allowed for the day-of-month and day-of-week fields. + * This character is short-hand for "last", but it has different + * meaning in each of the two fields. For example, the value "L" in + * the day-of-month field means "the last day of the month" - 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 "7" or + * "SAT". But if used in the day-of-week field after another value, it + * means "the last xxx day of the month" - for example "6L" + * means "the last friday of the month". 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. When using the 'L' option, it is important not to + * specify lists, or ranges of values, as you'll get confusing/unexpected results. + *

+ * 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 "15W" as the value for the + * day-of-month field, the meaning is: "the nearest weekday to the 15th of + * the month". 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 "1W" 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. + *

+ * The 'L' and 'W' characters can also be combined for the day-of-month + * expression to yield 'LW', which translates to "last weekday of the + * month". + *

+ * The '#' character is allowed for the day-of-week field. This character is + * used to specify "the nth" XXX day of the month. For example, the + * value of "6#3" in the day-of-week field means the third Friday of + * the month (day 6 = Friday and "#3" = the 3rd one in the month). + * Other examples: "2#1" = the first Monday of the month and + * "4#5" = the fifth Wednesday of the month. Note that if you specify + * "#5" 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 ("3#1,6#3" is + * not valid, since there are two expressions). + *

+ * + *

+ * The legal characters and the names of months and days of the week are not + * case sensitive. + * + *

+ * NOTES: + *

    + *
  • 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). + *
  • + *
  • 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".
  • + *
+ *

+ * + * + * @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 monthMap = new HashMap(20); + protected static final Map dayMap = new HashMap(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 seconds; + protected transient TreeSet minutes; + protected transient TreeSet hours; + protected transient TreeSet daysOfMonth; + protected transient TreeSet months; + protected transient TreeSet daysOfWeek; + protected transient TreeSet 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 CronExpression 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 + * CronExpression + */ + 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 after 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 after the given date/time which does + * not 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 CronExpression + * will be resolved. + */ + public TimeZone getTimeZone() { + if (timeZone == null) { + timeZone = TimeZone.getDefault(); + } + + return timeZone; + } + + /** + * Sets the time zone for which this CronExpression + * will be resolved. + */ + public void setTimeZone(TimeZone timeZone) { + this.timeZone = timeZone; + } + + /** + * Returns the string representation of the CronExpression + * + * @return a string representation of the CronExpression + */ + @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(); + } + if (minutes == null) { + minutes = new TreeSet(); + } + if (hours == null) { + hours = new TreeSet(); + } + if (daysOfMonth == null) { + daysOfMonth = new TreeSet(); + } + if (months == null) { + months = new TreeSet(); + } + if (daysOfWeek == null) { + daysOfWeek = new TreeSet(); + } + if (years == null) { + years = new TreeSet(); + } + + 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 dow = getSet(DAY_OF_WEEK); + TreeSet 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 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 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 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 set) { + + if (set.contains(NO_SPEC)) { + return "?"; + } + if (set.contains(ALL_SPEC)) { + return "*"; + } + + StringBuilder buf = new StringBuilder(); + + Iterator 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 list) { + + if (list.contains(NO_SPEC)) { + return "?"; + } + if (list.contains(ALL_SPEC)) { + return "*"; + } + + StringBuilder buf = new StringBuilder(); + + Iterator 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 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 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 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 CronExpression matches. + */ + public Date getTimeBefore(Date endTime) { + // FUTURE_TODO: implement QUARTZ-423 + return null; + } + + /** + * NOT YET IMPLEMENTED: Returns the final time that the + * CronExpression 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; +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java deleted file mode 100644 index 0dd32594..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java +++ /dev/null @@ -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); - } - -} \ No newline at end of file diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java index 905fb863..1e4a74b1 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java +++ b/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; + } } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java new file mode 100644 index 00000000..bd640113 --- /dev/null +++ b/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()); +// +// +// } +// +//} \ No newline at end of file diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java new file mode 100644 index 00000000..1e62aa19 --- /dev/null +++ b/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 executorBizRepository = new ConcurrentHashMap(); +// 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 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 triggerSet = new HashSet(); +// 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> finaAllJobList(){ +// List> jobList = new ArrayList>(); +// +// try { +// if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) { +// return null; +// } +// String groupName = scheduler.getJobGroupNames().get(0); +// Set 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 jobMap = new HashMap(); +// 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; +// }*/ +// +//} \ No newline at end of file diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java new file mode 100644 index 00000000..ad074307 --- /dev/null +++ b/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) { +// // +// } +// +//} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java deleted file mode 100644 index 07d44f1c..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java +++ /dev/null @@ -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) { - // - } - -} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java index 40d8373f..992b4ceb 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java +++ b/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 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); diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java index 63428094..26aaa067 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java +++ b/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 beatResult = null; try { - ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address); + ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address); beatResult = executorBiz.beat(); } catch (Exception e) { logger.error(e.getMessage(), e); diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java deleted file mode 100644 index f18d9e42..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java +++ /dev/null @@ -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 executorBizRepository = new ConcurrentHashMap(); - 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 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 triggerSet = new HashSet(); - 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> finaAllJobList(){ - List> jobList = new ArrayList>(); - - try { - if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) { - return null; - } - String groupName = scheduler.getJobGroupNames().get(0); - Set 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 jobMap = new HashMap(); - 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; - }*/ - -} \ No newline at end of file diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java new file mode 100644 index 00000000..cded7d5a --- /dev/null +++ b/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> 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 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 ringItemData = ringData.get(second); + if (ringItemData == null) { + ringItemData = new ArrayList(); + 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 ringItemData = new ArrayList<>(); + int nowSecond = (int)((System.currentTimeMillis()/1000)%60); // 避免处理耗时太长,跨过刻度; + if (lastSecond == -1) { + if (ringData.containsKey(nowSecond)) { + List 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 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); + } + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java index 9dd63082..7b3971c7 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java +++ b/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 runExecutor(TriggerParam triggerParam, String address){ ReturnT 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); diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java index 338ab433..3cb89ad2 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java +++ b/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 scheduleJobQuery(@Param("maxNextTime") long maxNextTime); + + public int scheduleUpdate(XxlJobInfo xxlJobInfo); + + } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java index fbcc3608..e8ef4329 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java @@ -28,7 +28,7 @@ public interface XxlJobService { public Map 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 add(XxlJobInfo jobInfo); /** - * update job, update quartz-cron if started + * update job * * @param jobInfo * @return @@ -44,15 +44,15 @@ public interface XxlJobService { public ReturnT update(XxlJobInfo jobInfo); /** - * remove job, unbind quartz - * + * remove job + * * * @param id * @return */ public ReturnT remove(int id); /** - * start job, bind quartz + * start job * * @param id * @return @@ -60,7 +60,7 @@ public interface XxlJobService { public ReturnT start(int id); /** - * stop job, unbind quartz + * stop job * * @param id * @return diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java index 0edab359..ba40bd8d 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java +++ b/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 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 maps = new HashMap(); maps.put("recordsTotal", list_count); // 总记录数 @@ -197,6 +189,15 @@ public class XxlJobServiceImpl implements XxlJobService { return new ReturnT(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(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 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(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 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(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid")+" | "+ e.getMessage()); } - } - /*@Override - public ReturnT 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(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(ReturnT.FAIL_CODE, e.getMessage()); - }*//* - - }*/ + } @Override public Map dashboardInfo() { diff --git a/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobGroupMapper.xml b/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobGroupMapper.xml index 78881a3a..f8ff78e3 100644 --- a/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobGroupMapper.xml +++ b/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobGroupMapper.xml @@ -23,24 +23,24 @@ - 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}); - UPDATE XXL_JOB_QRTZ_TRIGGER_GROUP + UPDATE XXL_JOB_GROUP SET `app_name` = #{appName}, `title` = #{title}, `order` = #{order}, @@ -50,13 +50,13 @@ - DELETE FROM XXL_JOB_QRTZ_TRIGGER_GROUP + DELETE FROM XXL_JOB_GROUP WHERE id = #{id} diff --git a/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml b/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml index 3b3b9cec..3380b251 100644 --- a/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml +++ b/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml @@ -29,6 +29,10 @@ + + + + @@ -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 SELECT count(1) - FROM XXL_JOB_QRTZ_TRIGGER_INFO AS t + FROM XXL_JOB_INFO AS t AND t.job_group = #{jobGroup} @@ -88,7 +95,7 @@ - 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} );