diff --git a/README.md b/README.md index 37910af..00b7db7 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,9 @@ Cronut supports **in-memory** scheduling of jobs within a single JVM. JDBC and d * [Jobs](#jobs) + [Job example](#job-example) * [Triggers](#triggers) - - [`cronut.trigger/cron`: Simple Cron Scheduling](#cronuttriggercron-simple-cron-scheduling) - - [`cronut.trigger/interval`: Simple Interval Scheduling](#cronuttriggerinterval-simple-interval-scheduling) - - [`cronut.trigger/builder`: Full trigger definition](#cronuttriggerbuilder-full-trigger-definition) + - [`cronut.trigger/cron`: Simple Cron Scheduling](#cronuttriggercron-simple-cron-scheduling) + - [`cronut.trigger/interval`: Simple Interval Scheduling](#cronuttriggerinterval-simple-interval-scheduling) + - [`cronut.trigger/builder`: Full trigger definition](#cronuttriggerbuilder-full-trigger-definition) * [Concurrent execution](#concurrent-execution) + [Global concurrent execution](#global-concurrent-execution) + [Job-specific concurrent execution](#job-specific-concurrent-execution) @@ -47,12 +47,12 @@ Cronut provides access to the Quartz Scheduler, exposed via the `cronut/schedule Create a scheduler with the following configuration: -1. `:concurrent-execution-disallowed?`: (optional, default false) - run all jobs with @DisableConcurrentExecution -2. `:update-check?`: (optional, default false) - check for Quartz updates on system startup. +1. `:concurrent-execution-disallowed?`: run all jobs with `@DisableConcurrentExecution` +2. `:update-check?`: check for Quartz updates on system startup. ````clojure -(cronut/scheduler {:concurrent-execution-disallowed? true - :update-check? false}) +(cronut/scheduler {:concurrent-execution-disallowed? true ;; default false + :update-check? false}) ;; default false ```` ### Scheduler lifecycle @@ -84,12 +84,8 @@ To schedule jobs, you can Each cronut job must implement the `org.quartz.Job` interface. -The expectation being that every job will reify that interface either directly via `reify` or by returning a `defrecord` -that implements the interface. - -Cronut supports further Quartz configuration of jobs (identity, description, recovery, and durability) by expecting -those values to be assoc'd onto your job. You do not have to set them (in fact in most cases you can likely ignore -them), however if you do want that control you will likely use the `defrecord` approach as opposed to `reify`. +Cronut supports further Quartz configuration of jobs (identity, description, recovery, and durability) by passing an +`opts` map when scheduling your job. Concurrent execution can be controlled on a per-job bases with the `disallow-concurrent-execution?` flag. @@ -104,22 +100,31 @@ Concurrent execution can be controlled on a per-job bases with the `disallow-con (let [scheduler (cronut/scheduler {:concurrent-execution-disallowed? true :update-check? false}) - defrecord-job (map->TestDefrecordJobImpl {:identity ["name1" "group2"] - :description "test job" - :recover? true - :durable? false}) + defrecord-job (map->TestDefrecordJobImpl {}) reify-job (reify Job (execute [_this _job-context] (let [rand-id (str (UUID/randomUUID))] (log/info rand-id "Reified Impl"))))] - (cronut/schedule-job scheduler (trigger/interval 1000) defrecord-job) + (cronut/schedule-job scheduler + (trigger/interval 1000) + defrecord-job + {:name "name1" + :group "group1" + :description "test job 1" + :recover? true + :durable? false}) (cronut/schedule-job scheduler (trigger/builder {:type :cron :cron "*/5 * * * * ?" :misfire :do-nothing}) - reify-job)) + reify-job + {:name "name2" + :group "group1" + :description "test job 2" + :recover? false + :durable? true})) ```` ## Triggers @@ -153,10 +158,11 @@ The `cronut.trigger/builder` function supports the full set of Quartz configurat ````clojure ;; interval -(cronut.trigger/builder {:type :simple +(cronut.trigger/builder {:name "trigger1" + :group "group3" + :type :simple :interval 3000 :repeat :forever - :identity ["trigger-two" "test"] :description "sample simple trigger" :start #inst "2019-01-01T00:00:00.000-00:00" :end #inst "2019-02-01T00:00:00.000-00:00" @@ -164,9 +170,10 @@ The `cronut.trigger/builder` function supports the full set of Quartz configurat :priority 5}) ;;cron -(cronut.trigger/builder {:type :cron +(cronut.trigger/builder {:name "trigger2" + :group "group3" + :type :cron :cron "*/6 * * * * ?" - :identity ["trigger-five" "test"] :description "sample cron trigger" :start #inst "2018-01-01T00:00:00.000-00:00" :end #inst "2029-02-01T00:00:00.000-00:00" @@ -207,7 +214,7 @@ See: integration test source: [test/cronut/integration-test.clj](test/cronut/int (:import (java.util UUID) (org.quartz Job))) -(defrecord TestDefrecordJobImpl [identity description recover? durable? test-dep disallowConcurrentExecution?] +(defrecord TestDefrecordJobImpl [] Job (execute [this _job-context] (log/info "Defrecord Impl:" this))) @@ -231,10 +238,12 @@ See: integration test source: [test/cronut/integration-test.clj](test/cronut/int (log/info "scheduling defrecord job on 1s interval") (cronut/schedule-job scheduler (trigger/interval 1000) - (map->TestDefrecordJobImpl {:identity ["name1" "group2"] - :description "test job" - :recover? true - :durable? false})) + (map->TestDefrecordJobImpl {}) + {:name "name1" + :group "group2" + :description "test job" + :recover? true + :durable? false}) ;; demonstrate scheduler can start with jobs, and jobs can start after scheduler (cronut/start scheduler) @@ -247,7 +256,12 @@ See: integration test source: [test/cronut/integration-test.clj](test/cronut/int (trigger/builder {:type :cron :cron "*/5 * * * * ?" :misfire :do-nothing}) - reify-job) + reify-job + {:name "name2" + :group "group2" + :description "test job 2" + :recover? false + :durable? true}) (async/ProxyJob job)))))) (defn detail - [job concurrent-execution-disallowed?] - (let [{:keys [identity description recover? durable? disallow-concurrent-execution?]} job] - (.build (cond-> (-> (JobBuilder/newJob (if (or concurrent-execution-disallowed? ;; global concurrency disallowed flag - disallow-concurrent-execution?) ;; job specific concurrency disallowed flag - SerialProxyJob ProxyJob)) + ^JobDetail [job opts] + (let [{:keys [name group description recover? durable? disallow-concurrent-execution?]} opts] + (.build (cond-> (-> (JobBuilder/newJob (if disallow-concurrent-execution? SerialProxyJob ProxyJob)) (.setJobData (JobDataMap. {"job-impl" job}))) - (seq identity) (.withIdentity (first identity) (second identity)) + name (.withIdentity name group) description (.withDescription description) (boolean? recover?) (.requestRecovery recover?) (boolean? durable?) (.storeDurably durable?))))) diff --git a/src/cronut/trigger.clj b/src/cronut/trigger.clj index 5ed0595..1d2b512 100644 --- a/src/cronut/trigger.clj +++ b/src/cronut/trigger.clj @@ -5,9 +5,9 @@ (defn base-builder "Provide a base trigger-builder from configuration" - [{:keys [identity description start end priority]}] + [{:keys [name group description start end priority]}] (cond-> (TriggerBuilder/newTrigger) - (seq identity) (.withIdentity (first identity) (second identity)) + name (.withIdentity name group) description (.withDescription description) start (.startAt start) (nil? start) (.startNow) diff --git a/test/cronut/integration_test.clj b/test/cronut/integration_test.clj index 56adce9..04d16ba 100644 --- a/test/cronut/integration_test.clj +++ b/test/cronut/integration_test.clj @@ -6,7 +6,7 @@ (:import (java.util UUID) (org.quartz Job))) -(defrecord TestDefrecordJobImpl [identity description recover? durable? test-dep disallowConcurrentExecution?] +(defrecord TestDefrecordJobImpl [] Job (execute [this _job-context] (log/info "Defrecord Impl:" this))) @@ -30,10 +30,12 @@ (log/info "scheduling defrecord job on 1s interval") (cronut/schedule-job scheduler (trigger/interval 1000) - (map->TestDefrecordJobImpl {:identity ["name1" "group2"] - :description "test job" - :recover? true - :durable? false})) + (map->TestDefrecordJobImpl {}) + {:name "name1" + :group "group2" + :description "test job" + :recover? true + :durable? false}) ;; demonstrate scheduler can start with jobs, and jobs can start after scheduler (cronut/start scheduler) @@ -46,7 +48,12 @@ (trigger/builder {:type :cron :cron "*/5 * * * * ?" :misfire :do-nothing}) - reify-job) + reify-job + {:name "name2" + :group "group2" + :description "test job 2" + :recover? false + :durable? true}) (async/ (job/detail {:identity ["name1" "group2"] - :description "desc3" - :durable? true - :recover? true - :disallow-concurrent-execution? true} false) + :jobDataMap {"job-impl" reify-job}} + (-> (job/detail reify-job {:name "job-name" + :group "job-group" + :description "desc3" + :durable? true + :recover? true + :disallow-concurrent-execution? true}) (bean) - (select-keys job-keys))))) + (select-keys job-keys)))) -(deftest job-detail-concurrency-reify-style - - ;; global concurrentExecutionDisallowed? = false - (is (= {:jobClass ProxyJob - :description nil - :durable false + ;; :name is required before :name :group identity takes effect + (is (= {:group JobKey/DEFAULT_GROUP + :jobClass ProxyJob + :description "desc3" + :durable false :concurrentExecutionDisallowed false - :jobDataMap {"job-impl" reify-job}} - (-> (job/detail reify-job false) - (bean) - (select-keys job-keys) - (dissoc :fullName)))) - - ;; global concurrentExecutionDisallowed? = true - (is (= {:jobClass SerialProxyJob - :description nil - :durable false - :concurrentExecutionDisallowed true - :jobDataMap {"job-impl" reify-job}} - (-> (job/detail reify-job true) + :jobDataMap {"job-impl" reify-job}} + (-> (job/detail reify-job {:group "job-group" + :description "desc3" + :durable? false + :recover? false + :disallow-concurrent-execution? false}) (bean) - (select-keys job-keys) - (dissoc :fullName))))) + (select-keys (conj (rest job-keys) :group)))))) -(deftest job-detail-concurrency-defrecord-style +(deftest job-detail-concurrency ;; global concurrentExecutionDisallowed? = false - ;; job disallowConcurrentExecution? = false - (is (= {:jobClass ProxyJob - :description nil - :durable false + (is (= {:jobClass ProxyJob + :description nil + :durable false :concurrentExecutionDisallowed false - :jobDataMap {"job-impl" {}}} - (-> (job/detail {} false) - (bean) - (select-keys job-keys) - (dissoc :fullName)))) - - ;; global concurrentExecutionDisallowed? true - ;; job disallowConcurrentExecution? = false - (is (= {:jobClass SerialProxyJob - :description nil - :durable false - :concurrentExecutionDisallowed true - :jobDataMap {"job-impl" {}}} - (-> (job/detail {} true) - (bean) - (select-keys job-keys) - (dissoc :fullName)))) - - ;; global concurrentExecutionDisallowed? = false - ;; job disallowConcurrentExecution? = true - (is (= {:jobClass SerialProxyJob - :description nil - :durable false - :concurrentExecutionDisallowed true - :jobDataMap {"job-impl" {:disallow-concurrent-execution? true}}} - (-> (job/detail {:disallow-concurrent-execution? true} false) + :jobDataMap {"job-impl" reify-job}} + (-> (job/detail reify-job false) (bean) (select-keys job-keys) (dissoc :fullName)))) ;; global concurrentExecutionDisallowed? = true - ;; job disallowConcurrentExecution? = true - (is (= {:jobClass SerialProxyJob - :description nil - :durable false + (is (= {:jobClass SerialProxyJob + :description nil + :durable false :concurrentExecutionDisallowed true - :jobDataMap {"job-impl" {:disallow-concurrent-execution? true}}} - (-> (job/detail {:disallow-concurrent-execution? true} false) + :jobDataMap {"job-impl" reify-job}} + (-> (job/detail reify-job {:disallow-concurrent-execution? true}) (bean) (select-keys job-keys) (dissoc :fullName))))) diff --git a/test/cronut/trigger_test.clj b/test/cronut/trigger_test.clj index 2d320ed..2bb6029 100644 --- a/test/cronut/trigger_test.clj +++ b/test/cronut/trigger_test.clj @@ -13,14 +13,24 @@ (bean) (select-keys [:group :description :priority])))) - (is (= {:group "test" - :name "trigger-two" + ;; :name is required before :name :group identity takes effect + (is (= {:group "DEFAULT" + :description nil + :priority 5} + (-> (trigger/base-builder {:group "trigger-group"}) + (.build) + (bean) + (select-keys [:group :description :priority])))) + + (is (= {:name "trigger-name" + :group "trigger-group" :description "test trigger" :priority 101 :startTime #inst "2019-01-01T00:00:00.000-00:00" :endTime #inst "2019-02-01T00:00:00.000-00:00"} (-> (trigger/base-builder - {:identity ["trigger-two" "test"] + {:name "trigger-name" + :group "trigger-group" :description "test trigger" :start #inst "2019-01-01T00:00:00.000-00:00" :end #inst "2019-02-01T00:00:00.000-00:00" @@ -116,5 +126,4 @@ :misfire :fire-and-proceed}) (.build) (bean) - (select-keys [:cronExpression :timeZone :misfireInstruction]))))) - + (select-keys [:cronExpression :timeZone :misfireInstruction]))))) \ No newline at end of file diff --git a/test/cronut_test.clj b/test/cronut_test.clj index 540ca5c..eec7ae2 100644 --- a/test/cronut_test.clj +++ b/test/cronut_test.clj @@ -3,12 +3,13 @@ [clojure.tools.logging :as log] [cronut :as cronut] [cronut.trigger :as trigger]) - (:import (org.quartz Job Trigger))) + (:import (org.quartz Job JobKey Trigger))) -(defrecord TestDefrecordJobImpl [identity description recover? durable? test-dep disallow-concurrent-execution?] - Job - (execute [this _job-context] - (log/info "Defrecord Impl:" this))) +(defn reify-job + [n] + (reify Job + (execute [_ _] + (log/info "Executing job: " n)))) (deftest concurrent-execution-disallowed @@ -18,43 +19,60 @@ (deftest scheduling - (let [scheduler (cronut/scheduler {}) + (let [scheduler (cronut/scheduler {:concurrent-execution-disallowed? true}) _ (cronut/clear scheduler) trigger (cronut/schedule-job scheduler (trigger/interval 2000) - (map->TestDefrecordJobImpl {:identity ["name1" "group1"] - :description "test job" - :recover? true - :durable? false})) + (reify-job 1) + {:name "name1" + :group "group1" + :description "test job" + :recover? true + :durable? false}) trigger2 (cronut/schedule-job scheduler (trigger/builder {:type :simple :interval 3000 :time-unit :millis :repeat :forever - :identity ["trigger-name2" "group1"]}) - (map->TestDefrecordJobImpl {:identity ["name2" "group2"] - :description "test job" - :recover? true - :durable? true})) + :name "trigger-name2" + :group "group1"}) + (reify-job 2) + {:name "name2" + :group "group2" + :description "test job" + :recover? true + :durable? true}) trigger3 (cronut/schedule-job scheduler (cronut.trigger/cron "*/8 * * * * ?") - (map->TestDefrecordJobImpl {:identity ["name3" "group2"] - :description "test job" - :recover? false - :durable? true})) + (reify-job 3) + {:name "name3" + :group "group2" + :description "test job" + :recover? false + :durable? true}) trigger4 (cronut/schedule-job scheduler - (trigger/builder {:type :cron - :cron "*/5 * * * * ?" - :identity ["trigger-name4" "group2"]}) - (map->TestDefrecordJobImpl {:identity ["name4" "group2"] - :description "test job" - :recover? false - :durable? false}))] + (trigger/builder {:type :cron + :cron "*/5 * * * * ?" + :name "trigger-name4" + :group "group2"}) + (reify-job 4) + {:name "name4" + :group "group2" + :description "test job" + :recover? false + :durable? false})] (is (instance? Trigger trigger)) (is (instance? Trigger trigger2)) (is (instance? Trigger trigger3)) (is (instance? Trigger trigger4)) + (testing "scheduler concurrent-execution-disallowed?" + + ;; concurrent-execution-disallowed? set on the scheduler only + (is (->> (JobKey. "name1" "group1") + (.getJobDetail scheduler) + (.isConcurrentExecutionDisallowed)))) + (testing "unschedule by trigger" (is (cronut/unschedule-trigger scheduler trigger)) ;; second call returns false, no trigger to unschedule