├── .circleci
└── config.yml
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── changelog.md
├── pom.xml
└── src
├── main
└── java
│ └── net
│ └── joelinn
│ └── quartz
│ └── jobstore
│ ├── AbstractRedisStorage.java
│ ├── RedisClusterStorage.java
│ ├── RedisJobStore.java
│ ├── RedisJobStoreSchema.java
│ ├── RedisStorage.java
│ ├── RedisTriggerState.java
│ ├── jedis
│ └── JedisClusterCommandsWrapper.java
│ └── mixin
│ ├── CronTriggerMixin.java
│ ├── HolidayCalendarMixin.java
│ ├── JobDetailMixin.java
│ └── TriggerMixin.java
└── test
├── java
└── net
│ └── joelinn
│ ├── junit
│ ├── Retry.java
│ └── RetryRule.java
│ └── quartz
│ ├── BaseIntegrationTest.java
│ ├── BaseTest.java
│ ├── MultiSchedulerIntegrationTest.java
│ ├── MultiThreadedIntegrationTest.java
│ ├── RedisJobStoreTest.java
│ ├── RedisSentinelJobStoreTest.java
│ ├── SingleThreadedIntegrationTest.java
│ ├── StoreCalendarTest.java
│ ├── StoreJobTest.java
│ ├── StoreTriggerTest.java
│ ├── TestJob.java
│ ├── TestJobNonConcurrent.java
│ ├── TestJobPersist.java
│ ├── TestUtils.java
│ ├── TriggeredJobCompleteTest.java
│ └── mixin
│ ├── CronTriggerMixinTest.java
│ ├── JobDetailMixinTest.java
│ └── SimpleTriggerMixinTest.java
└── resources
└── logback-test.xml
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 |
3 | orbs:
4 | maven: circleci/maven@1.1
5 |
6 | workflows:
7 | maven_test:
8 | jobs:
9 | - maven/test
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .idea/
3 |
4 | target/
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | dist: trusty
3 | jdk:
4 | - openjdk7
5 | - openjdk8
6 | - oraclejdk8
7 | - oraclejdk9
8 |
9 | sudo: false
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | quartz-redis-jobstore
2 | =====================
3 |
4 | [](http://travis-ci.org/jlinn/quartz-redis-jobstore)
5 |
6 | A [Quartz Scheduler](http://quartz-scheduler.org/) JobStore using [Redis](http://redis.io).
7 |
8 | This project was inspired by [redis-quartz](https://github.com/RedisLabs/redis-quartz), and provides similar functionality with some key differences:
9 |
10 | * Redis database and key prefix are configurable.
11 | * Redis' [recommended distributed locking method](http://redis.io/topics/distlock) is used.
12 | * All of the functionality of this library is covered by a [test suite](https://github.com/jlinn/quartz-redis-jobstore/tree/master/src/test/java/net/joelinn/quartz).
13 |
14 | ## Requirements
15 | * Java 7 or higher
16 | * Redis 2.6.12 or higher (3.0 or higher for Redis cluster)
17 |
18 | ## Installation
19 | Maven dependency:
20 | ```xml
21 |
22 | net.joelinn
23 | quartz-redis-jobstore
24 | 1.2.0
25 |
26 | ```
27 |
28 | ## Configuration
29 | The following properties may be set in your `quartz.properties` file:
30 | ```
31 | # set the scheduler's JobStore class (required)
32 | org.quartz.jobStore.class = net.joelinn.quartz.jobstore.RedisJobStore
33 |
34 | # set the Redis host (required)
35 | org.quartz.jobStore.host =
36 |
37 | # set the scheduler's trigger misfire threshold in milliseconds (optional, defaults to 60000)
38 | org.quartz.jobStore.misfireThreshold = 60000
39 |
40 | # set the redis password (optional, defaults null)
41 | org.quartz.jobStore.password =
42 |
43 | # set the redis port (optional, defaults to 6379)
44 | org.quartz.jobStore.port =
45 |
46 | # enable Redis clustering (optional, defaults to false)
47 | org.quartz.jobStore.redisCluster =
48 |
49 | # enable Redis sentinel (optional, defaults to false)
50 | org.quartz.jobStore.redisSentinel =
51 |
52 | # set the sentinel master group name (required if redisSentinel = true)
53 | org.quartz.jobStore.masterGroupName =
54 |
55 | # set the redis database (optional, defaults to 0)
56 | org.quartz.jobStore.database:
57 |
58 | # set the Redis key prefix for all JobStore Redis keys (optional, defaults to none)
59 | org.quartz.jobStore.keyPrefix = a_prefix_
60 |
61 | # set the Redis lock timeout in milliseconds (optional, defaults to 30000)
62 | org.quartz.jobStore.lockTimeout = 30000
63 |
64 | # enable SSL (defaults to false)
65 | org.quartz.jobStore.ssl =
66 | ```
67 |
68 | ## Limitations
69 | All GroupMatcher comparators have been implemented.
70 | Aside from that, the same limitations outlined in [redis-quartz's readme](https://github.com/RedisLabs/redis-quartz#limitations) apply.
71 |
--------------------------------------------------------------------------------
/changelog.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | ### 2019-07-02
3 | * Upgrade to Jedis 3.0.1
4 |
5 | ### 2019-06-26
6 | * Delete job data map set from Redis prior to storing new job data when updating / overwriting a job.
7 | This will prevent keys which were removed from the job's data map prior to storage from being preserved.
8 |
9 | ### 2018-03-01
10 | * Detect dead schedulers and unblock their blocked triggers
11 | * Keep track of `previousFireTime` for triggers
12 |
13 | ### 2018-02-27
14 | * Set fire instance id on retrieved triggers
15 | * Fixed a bug where trigger locks would get incorrectly removed for non-concurrent jobs
16 |
17 | ### 2016-12-30
18 | * Fix a bug when handling trigger firing for triggers with no next fire time
19 |
20 | ### 2016-12-04
21 | * Fixed handling of jobs marked with `@DisallowConcurrentExecution`.
22 |
23 | ### 2016-10-30
24 | * Fix serialization of HolidayCalendar
25 |
26 | ### 2016-10-23
27 | * Add support for storing trigger-specific job data
28 |
29 | ### 2016-05-04
30 | * Add support for Redis password
31 |
32 | ### 2016-03-17
33 | * Allow Redis db to be set when using Sentinel
34 |
35 | ### 2016-03-02
36 | * Fix a bug where acquired triggers were not being released.
37 |
38 | ### 2016-01-31
39 | * Add support for Redis Sentinel
40 |
41 | ### 2015-08-19
42 | * Add support for [Jedis cluster](https://github.com/xetorthio/jedis#jedis-cluster).
43 | * Allow a pre-configured Pool or JedisCluster to be passed in to RedisJobStore.
44 | * Update to Jackson v2.6.1.
45 |
46 | ### 2014-12-09
47 | * Remove Guava dependency
48 |
49 | ### 2014-09-24
50 | * Add the ability to specify a redis database.
51 | * Fix setter methods for `keyPrefix` and `keyDelimiter` properties.
52 | * Set default port to 6379.
53 |
54 | ### 2014-08-21
55 | * Fix a bug where non-durable jobs with only one trigger would be deleted when replaceTrigger() was called with that trigger.
56 |
57 | ### 2014-07-25
58 | * Handle `ObjectAlreadyExistsException` separately in RedisJobStore::storeJobAndTrigger()
59 |
60 | ### 2014-07-24
61 | * Enable the use of all GroupMatchers (not just EQUALS)
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | net.joelinn
6 | quartz-redis-jobstore
7 | 1.2.1-SNAPSHOT
8 | jar
9 | quartz-redis-jobstore
10 | A Quartz Scheduler JobStore using Redis.
11 | https://github.com/jlinn/quartz-redis-jobstore
12 |
13 |
14 |
15 | The Apache Software License, Version 2.0
16 | http://www.apache.org/licenses/LICENSE-2.0.txt
17 | repo
18 |
19 |
20 |
21 |
22 |
23 | Joe Linn
24 | https://github.com/jlinn
25 |
26 |
27 |
28 |
29 | scm:git:git@github.com:jlinn/quartz-redis-jobstore.git
30 | scm:git:git@github.com:jlinn/quartz-redis-jobstore.git
31 | http://github.com/jlinn/quartz-redis-jobstore
32 | HEAD
33 |
34 |
35 |
36 | UTF-8
37 | -Xdoclint:none
38 | 2.2.1
39 | 2.11.1
40 | 1.1.7
41 |
42 |
43 |
44 |
45 | clojars.org
46 | http://clojars.org/repo
47 |
48 |
49 |
50 |
51 |
52 | org.quartz-scheduler
53 | quartz
54 | ${quartz.version}
55 |
56 |
57 |
58 | org.quartz-scheduler
59 | quartz-jobs
60 | ${quartz.version}
61 |
62 |
63 |
64 | redis.clients
65 | jedis
66 | 3.3.0
67 |
68 |
69 |
70 | com.fasterxml.jackson.core
71 | jackson-core
72 | ${jackson.version}
73 |
74 |
75 |
76 | com.fasterxml.jackson.core
77 | jackson-annotations
78 | ${jackson.version}
79 |
80 |
81 |
82 | com.fasterxml.jackson.core
83 | jackson-databind
84 | ${jackson.version}
85 |
86 |
87 |
88 | org.slf4j
89 | slf4j-api
90 | 1.7.7
91 |
92 |
93 |
94 |
95 | junit
96 | junit
97 | 4.12
98 | test
99 |
100 |
101 |
102 | org.hamcrest
103 | hamcrest-all
104 | 1.3
105 | test
106 |
107 |
108 |
109 | org.mockito
110 | mockito-all
111 | 1.9.5
112 | test
113 |
114 |
115 |
116 | com.google.guava
117 | guava-io
118 | r03
119 | test
120 |
121 |
122 |
123 | commons-io
124 | commons-io
125 | 2.4
126 | test
127 |
128 |
129 |
130 | com.github.kstyrc
131 | embedded-redis
132 | 0.6
133 | test
134 |
135 |
136 |
137 | net.jodah
138 | concurrentunit
139 | 0.4.2
140 | test
141 |
142 |
143 |
144 | ch.qos.logback
145 | logback-classic
146 | ${logback.version}
147 | test
148 |
149 |
150 |
151 | ch.qos.logback
152 | logback-core
153 | ${logback.version}
154 | test
155 |
156 |
157 |
158 |
159 |
160 |
161 | src/test/resources
162 |
163 |
164 |
165 |
166 |
167 | org.apache.maven.plugins
168 | maven-source-plugin
169 | 2.3
170 |
171 |
172 | attach-sources
173 |
174 | jar
175 |
176 |
177 |
178 |
179 |
180 |
181 | maven-assembly-plugin
182 |
183 |
184 | package
185 |
186 | attached
187 |
188 |
189 |
190 |
191 |
192 | jar-with-dependencies
193 |
194 |
195 |
196 |
197 |
198 | org.apache.maven.plugins
199 | maven-release-plugin
200 | 2.5
201 |
202 | -Dgpg.passphrase=${gpg.passphrase}
203 | deploy -Dmaven.test.skip=true
204 |
205 |
206 |
207 |
208 | org.apache.maven.plugins
209 | maven-surefire-plugin
210 | 2.17
211 |
212 | false
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 | org.apache.maven.plugins
221 | maven-compiler-plugin
222 |
223 | 1.7
224 | 1.7
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 | release-sign-artifacts
234 |
235 |
236 | performRelease
237 | true
238 |
239 |
240 |
241 |
242 |
243 | org.apache.maven.plugins
244 | maven-gpg-plugin
245 | 1.5
246 |
247 | ${gpg.passphrase}
248 |
249 |
250 |
251 | sign-artifacts
252 | verify
253 |
254 | sign
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 | sonatype-nexus-snapshots
267 | Sonatype Nexus snapshot repository
268 | https://oss.sonatype.org/content/repositories/snapshots
269 |
270 |
271 | sonatype-nexus-staging
272 | Sonatype Nexus release repository
273 | https://oss.sonatype.org/service/local/staging/deploy/maven2/
274 |
275 |
276 |
277 |
--------------------------------------------------------------------------------
/src/main/java/net/joelinn/quartz/jobstore/RedisClusterStorage.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.quartz.jobstore;
2 |
3 | import com.fasterxml.jackson.core.JsonProcessingException;
4 | import com.fasterxml.jackson.core.type.TypeReference;
5 | import com.fasterxml.jackson.databind.ObjectMapper;
6 | import net.joelinn.quartz.jobstore.jedis.JedisClusterCommandsWrapper;
7 | import org.quartz.Calendar;
8 | import org.quartz.*;
9 | import org.quartz.impl.matchers.GroupMatcher;
10 | import org.quartz.impl.matchers.StringMatcher;
11 | import org.quartz.spi.OperableTrigger;
12 | import org.quartz.spi.SchedulerSignaler;
13 | import org.quartz.spi.TriggerFiredBundle;
14 | import org.quartz.spi.TriggerFiredResult;
15 | import org.slf4j.Logger;
16 | import org.slf4j.LoggerFactory;
17 |
18 | import java.util.*;
19 |
20 | /**
21 | * Joe Linn
22 | * 8/22/2015
23 | */
24 | public class RedisClusterStorage extends AbstractRedisStorage {
25 | private static final Logger logger = LoggerFactory.getLogger(RedisClusterStorage.class);
26 |
27 | public RedisClusterStorage(RedisJobStoreSchema redisSchema, ObjectMapper mapper, SchedulerSignaler signaler, String schedulerInstanceId, int lockTimeout) {
28 | super(redisSchema, mapper, signaler, schedulerInstanceId, lockTimeout);
29 | }
30 |
31 | /**
32 | * Store a job in Redis
33 | *
34 | * @param jobDetail the {@link JobDetail} object to be stored
35 | * @param replaceExisting if true, any existing job with the same group and name as the given job will be overwritten
36 | * @param jedis a thread-safe Redis connection
37 | * @throws ObjectAlreadyExistsException
38 | */
39 | @Override
40 | @SuppressWarnings("unchecked")
41 | public void storeJob(JobDetail jobDetail, boolean replaceExisting, JedisClusterCommandsWrapper jedis) throws ObjectAlreadyExistsException {
42 | final String jobHashKey = redisSchema.jobHashKey(jobDetail.getKey());
43 | final String jobDataMapHashKey = redisSchema.jobDataMapHashKey(jobDetail.getKey());
44 | final String jobGroupSetKey = redisSchema.jobGroupSetKey(jobDetail.getKey());
45 |
46 | if (!replaceExisting && jedis.exists(jobHashKey)) {
47 | throw new ObjectAlreadyExistsException(jobDetail);
48 | }
49 |
50 | jedis.hmset(jobHashKey, (Map) mapper.convertValue(jobDetail, new TypeReference>() {
51 | }));
52 | jedis.del(jobDataMapHashKey);
53 | if (jobDetail.getJobDataMap() != null && !jobDetail.getJobDataMap().isEmpty()) {
54 | jedis.hmset(jobDataMapHashKey, getStringDataMap(jobDetail.getJobDataMap()));
55 | }
56 |
57 | jedis.sadd(redisSchema.jobsSet(), jobHashKey);
58 | jedis.sadd(redisSchema.jobGroupsSet(), jobGroupSetKey);
59 | jedis.sadd(jobGroupSetKey, jobHashKey);
60 | }
61 |
62 | /**
63 | * Remove the given job from Redis
64 | *
65 | * @param jobKey the job to be removed
66 | * @param jedis a thread-safe Redis connection
67 | * @return true if the job was removed; false if it did not exist
68 | */
69 | @Override
70 | public boolean removeJob(JobKey jobKey, JedisClusterCommandsWrapper jedis) throws JobPersistenceException {
71 | final String jobHashKey = redisSchema.jobHashKey(jobKey);
72 | final String jobBlockedKey = redisSchema.jobBlockedKey(jobKey);
73 | final String jobDataMapHashKey = redisSchema.jobDataMapHashKey(jobKey);
74 | final String jobGroupSetKey = redisSchema.jobGroupSetKey(jobKey);
75 | final String jobTriggerSetKey = redisSchema.jobTriggersSetKey(jobKey);
76 |
77 | // remove the job and any associated data
78 | Long delJobHashKeyResponse = jedis.del(jobHashKey);
79 | // remove the blocked job key
80 | jedis.del(jobBlockedKey);
81 | // remove the job's data map
82 | jedis.del(jobDataMapHashKey);
83 | // remove the job from the set of all jobs
84 | jedis.srem(redisSchema.jobsSet(), jobHashKey);
85 | // remove the job from the set of blocked jobs
86 | jedis.srem(redisSchema.blockedJobsSet(), jobHashKey);
87 | // remove the job from its group
88 | jedis.srem(jobGroupSetKey, jobHashKey);
89 | // retrieve the keys for all triggers associated with this job, then delete that set
90 | Set jobTriggerSetResponse = jedis.smembers(jobTriggerSetKey);
91 | jedis.del(jobTriggerSetKey);
92 | Long jobGroupSetSizeResponse = jedis.scard(jobGroupSetKey);
93 | if (jobGroupSetSizeResponse == 0) {
94 | // The group now contains no jobs. Remove it from the set of all job groups.
95 | jedis.srem(redisSchema.jobGroupsSet(), jobGroupSetKey);
96 | }
97 |
98 | // remove all triggers associated with this job
99 | for (String triggerHashKey : jobTriggerSetResponse) {
100 | // get this trigger's TriggerKey
101 | final TriggerKey triggerKey = redisSchema.triggerKey(triggerHashKey);
102 | final String triggerGroupSetKey = redisSchema.triggerGroupSetKey(triggerKey);
103 | unsetTriggerState(triggerHashKey, jedis);
104 | // remove the trigger from the set of all triggers
105 | jedis.srem(redisSchema.triggersSet(), triggerHashKey);
106 | // remove the trigger's group from the set of all trigger groups
107 | jedis.srem(redisSchema.triggerGroupsSet(), triggerGroupSetKey);
108 | // remove this trigger from its group
109 | jedis.srem(triggerGroupSetKey, triggerHashKey);
110 | // delete the trigger
111 | jedis.del(triggerHashKey);
112 | }
113 | return delJobHashKeyResponse == 1;
114 | }
115 |
116 | /**
117 | * Store a trigger in redis
118 | *
119 | * @param trigger the trigger to be stored
120 | * @param replaceExisting true if an existing trigger with the same identity should be replaced
121 | * @param jedis a thread-safe Redis connection
122 | * @throws JobPersistenceException
123 | * @throws ObjectAlreadyExistsException
124 | */
125 | @Override
126 | public void storeTrigger(OperableTrigger trigger, boolean replaceExisting, JedisClusterCommandsWrapper jedis) throws JobPersistenceException {
127 | final String triggerHashKey = redisSchema.triggerHashKey(trigger.getKey());
128 | final String triggerGroupSetKey = redisSchema.triggerGroupSetKey(trigger.getKey());
129 | final String jobTriggerSetKey = redisSchema.jobTriggersSetKey(trigger.getJobKey());
130 |
131 | if (!(trigger instanceof SimpleTrigger) && !(trigger instanceof CronTrigger)) {
132 | throw new UnsupportedOperationException("Only SimpleTrigger and CronTrigger are supported.");
133 | }
134 | final boolean exists = jedis.exists(triggerHashKey);
135 | if (exists && !replaceExisting) {
136 | throw new ObjectAlreadyExistsException(trigger);
137 | }
138 |
139 | Map triggerMap = mapper.convertValue(trigger, new TypeReference>() {
140 | });
141 | triggerMap.put(TRIGGER_CLASS, trigger.getClass().getName());
142 |
143 | jedis.hmset(triggerHashKey, triggerMap);
144 | jedis.sadd(redisSchema.triggersSet(), triggerHashKey);
145 | jedis.sadd(redisSchema.triggerGroupsSet(), triggerGroupSetKey);
146 | jedis.sadd(triggerGroupSetKey, triggerHashKey);
147 | jedis.sadd(jobTriggerSetKey, triggerHashKey);
148 | if (trigger.getCalendarName() != null && !trigger.getCalendarName().isEmpty()) {
149 | final String calendarTriggersSetKey = redisSchema.calendarTriggersSetKey(trigger.getCalendarName());
150 | jedis.sadd(calendarTriggersSetKey, triggerHashKey);
151 | }
152 | if (trigger.getJobDataMap() != null && !trigger.getJobDataMap().isEmpty()) {
153 | final String triggerDataMapHashKey = redisSchema.triggerDataMapHashKey(trigger.getKey());
154 | jedis.hmset(triggerDataMapHashKey, getStringDataMap(trigger.getJobDataMap()));
155 | }
156 |
157 | if (exists) {
158 | // We're overwriting a previously stored instance of this trigger, so clear any existing trigger state.
159 | unsetTriggerState(triggerHashKey, jedis);
160 | }
161 |
162 | Boolean triggerPausedResponse = jedis.sismember(redisSchema.pausedTriggerGroupsSet(), triggerGroupSetKey);
163 | Boolean jobPausedResponse = jedis.sismember(redisSchema.pausedJobGroupsSet(), redisSchema.jobGroupSetKey(trigger.getJobKey()));
164 |
165 | if (triggerPausedResponse || jobPausedResponse) {
166 | final long nextFireTime = trigger.getNextFireTime() != null ? trigger.getNextFireTime().getTime() : -1;
167 | final String jobHashKey = redisSchema.jobHashKey(trigger.getJobKey());
168 | if (isBlockedJob(jobHashKey, jedis)) {
169 | setTriggerState(RedisTriggerState.PAUSED_BLOCKED, (double) nextFireTime, triggerHashKey, jedis);
170 | } else {
171 | setTriggerState(RedisTriggerState.PAUSED, (double) nextFireTime, triggerHashKey, jedis);
172 | }
173 | } else if (trigger.getNextFireTime() != null) {
174 | setTriggerState(RedisTriggerState.WAITING, (double) trigger.getNextFireTime().getTime(), triggerHashKey, jedis);
175 | }
176 | }
177 |
178 | /**
179 | * Remove (delete) the {@link Trigger}
with the given key.
180 | *
181 | * @param triggerKey the key of the trigger to be removed
182 | * @param removeNonDurableJob if true, the job associated with the given trigger will be removed if it is non-durable
183 | * and has no other triggers
184 | * @param jedis a thread-safe Redis connection
185 | * @return true if the trigger was found and removed
186 | */
187 | @Override
188 | protected boolean removeTrigger(TriggerKey triggerKey, boolean removeNonDurableJob, JedisClusterCommandsWrapper jedis) throws JobPersistenceException, ClassNotFoundException {
189 | final String triggerHashKey = redisSchema.triggerHashKey(triggerKey);
190 | final String triggerGroupSetKey = redisSchema.triggerGroupSetKey(triggerKey);
191 |
192 | if (!jedis.exists(triggerHashKey)) {
193 | return false;
194 | }
195 |
196 | OperableTrigger trigger = retrieveTrigger(triggerKey, jedis);
197 |
198 | final String jobHashKey = redisSchema.jobHashKey(trigger.getJobKey());
199 | final String jobTriggerSetKey = redisSchema.jobTriggersSetKey(trigger.getJobKey());
200 |
201 | // remove the trigger from the set of all triggers
202 | jedis.srem(redisSchema.triggersSet(), triggerHashKey);
203 | // remove the trigger from its trigger group set
204 | jedis.srem(triggerGroupSetKey, triggerHashKey);
205 | // remove the trigger from the associated job's trigger set
206 | jedis.srem(jobTriggerSetKey, triggerHashKey);
207 |
208 | if (jedis.scard(triggerGroupSetKey) == 0) {
209 | // The trigger group set is empty. Remove the trigger group from the set of trigger groups.
210 | jedis.srem(redisSchema.triggerGroupsSet(), triggerGroupSetKey);
211 | }
212 |
213 | if (removeNonDurableJob) {
214 | Long jobTriggerSetKeySizeResponse = jedis.scard(jobTriggerSetKey);
215 | Boolean jobExistsResponse = jedis.exists(jobHashKey);
216 | if (jobTriggerSetKeySizeResponse == 0 && jobExistsResponse) {
217 | JobDetail job = retrieveJob(trigger.getJobKey(), jedis);
218 | if (!job.isDurable()) {
219 | // Job is not durable and has no remaining triggers. Delete it.
220 | removeJob(job.getKey(), jedis);
221 | signaler.notifySchedulerListenersJobDeleted(job.getKey());
222 | }
223 | }
224 | }
225 |
226 | if (isNullOrEmpty(trigger.getCalendarName())) {
227 | jedis.srem(redisSchema.calendarTriggersSetKey(trigger.getCalendarName()), triggerHashKey);
228 | }
229 | unsetTriggerState(triggerHashKey, jedis);
230 | jedis.del(triggerHashKey);
231 | return true;
232 | }
233 |
234 | /**
235 | * Unsets the state of the given trigger key by removing the trigger from all trigger state sets.
236 | *
237 | * @param triggerHashKey the redis key of the desired trigger hash
238 | * @param jedis a thread-safe Redis connection
239 | * @return true if the trigger was removed, false if the trigger was stateless
240 | * @throws JobPersistenceException if the unset operation failed
241 | */
242 | @Override
243 | public boolean unsetTriggerState(String triggerHashKey, JedisClusterCommandsWrapper jedis) throws JobPersistenceException {
244 | boolean removed = false;
245 | List responses = new ArrayList<>(RedisTriggerState.values().length);
246 | for (RedisTriggerState state : RedisTriggerState.values()) {
247 | responses.add(jedis.zrem(redisSchema.triggerStateKey(state), triggerHashKey));
248 | }
249 | for (Long response : responses) {
250 | removed = response == 1;
251 | if (removed) {
252 | jedis.del(redisSchema.triggerLockKey(redisSchema.triggerKey(triggerHashKey)));
253 | break;
254 | }
255 | }
256 | return removed;
257 | }
258 |
259 | /**
260 | * Store a {@link Calendar}
261 | *
262 | * @param name the name of the calendar
263 | * @param calendar the calendar object to be stored
264 | * @param replaceExisting if true, any existing calendar with the same name will be overwritten
265 | * @param updateTriggers if true, any existing triggers associated with the calendar will be updated
266 | * @param jedis a thread-safe Redis connection
267 | * @throws JobPersistenceException
268 | */
269 | @Override
270 | public void storeCalendar(String name, Calendar calendar, boolean replaceExisting, boolean updateTriggers, JedisClusterCommandsWrapper jedis) throws JobPersistenceException {
271 | final String calendarHashKey = redisSchema.calendarHashKey(name);
272 | if (!replaceExisting && jedis.exists(calendarHashKey)) {
273 | throw new ObjectAlreadyExistsException(String.format("Calendar with key %s already exists.", calendarHashKey));
274 | }
275 | Map calendarMap = new HashMap<>();
276 | calendarMap.put(CALENDAR_CLASS, calendar.getClass().getName());
277 | try {
278 | calendarMap.put(CALENDAR_JSON, mapper.writeValueAsString(calendar));
279 | } catch (JsonProcessingException e) {
280 | throw new JobPersistenceException("Unable to serialize calendar.", e);
281 | }
282 |
283 | jedis.hmset(calendarHashKey, calendarMap);
284 | jedis.sadd(redisSchema.calendarsSet(), calendarHashKey);
285 |
286 | if (updateTriggers) {
287 | final String calendarTriggersSetKey = redisSchema.calendarTriggersSetKey(name);
288 | Set triggerHashKeys = jedis.smembers(calendarTriggersSetKey);
289 | for (String triggerHashKey : triggerHashKeys) {
290 | OperableTrigger trigger = retrieveTrigger(redisSchema.triggerKey(triggerHashKey), jedis);
291 | long removed = jedis.zrem(redisSchema.triggerStateKey(RedisTriggerState.WAITING), triggerHashKey);
292 | trigger.updateWithNewCalendar(calendar, misfireThreshold);
293 | if (removed == 1) {
294 | setTriggerState(RedisTriggerState.WAITING, (double) trigger.getNextFireTime().getTime(), triggerHashKey, jedis);
295 | }
296 | }
297 | }
298 | }
299 |
300 | /**
301 | * Remove (delete) the {@link Calendar}
with the given name.
302 | *
303 | * @param calendarName the name of the calendar to be removed
304 | * @param jedis a thread-safe Redis connection
305 | * @return true if a calendar with the given name was found and removed
306 | */
307 | @Override
308 | public boolean removeCalendar(String calendarName, JedisClusterCommandsWrapper jedis) throws JobPersistenceException {
309 | final String calendarTriggersSetKey = redisSchema.calendarTriggersSetKey(calendarName);
310 |
311 | if (jedis.scard(calendarTriggersSetKey) > 0) {
312 | throw new JobPersistenceException(String.format("There are triggers pointing to calendar %s, so it cannot be removed.", calendarName));
313 | }
314 | final String calendarHashKey = redisSchema.calendarHashKey(calendarName);
315 | Long deleteResponse = jedis.del(calendarHashKey);
316 | jedis.srem(redisSchema.calendarsSet(), calendarHashKey);
317 |
318 | return deleteResponse == 1;
319 | }
320 |
321 | /**
322 | * Get the keys of all of the {@link Job}
s that have the given group name.
323 | *
324 | * @param matcher the matcher with which to compare group names
325 | * @param jedis a thread-safe Redis connection
326 | * @return the set of all JobKeys which have the given group name
327 | */
328 | @Override
329 | public Set getJobKeys(GroupMatcher matcher, JedisClusterCommandsWrapper jedis) {
330 | Set jobKeys = new HashSet<>();
331 | if (matcher.getCompareWithOperator() == StringMatcher.StringOperatorName.EQUALS) {
332 | final String jobGroupSetKey = redisSchema.jobGroupSetKey(new JobKey("", matcher.getCompareToValue()));
333 | final Set jobs = jedis.smembers(jobGroupSetKey);
334 | if (jobs != null) {
335 | for (final String job : jobs) {
336 | jobKeys.add(redisSchema.jobKey(job));
337 | }
338 | }
339 | } else {
340 | List> jobGroups = new ArrayList<>();
341 | for (final String jobGroupSetKey : jedis.smembers(redisSchema.jobGroupsSet())) {
342 | if (matcher.getCompareWithOperator().evaluate(redisSchema.jobGroup(jobGroupSetKey), matcher.getCompareToValue())) {
343 | jobGroups.add(jedis.smembers(jobGroupSetKey));
344 | }
345 | }
346 | for (Set jobGroup : jobGroups) {
347 | if (jobGroup != null) {
348 | for (final String job : jobGroup) {
349 | jobKeys.add(redisSchema.jobKey(job));
350 | }
351 | }
352 | }
353 | }
354 | return jobKeys;
355 | }
356 |
357 | /**
358 | * Get the names of all of the {@link Trigger}
s that have the given group name.
359 | *
360 | * @param matcher the matcher with which to compare group names
361 | * @param jedis a thread-safe Redis connection
362 | * @return the set of all TriggerKeys which have the given group name
363 | */
364 | @Override
365 | public Set getTriggerKeys(GroupMatcher matcher, JedisClusterCommandsWrapper jedis) {
366 | Set triggerKeys = new HashSet<>();
367 | if (matcher.getCompareWithOperator() == StringMatcher.StringOperatorName.EQUALS) {
368 | final String triggerGroupSetKey = redisSchema.triggerGroupSetKey(new TriggerKey("", matcher.getCompareToValue()));
369 | final Set triggers = jedis.smembers(triggerGroupSetKey);
370 | if (triggers != null) {
371 | for (final String trigger : triggers) {
372 | triggerKeys.add(redisSchema.triggerKey(trigger));
373 | }
374 | }
375 | } else {
376 | List> triggerGroups = new ArrayList<>();
377 | for (final String triggerGroupSetKey : jedis.smembers(redisSchema.triggerGroupsSet())) {
378 | if (matcher.getCompareWithOperator().evaluate(redisSchema.triggerGroup(triggerGroupSetKey), matcher.getCompareToValue())) {
379 | triggerGroups.add(jedis.smembers(triggerGroupSetKey));
380 | }
381 | }
382 | for (Set triggerGroup : triggerGroups) {
383 | if (triggerGroup != null) {
384 | for (final String trigger : triggerGroup) {
385 | triggerKeys.add(redisSchema.triggerKey(trigger));
386 | }
387 | }
388 | }
389 | }
390 | return triggerKeys;
391 | }
392 |
393 | /**
394 | * Get the current state of the identified {@link Trigger}
.
395 | *
396 | * @param triggerKey the key of the desired trigger
397 | * @param jedis a thread-safe Redis connection
398 | * @return the state of the trigger
399 | */
400 | @Override
401 | public Trigger.TriggerState getTriggerState(TriggerKey triggerKey, JedisClusterCommandsWrapper jedis) {
402 | final String triggerHashKey = redisSchema.triggerHashKey(triggerKey);
403 | Map scores = new HashMap<>(RedisTriggerState.values().length);
404 | for (RedisTriggerState redisTriggerState : RedisTriggerState.values()) {
405 | scores.put(redisTriggerState, jedis.zscore(redisSchema.triggerStateKey(redisTriggerState), triggerHashKey));
406 | }
407 | for (Map.Entry entry : scores.entrySet()) {
408 | if (entry.getValue() != null) {
409 | return entry.getKey().getTriggerState();
410 | }
411 | }
412 | return Trigger.TriggerState.NONE;
413 | }
414 |
415 | /**
416 | * Pause the trigger with the given key
417 | *
418 | * @param triggerKey the key of the trigger to be paused
419 | * @param jedis a thread-safe Redis connection
420 | * @throws JobPersistenceException if the desired trigger does not exist
421 | */
422 | @Override
423 | public void pauseTrigger(TriggerKey triggerKey, JedisClusterCommandsWrapper jedis) throws JobPersistenceException {
424 | final String triggerHashKey = redisSchema.triggerHashKey(triggerKey);
425 | Boolean exists = jedis.exists(triggerHashKey);
426 | Double completedScore = jedis.zscore(redisSchema.triggerStateKey(RedisTriggerState.COMPLETED), triggerHashKey);
427 | String nextFireTimeResponse = jedis.hget(triggerHashKey, TRIGGER_NEXT_FIRE_TIME);
428 | Double blockedScore = jedis.zscore(redisSchema.triggerStateKey(RedisTriggerState.BLOCKED), triggerHashKey);
429 |
430 | if (!exists) {
431 | return;
432 | }
433 | if (completedScore != null) {
434 | // doesn't make sense to pause a completed trigger
435 | return;
436 | }
437 |
438 | final long nextFireTime = nextFireTimeResponse == null
439 | || nextFireTimeResponse.isEmpty() ? -1 : Long.parseLong(nextFireTimeResponse);
440 | if (blockedScore != null) {
441 | setTriggerState(RedisTriggerState.PAUSED_BLOCKED, (double) nextFireTime, triggerHashKey, jedis);
442 | } else {
443 | setTriggerState(RedisTriggerState.PAUSED, (double) nextFireTime, triggerHashKey, jedis);
444 | }
445 | }
446 |
447 | /**
448 | * Pause all of the {@link Trigger}s
in the given group.
449 | *
450 | * @param matcher matcher for the trigger groups to be paused
451 | * @param jedis a thread-safe Redis connection
452 | * @return a collection of names of trigger groups which were matched and paused
453 | * @throws JobPersistenceException
454 | */
455 | @Override
456 | public Collection pauseTriggers(GroupMatcher matcher, JedisClusterCommandsWrapper jedis) throws JobPersistenceException {
457 | Set pausedTriggerGroups = new HashSet<>();
458 | if (matcher.getCompareWithOperator() == StringMatcher.StringOperatorName.EQUALS) {
459 | final String triggerGroupSetKey = redisSchema.triggerGroupSetKey(new TriggerKey("", matcher.getCompareToValue()));
460 | final long addResult = jedis.sadd(redisSchema.pausedTriggerGroupsSet(), triggerGroupSetKey);
461 | if (addResult > 0) {
462 | for (final String trigger : jedis.smembers(triggerGroupSetKey)) {
463 | pauseTrigger(redisSchema.triggerKey(trigger), jedis);
464 | }
465 | pausedTriggerGroups.add(redisSchema.triggerGroup(triggerGroupSetKey));
466 | }
467 | } else {
468 | Map> triggerGroups = new HashMap<>();
469 | for (final String triggerGroupSetKey : jedis.smembers(redisSchema.triggerGroupsSet())) {
470 | if (matcher.getCompareWithOperator().evaluate(redisSchema.triggerGroup(triggerGroupSetKey), matcher.getCompareToValue())) {
471 | triggerGroups.put(triggerGroupSetKey, jedis.smembers(triggerGroupSetKey));
472 | }
473 | }
474 | for (final Map.Entry> entry : triggerGroups.entrySet()) {
475 | if (jedis.sadd(redisSchema.pausedJobGroupsSet(), entry.getKey()) > 0) {
476 | // This trigger group was not paused. Pause it now.
477 | pausedTriggerGroups.add(redisSchema.triggerGroup(entry.getKey()));
478 | for (final String triggerHashKey : entry.getValue()) {
479 | pauseTrigger(redisSchema.triggerKey(triggerHashKey), jedis);
480 | }
481 | }
482 | }
483 | }
484 | return pausedTriggerGroups;
485 | }
486 |
487 | /**
488 | * Pause all of the {@link Job}s
in the given group - by pausing all of their
489 | * Trigger
s.
490 | *
491 | * @param groupMatcher the mather which will determine which job group should be paused
492 | * @param jedis a thread-safe Redis connection
493 | * @return a collection of names of job groups which have been paused
494 | * @throws JobPersistenceException
495 | */
496 | @Override
497 | public Collection pauseJobs(GroupMatcher groupMatcher, JedisClusterCommandsWrapper jedis) throws JobPersistenceException {
498 | Set pausedJobGroups = new HashSet<>();
499 | if (groupMatcher.getCompareWithOperator() == StringMatcher.StringOperatorName.EQUALS) {
500 | final String jobGroupSetKey = redisSchema.jobGroupSetKey(new JobKey("", groupMatcher.getCompareToValue()));
501 | if (jedis.sadd(redisSchema.pausedJobGroupsSet(), jobGroupSetKey) > 0) {
502 | pausedJobGroups.add(redisSchema.jobGroup(jobGroupSetKey));
503 | for (String job : jedis.smembers(jobGroupSetKey)) {
504 | pauseJob(redisSchema.jobKey(job), jedis);
505 | }
506 | }
507 | } else {
508 | Map> jobGroups = new HashMap<>();
509 | for (final String jobGroupSetKey : jedis.smembers(redisSchema.jobGroupsSet())) {
510 | if (groupMatcher.getCompareWithOperator().evaluate(redisSchema.jobGroup(jobGroupSetKey), groupMatcher.getCompareToValue())) {
511 | jobGroups.put(jobGroupSetKey, jedis.smembers(jobGroupSetKey));
512 | }
513 | }
514 | for (final Map.Entry> entry : jobGroups.entrySet()) {
515 | if (jedis.sadd(redisSchema.pausedJobGroupsSet(), entry.getKey()) > 0) {
516 | // This job group was not already paused. Pause it now.
517 | pausedJobGroups.add(redisSchema.jobGroup(entry.getKey()));
518 | for (final String jobHashKey : entry.getValue()) {
519 | pauseJob(redisSchema.jobKey(jobHashKey), jedis);
520 | }
521 | }
522 | }
523 | }
524 | return pausedJobGroups;
525 | }
526 |
527 | /**
528 | * Resume (un-pause) a {@link Trigger}
529 | *
530 | * @param triggerKey the key of the trigger to be resumed
531 | * @param jedis a thread-safe Redis connection
532 | */
533 | @Override
534 | public void resumeTrigger(TriggerKey triggerKey, JedisClusterCommandsWrapper jedis) throws JobPersistenceException {
535 | final String triggerHashKey = redisSchema.triggerHashKey(triggerKey);
536 | Boolean exists = jedis.sismember(redisSchema.triggersSet(), triggerHashKey);
537 | Double isPaused = jedis.zscore(redisSchema.triggerStateKey(RedisTriggerState.PAUSED), triggerHashKey);
538 | Double isPausedBlocked = jedis.zscore(redisSchema.triggerStateKey(RedisTriggerState.PAUSED_BLOCKED), triggerHashKey);
539 |
540 | if (!exists) {
541 | // Trigger does not exist. Nothing to do.
542 | return;
543 | }
544 | if (isPaused == null && isPausedBlocked == null) {
545 | // Trigger is not paused. Nothing to do.
546 | return;
547 | }
548 | OperableTrigger trigger = retrieveTrigger(triggerKey, jedis);
549 | final String jobHashKey = redisSchema.jobHashKey(trigger.getJobKey());
550 | final Date nextFireTime = trigger.getNextFireTime();
551 |
552 | if (nextFireTime != null) {
553 | if (isBlockedJob(jobHashKey, jedis)) {
554 | setTriggerState(RedisTriggerState.BLOCKED, (double) nextFireTime.getTime(), triggerHashKey, jedis);
555 | } else {
556 | setTriggerState(RedisTriggerState.WAITING, (double) nextFireTime.getTime(), triggerHashKey, jedis);
557 | }
558 | }
559 | applyMisfire(trigger, jedis);
560 | }
561 |
562 | /**
563 | * Resume (un-pause) all of the {@link Trigger}s
in the given group.
564 | *
565 | * @param matcher matcher for the trigger groups to be resumed
566 | * @param jedis a thread-safe Redis connection
567 | * @return the names of trigger groups which were resumed
568 | */
569 | @Override
570 | public Collection resumeTriggers(GroupMatcher matcher, JedisClusterCommandsWrapper jedis) throws JobPersistenceException {
571 | Set resumedTriggerGroups = new HashSet<>();
572 | if (matcher.getCompareWithOperator() == StringMatcher.StringOperatorName.EQUALS) {
573 | final String triggerGroupSetKey = redisSchema.triggerGroupSetKey(new TriggerKey("", matcher.getCompareToValue()));
574 | jedis.srem(redisSchema.pausedJobGroupsSet(), triggerGroupSetKey);
575 | Set triggerHashKeysResponse = jedis.smembers(triggerGroupSetKey);
576 | for (String triggerHashKey : triggerHashKeysResponse) {
577 | OperableTrigger trigger = retrieveTrigger(redisSchema.triggerKey(triggerHashKey), jedis);
578 | resumeTrigger(trigger.getKey(), jedis);
579 | resumedTriggerGroups.add(trigger.getKey().getGroup());
580 | }
581 | } else {
582 | for (final String triggerGroupSetKey : jedis.smembers(redisSchema.triggerGroupsSet())) {
583 | if (matcher.getCompareWithOperator().evaluate(redisSchema.triggerGroup(triggerGroupSetKey), matcher.getCompareToValue())) {
584 | resumedTriggerGroups.addAll(resumeTriggers(GroupMatcher.triggerGroupEquals(redisSchema.triggerGroup(triggerGroupSetKey)), jedis));
585 | }
586 | }
587 | }
588 | return resumedTriggerGroups;
589 | }
590 |
591 | /**
592 | * Resume (un-pause) all of the {@link Job}s
in the given group.
593 | *
594 | * @param matcher the matcher with which to compare job group names
595 | * @param jedis a thread-safe Redis connection
596 | * @return the set of job groups which were matched and resumed
597 | */
598 | @Override
599 | public Collection resumeJobs(GroupMatcher matcher, JedisClusterCommandsWrapper jedis) throws JobPersistenceException {
600 | Set resumedJobGroups = new HashSet<>();
601 | if (matcher.getCompareWithOperator() == StringMatcher.StringOperatorName.EQUALS) {
602 | final String jobGroupSetKey = redisSchema.jobGroupSetKey(new JobKey("", matcher.getCompareToValue()));
603 | Long unpauseResponse = jedis.srem(redisSchema.pausedJobGroupsSet(), jobGroupSetKey);
604 | Set jobsResponse = jedis.smembers(jobGroupSetKey);
605 | if (unpauseResponse > 0) {
606 | resumedJobGroups.add(redisSchema.jobGroup(jobGroupSetKey));
607 | }
608 | for (String job : jobsResponse) {
609 | resumeJob(redisSchema.jobKey(job), jedis);
610 | }
611 | } else {
612 | for (final String jobGroupSetKey : jedis.smembers(redisSchema.jobGroupsSet())) {
613 | if (matcher.getCompareWithOperator().evaluate(redisSchema.jobGroup(jobGroupSetKey), matcher.getCompareToValue())) {
614 | resumedJobGroups.addAll(resumeJobs(GroupMatcher.jobGroupEquals(redisSchema.jobGroup(jobGroupSetKey)), jedis));
615 | }
616 | }
617 | }
618 | return resumedJobGroups;
619 | }
620 |
621 | /**
622 | * Inform the JobStore
that the scheduler is now firing the
623 | * given Trigger
(executing its associated Job
),
624 | * that it had previously acquired (reserved).
625 | *
626 | * @param triggers a list of triggers
627 | * @param jedis a thread-safe Redis connection
628 | * @return may return null if all the triggers or their calendars no longer exist, or
629 | * if the trigger was not successfully put into the 'executing'
630 | * state. Preference is to return an empty list if none of the triggers
631 | * could be fired.
632 | */
633 | @Override
634 | public List triggersFired(List triggers, JedisClusterCommandsWrapper jedis) throws JobPersistenceException, ClassNotFoundException {
635 | List results = new ArrayList<>();
636 | for (OperableTrigger trigger : triggers) {
637 | final String triggerHashKey = redisSchema.triggerHashKey(trigger.getKey());
638 | logger.debug(String.format("Trigger %s fired.", triggerHashKey));
639 | Boolean triggerExistsResponse = jedis.exists(triggerHashKey);
640 | Double triggerAcquiredResponse = jedis.zscore(redisSchema.triggerStateKey(RedisTriggerState.ACQUIRED), triggerHashKey);
641 | if (!triggerExistsResponse || triggerAcquiredResponse == null) {
642 | // the trigger does not exist or the trigger is not acquired
643 | if (!triggerExistsResponse) {
644 | logger.debug(String.format("Trigger %s does not exist.", triggerHashKey));
645 | } else {
646 | logger.debug(String.format("Trigger %s was not acquired.", triggerHashKey));
647 | }
648 | continue;
649 | }
650 | Calendar calendar = null;
651 | final String calendarName = trigger.getCalendarName();
652 | if (calendarName != null) {
653 | calendar = retrieveCalendar(calendarName, jedis);
654 | if (calendar == null) {
655 | continue;
656 | }
657 | }
658 |
659 | final Date previousFireTime = trigger.getPreviousFireTime();
660 | trigger.triggered(calendar);
661 |
662 | JobDetail job = retrieveJob(trigger.getJobKey(), jedis);
663 | TriggerFiredBundle triggerFiredBundle = new TriggerFiredBundle(job, trigger, calendar, false, new Date(), previousFireTime, previousFireTime, trigger.getNextFireTime());
664 |
665 | // handling jobs for which concurrent execution is disallowed
666 | if (isJobConcurrentExecutionDisallowed(job.getJobClass())) {
667 | final String jobHashKey = redisSchema.jobHashKey(trigger.getJobKey());
668 | final String jobTriggerSetKey = redisSchema.jobTriggersSetKey(job.getKey());
669 | for (String nonConcurrentTriggerHashKey : jedis.smembers(jobTriggerSetKey)) {
670 | Double score = jedis.zscore(redisSchema.triggerStateKey(RedisTriggerState.WAITING), nonConcurrentTriggerHashKey);
671 | if (score != null) {
672 | setTriggerState(RedisTriggerState.BLOCKED, score, nonConcurrentTriggerHashKey, jedis);
673 | // setting trigger state removes locks, so re-lock
674 | lockTrigger(redisSchema.triggerKey(nonConcurrentTriggerHashKey), jedis);
675 | } else {
676 | score = jedis.zscore(redisSchema.triggerStateKey(RedisTriggerState.PAUSED), nonConcurrentTriggerHashKey);
677 | if (score != null) {
678 | setTriggerState(RedisTriggerState.PAUSED_BLOCKED, score, nonConcurrentTriggerHashKey, jedis);
679 | // setting trigger state removes locks, so re-lock
680 | lockTrigger(redisSchema.triggerKey(nonConcurrentTriggerHashKey), jedis);
681 | }
682 | }
683 | }
684 | jedis.set(redisSchema.jobBlockedKey(job.getKey()), schedulerInstanceId);
685 | jedis.sadd(redisSchema.blockedJobsSet(), jobHashKey);
686 | }
687 |
688 | // release the fired trigger
689 | if (trigger.getNextFireTime() != null) {
690 | final long nextFireTime = trigger.getNextFireTime().getTime();
691 | jedis.hset(triggerHashKey, TRIGGER_NEXT_FIRE_TIME, Long.toString(nextFireTime));
692 | logger.debug(String.format("Releasing trigger %s with next fire time %s. Setting state to WAITING.", triggerHashKey, nextFireTime));
693 | setTriggerState(RedisTriggerState.WAITING, (double) nextFireTime, triggerHashKey, jedis);
694 | } else {
695 | jedis.hset(triggerHashKey, TRIGGER_NEXT_FIRE_TIME, "");
696 | unsetTriggerState(triggerHashKey, jedis);
697 | }
698 | jedis.hset(triggerHashKey, TRIGGER_PREVIOUS_FIRE_TIME, Long.toString(System.currentTimeMillis()));
699 |
700 | results.add(new TriggerFiredResult(triggerFiredBundle));
701 | }
702 | return results;
703 | }
704 |
705 | /**
706 | * Inform the JobStore
that the scheduler has completed the
707 | * firing of the given Trigger
(and the execution of its
708 | * associated Job
completed, threw an exception, or was vetoed),
709 | * and that the {@link JobDataMap}
710 | * in the given JobDetail
should be updated if the Job
711 | * is stateful.
712 | *
713 | * @param trigger the trigger which was completed
714 | * @param jobDetail the job which was completed
715 | * @param triggerInstCode the status of the completed job
716 | * @param jedis a thread-safe Redis connection
717 | */
718 | @Override
719 | public void triggeredJobComplete(OperableTrigger trigger, JobDetail jobDetail, Trigger.CompletedExecutionInstruction triggerInstCode, JedisClusterCommandsWrapper jedis) throws JobPersistenceException, ClassNotFoundException {
720 | final String jobHashKey = redisSchema.jobHashKey(jobDetail.getKey());
721 | final String jobDataMapHashKey = redisSchema.jobDataMapHashKey(jobDetail.getKey());
722 | final String triggerHashKey = redisSchema.triggerHashKey(trigger.getKey());
723 | logger.debug(String.format("Job %s completed.", jobHashKey));
724 | if (jedis.exists(jobHashKey)) {
725 | // job was not deleted during execution
726 | if (isPersistJobDataAfterExecution(jobDetail.getJobClass())) {
727 | // update the job data map
728 | JobDataMap jobDataMap = jobDetail.getJobDataMap();
729 | jedis.del(jobDataMapHashKey);
730 | if (jobDataMap != null && !jobDataMap.isEmpty()) {
731 | jedis.hmset(jobDataMapHashKey, getStringDataMap(jobDataMap));
732 | }
733 | }
734 | if (isJobConcurrentExecutionDisallowed(jobDetail.getJobClass())) {
735 | // unblock the job
736 | jedis.srem(redisSchema.blockedJobsSet(), jobHashKey);
737 | jedis.del(redisSchema.jobBlockedKey(jobDetail.getKey()));
738 |
739 | final String jobTriggersSetKey = redisSchema.jobTriggersSetKey(jobDetail.getKey());
740 | for (String nonConcurrentTriggerHashKey : jedis.smembers(jobTriggersSetKey)) {
741 | Double score = jedis.zscore(redisSchema.triggerStateKey(RedisTriggerState.BLOCKED), nonConcurrentTriggerHashKey);
742 | if (score != null) {
743 | setTriggerState(RedisTriggerState.WAITING, score, nonConcurrentTriggerHashKey, jedis);
744 | } else {
745 | score = jedis.zscore(redisSchema.triggerStateKey(RedisTriggerState.PAUSED_BLOCKED), nonConcurrentTriggerHashKey);
746 | if (score != null) {
747 | setTriggerState(RedisTriggerState.PAUSED, score, nonConcurrentTriggerHashKey, jedis);
748 | }
749 | }
750 | }
751 | signaler.signalSchedulingChange(0L);
752 | }
753 | } else {
754 | // unblock the job, even if it has been deleted
755 | jedis.srem(redisSchema.blockedJobsSet(), jobHashKey);
756 | }
757 |
758 | if (jedis.exists(triggerHashKey)) {
759 | // trigger was not deleted during job execution
760 | if (triggerInstCode == Trigger.CompletedExecutionInstruction.DELETE_TRIGGER) {
761 | if (trigger.getNextFireTime() == null) {
762 | // double-check for possible reschedule within job execution, which would cancel the need to delete
763 | if (isNullOrEmpty(jedis.hget(triggerHashKey, TRIGGER_NEXT_FIRE_TIME))) {
764 | removeTrigger(trigger.getKey(), jedis);
765 | }
766 | } else {
767 | removeTrigger(trigger.getKey(), jedis);
768 | signaler.signalSchedulingChange(0L);
769 | }
770 | } else if (triggerInstCode == Trigger.CompletedExecutionInstruction.SET_TRIGGER_COMPLETE) {
771 | setTriggerState(RedisTriggerState.COMPLETED, (double) System.currentTimeMillis(), triggerHashKey, jedis);
772 | signaler.signalSchedulingChange(0L);
773 | } else if (triggerInstCode == Trigger.CompletedExecutionInstruction.SET_TRIGGER_ERROR) {
774 | logger.debug(String.format("Trigger %s set to ERROR state.", triggerHashKey));
775 | final double score = trigger.getNextFireTime() != null ? (double) trigger.getNextFireTime().getTime() : 0;
776 | setTriggerState(RedisTriggerState.ERROR, score, triggerHashKey, jedis);
777 | signaler.signalSchedulingChange(0L);
778 | } else if (triggerInstCode == Trigger.CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR) {
779 | final String jobTriggersSetKey = redisSchema.jobTriggersSetKey(jobDetail.getKey());
780 | for (String errorTriggerHashKey : jedis.smembers(jobTriggersSetKey)) {
781 | final String nextFireTime = jedis.hget(errorTriggerHashKey, TRIGGER_NEXT_FIRE_TIME);
782 | final double score = isNullOrEmpty(nextFireTime) ? 0 : Double.parseDouble(nextFireTime);
783 | setTriggerState(RedisTriggerState.ERROR, score, errorTriggerHashKey, jedis);
784 | }
785 | signaler.signalSchedulingChange(0L);
786 | } else if (triggerInstCode == Trigger.CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_COMPLETE) {
787 | final String jobTriggerSetKey = redisSchema.jobTriggersSetKey(jobDetail.getKey());
788 | for (String completedTriggerHashKey : jedis.smembers(jobTriggerSetKey)) {
789 | setTriggerState(RedisTriggerState.COMPLETED, (double) System.currentTimeMillis(), completedTriggerHashKey, jedis);
790 | }
791 | signaler.signalSchedulingChange(0L);
792 | }
793 | }
794 | }
795 | }
796 |
--------------------------------------------------------------------------------
/src/main/java/net/joelinn/quartz/jobstore/RedisJobStoreSchema.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.quartz.jobstore;
2 |
3 | import org.quartz.JobKey;
4 | import org.quartz.TriggerKey;
5 |
6 | import java.util.Arrays;
7 | import java.util.List;
8 |
9 | /**
10 | * Joe Linn
11 | * 7/14/2014
12 | */
13 | public class RedisJobStoreSchema {
14 | protected static final String DEFAULT_DELIMITER = ":";
15 | protected static final String JOBS_SET = "jobs";
16 | protected static final String JOB_GROUPS_SET = "job_groups";
17 | protected static final String LOCK = "lock";
18 |
19 | protected final String prefix;
20 |
21 | protected final String delimiter;
22 |
23 | public RedisJobStoreSchema(){
24 | this("");
25 | }
26 |
27 | /**
28 | * @param prefix the prefix to be prepended to all redis keys
29 | */
30 | public RedisJobStoreSchema(String prefix){
31 | this(prefix, DEFAULT_DELIMITER);
32 | }
33 |
34 | /**
35 | * @param prefix the prefix to be prepended to all redis keys
36 | * @param delimiter the delimiter to be used to separate key segments
37 | */
38 | public RedisJobStoreSchema(String prefix, String delimiter) {
39 | this.prefix = prefix;
40 | this.delimiter = delimiter;
41 | }
42 |
43 | /**
44 | *
45 | * @return the redis key used for locking
46 | */
47 | public String lockKey(){
48 | return addPrefix(LOCK);
49 | }
50 |
51 | /**
52 | * @return the redis key for the set containing all job keys
53 | */
54 | public String jobsSet(){
55 | return addPrefix(JOBS_SET);
56 | }
57 |
58 | /**
59 | * @return the redis key for the set containing all job group keys
60 | */
61 | public String jobGroupsSet(){
62 | return addPrefix(JOB_GROUPS_SET);
63 | }
64 |
65 | /**
66 | *
67 | * @param jobKey
68 | * @return the redis key associated with the given {@link org.quartz.JobKey}
69 | */
70 | public String jobHashKey(final JobKey jobKey){
71 | return addPrefix("job" + delimiter + jobKey.getGroup() + delimiter + jobKey.getName());
72 | }
73 |
74 | /**
75 | *
76 | * @param jobKey
77 | * @return the redis key associated with the job data for the given {@link org.quartz.JobKey}
78 | */
79 | public String jobDataMapHashKey(final JobKey jobKey){
80 | return addPrefix("job_data_map" + delimiter + jobKey.getGroup() + delimiter + jobKey.getName());
81 | }
82 |
83 | /**
84 | *
85 | * @param jobKey
86 | * @return the key associated with the group set for the given {@link org.quartz.JobKey}
87 | */
88 | public String jobGroupSetKey(final JobKey jobKey){
89 | return addPrefix("job_group" + delimiter + jobKey.getGroup());
90 | }
91 |
92 | /**
93 | *
94 | * @param jobHashKey the hash key for a job
95 | * @return the {@link org.quartz.JobKey} object describing the job
96 | */
97 | public JobKey jobKey(final String jobHashKey){
98 | final List hashParts = split(jobHashKey);
99 | return new JobKey(hashParts.get(2), hashParts.get(1));
100 | }
101 |
102 | /**
103 | *
104 | * @param jobGroupSetKey the redis key for a job group set
105 | * @return the name of the job group
106 | */
107 | public String jobGroup(final String jobGroupSetKey){
108 | return split(jobGroupSetKey).get(1);
109 | }
110 |
111 | /**
112 | * @param jobKey the job key for which to get a trigger set key
113 | * @return the key associated with the set of triggers for the given {@link org.quartz.JobKey}
114 | */
115 | public String jobTriggersSetKey(final JobKey jobKey){
116 | return addPrefix("job_triggers" + delimiter + jobKey.getGroup() + delimiter + jobKey.getName());
117 | }
118 |
119 | /**
120 | * @return the key associated with the set of blocked jobs
121 | */
122 | public String blockedJobsSet(){
123 | return addPrefix("blocked_jobs");
124 | }
125 |
126 | /**
127 | *
128 | * @param triggerKey a trigger key
129 | * @return the redis key associated with the given {@link org.quartz.TriggerKey}
130 | */
131 | public String triggerHashKey(final TriggerKey triggerKey){
132 | return addPrefix("trigger" + delimiter + triggerKey.getGroup() + delimiter + triggerKey.getName());
133 | }
134 |
135 | /**
136 | *
137 | * @param triggerKey
138 | * @return the redis key associated with the trigger data for the given {@link org.quartz.TriggerKey}
139 | */
140 | public String triggerDataMapHashKey(final TriggerKey triggerKey){
141 | return addPrefix("trigger_data_map" + delimiter + triggerKey.getGroup() + delimiter + triggerKey.getName());
142 | }
143 |
144 | /**
145 | *
146 | * @param triggerHashKey the hash key for a trigger
147 | * @return the {@link org.quartz.TriggerKey} object describing the desired trigger
148 | */
149 | public TriggerKey triggerKey(final String triggerHashKey){
150 | final List hashParts = split(triggerHashKey);
151 | return new TriggerKey(hashParts.get(2), hashParts.get(1));
152 | }
153 |
154 | /**
155 | *
156 | * @param triggerGroupSetKey the redis key for a trigger group set
157 | * @return the name of the trigger group represented by the given redis key
158 | */
159 | public String triggerGroup(final String triggerGroupSetKey){
160 | return split(triggerGroupSetKey).get(1);
161 | }
162 |
163 | /**
164 | * @param triggerKey a trigger key
165 | * @return the redis key associated with the group of the given {@link org.quartz.TriggerKey}
166 | */
167 | public String triggerGroupSetKey(final TriggerKey triggerKey){
168 | return addPrefix("trigger_group" + delimiter + triggerKey.getGroup());
169 | }
170 |
171 | /**
172 | * @return the key of the set containing all trigger keys
173 | */
174 | public String triggersSet(){
175 | return addPrefix("triggers");
176 | }
177 |
178 | /**
179 | * @return the key of the set containing all trigger group keys
180 | */
181 | public String triggerGroupsSet(){
182 | return addPrefix("trigger_groups");
183 | }
184 |
185 | /**
186 | * @return the key of the set containing paused trigger group keys
187 | */
188 | public String pausedTriggerGroupsSet(){
189 | return addPrefix("paused_trigger_groups");
190 | }
191 |
192 | /**
193 | * @param state a {@link net.joelinn.quartz.jobstore.RedisTriggerState}
194 | * @return the key of a set containing the keys of triggers which are in the given state
195 | */
196 | public String triggerStateKey(final RedisTriggerState state){
197 | return addPrefix(state.getKey());
198 | }
199 |
200 | /**
201 | *
202 | * @param triggerKey the key of the trigger for which to retrieve a lock key
203 | * @return the redis key for the lock state of the given trigger
204 | */
205 | public String triggerLockKey(final TriggerKey triggerKey){
206 | return addPrefix("trigger_lock" + delimiter + triggerKey.getGroup() + delimiter + triggerKey.getName());
207 | }
208 |
209 | /**
210 | *
211 | * @param jobKey the key of the job for which to retrieve a block key
212 | * @return the redis key for the blocked state of the given job
213 | */
214 | public String jobBlockedKey(final JobKey jobKey){
215 | return addPrefix("job_blocked" + delimiter + jobKey.getGroup() + delimiter + jobKey.getName());
216 | }
217 |
218 | /**
219 | * @return the key which holds the time at which triggers were last released
220 | */
221 | public String lastTriggerReleaseTime(){
222 | return addPrefix("last_triggers_release_time");
223 | }
224 |
225 | /**
226 | * @return the key which holds a hash of scheduler instance ids to last active time
227 | */
228 | public String lastInstanceActiveTime(){
229 | return addPrefix("last_instance_active_time");
230 | }
231 |
232 | /**
233 | * @return the key of the set containing paused job groups
234 | */
235 | public String pausedJobGroupsSet(){
236 | return addPrefix("paused_job_groups");
237 | }
238 |
239 | /**
240 | * @param calendarName the name of the calendar for which to retrieve a key
241 | * @return the redis key for the set containing trigger keys for the given calendar name
242 | */
243 | public String calendarTriggersSetKey(final String calendarName){
244 | return addPrefix("calendar_triggers" + delimiter + calendarName);
245 | }
246 |
247 | /**
248 | * @param calendarName the name of the calendar for which to retrieve a key
249 | * @return the redis key for the calendar with the given name
250 | */
251 | public String calendarHashKey(final String calendarName){
252 | return addPrefix("calendar" + delimiter + calendarName);
253 | }
254 |
255 | /**
256 | *
257 | * @param calendarHashKey the redis key for a calendar
258 | * @return the name of the calendar represented by the given key
259 | */
260 | public String calendarName(final String calendarHashKey){
261 | return split(calendarHashKey).get(1);
262 | }
263 |
264 | /**
265 | * @return the key of the set containing all calendar keys
266 | */
267 | public String calendarsSet(){
268 | return addPrefix("calendars");
269 | }
270 |
271 | /**
272 | * Add the configured prefix string to the given key
273 | * @param key the key to which the prefix should be prepended
274 | * @return a prefixed key
275 | */
276 | protected String addPrefix(String key){
277 | return prefix + key;
278 | }
279 |
280 | /**
281 | * Split a string on the configured delimiter
282 | * @param string the string to split
283 | * @return a list comprised of the split parts of the given string
284 | */
285 | protected List split(final String string){
286 | if (null!=prefix){
287 | //remove prefix before split
288 | return Arrays.asList(string.substring(prefix.length()).split(delimiter));
289 | }else{
290 | return Arrays.asList(string.split(delimiter));
291 | }
292 |
293 | }
294 | }
295 |
--------------------------------------------------------------------------------
/src/main/java/net/joelinn/quartz/jobstore/RedisTriggerState.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.quartz.jobstore;
2 |
3 | import org.quartz.Trigger;
4 |
5 | /**
6 | * Joe Linn
7 | * 7/15/2014
8 | */
9 | public enum RedisTriggerState {
10 | WAITING("waiting_triggers", Trigger.TriggerState.NORMAL),
11 | PAUSED("paused_triggers", Trigger.TriggerState.PAUSED),
12 | BLOCKED("blocked_triggers", Trigger.TriggerState.BLOCKED),
13 | PAUSED_BLOCKED("paused_blocked_triggers", Trigger.TriggerState.PAUSED),
14 | ACQUIRED("acquired_triggers", Trigger.TriggerState.NORMAL),
15 | COMPLETED("completed_triggers", Trigger.TriggerState.COMPLETE),
16 | ERROR("error_triggers", Trigger.TriggerState.ERROR);
17 |
18 | private final String key;
19 |
20 | private final Trigger.TriggerState triggerState;
21 |
22 | RedisTriggerState(String key, Trigger.TriggerState triggerState) {
23 | this.key = key;
24 | this.triggerState = triggerState;
25 | }
26 |
27 | public String getKey() {
28 | return key;
29 | }
30 |
31 | public Trigger.TriggerState getTriggerState() {
32 | return triggerState;
33 | }
34 |
35 | public static RedisTriggerState toState(String key){
36 | for (RedisTriggerState state : RedisTriggerState.values()) {
37 | if(state.getKey().equals(key)){
38 | return state;
39 | }
40 | }
41 | return null;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/net/joelinn/quartz/jobstore/jedis/JedisClusterCommandsWrapper.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.quartz.jobstore.jedis;
2 |
3 | import redis.clients.jedis.*;
4 | import redis.clients.jedis.commands.JedisCommands;
5 | import redis.clients.jedis.params.GeoRadiusParam;
6 | import redis.clients.jedis.params.SetParams;
7 | import redis.clients.jedis.params.ZAddParams;
8 | import redis.clients.jedis.params.ZIncrByParams;
9 |
10 | import java.util.List;
11 | import java.util.Map;
12 | import java.util.Set;
13 |
14 | /**
15 | * Unfortunately, {@link JedisCluster} does not implement the {@link JedisCommands} interface, even though the vast
16 | * majority of its method signatures line up. This class works around that issue. Hopefully future versions of Jedis
17 | * will render this unnecessary.
18 | * @author Joe Linn
19 | * 7/2/2019
20 | */
21 | public class JedisClusterCommandsWrapper implements JedisCommands {
22 | private final JedisCluster cluster;
23 |
24 | public JedisClusterCommandsWrapper(JedisCluster cluster) {
25 | this.cluster = cluster;
26 | }
27 |
28 |
29 | @Override
30 | public String set(String s, String s1) {
31 | return cluster.set(s, s1);
32 | }
33 |
34 | @Override
35 | public String set(String s, String s1, SetParams setParams) {
36 | return cluster.set(s, s1, setParams);
37 | }
38 |
39 | @Override
40 | public String get(String s) {
41 | return cluster.get(s);
42 | }
43 |
44 | @Override
45 | public Boolean exists(String s) {
46 | return cluster.exists(s);
47 | }
48 |
49 | @Override
50 | public Long persist(String s) {
51 | return cluster.persist(s);
52 | }
53 |
54 | @Override
55 | public String type(String s) {
56 | return cluster.type(s);
57 | }
58 |
59 | @Override
60 | public byte[] dump(String s) {
61 | return cluster.dump(s);
62 | }
63 |
64 | @Override
65 | public String restore(String s, int i, byte[] bytes) {
66 | return cluster.restore(s, i, bytes);
67 | }
68 |
69 | @Override
70 | public String restoreReplace(String s, int i, byte[] bytes) {
71 | return null;
72 | }
73 |
74 | @Override
75 | public Long expire(String s, int i) {
76 | return cluster.expire(s, i);
77 | }
78 |
79 | @Override
80 | public Long pexpire(String s, long l) {
81 | return cluster.pexpire(s, l);
82 | }
83 |
84 | @Override
85 | public Long expireAt(String s, long l) {
86 | return cluster.expireAt(s, l);
87 | }
88 |
89 | @Override
90 | public Long pexpireAt(String s, long l) {
91 | return cluster.pexpireAt(s, l);
92 | }
93 |
94 | @Override
95 | public Long ttl(String s) {
96 | return cluster.ttl(s);
97 | }
98 |
99 | @Override
100 | public Long pttl(String s) {
101 | return cluster.pttl(s);
102 | }
103 |
104 | @Override
105 | public Long touch(String s) {
106 | return cluster.touch(s);
107 | }
108 |
109 | @Override
110 | public Boolean setbit(String s, long l, boolean b) {
111 | return cluster.setbit(s, l, b);
112 | }
113 |
114 | @Override
115 | public Boolean setbit(String s, long l, String s1) {
116 | return cluster.setbit(s, l, s1);
117 | }
118 |
119 | @Override
120 | public Boolean getbit(String s, long l) {
121 | return cluster.getbit(s, l);
122 | }
123 |
124 | @Override
125 | public Long setrange(String s, long l, String s1) {
126 | return cluster.setrange(s, l, s1);
127 | }
128 |
129 | @Override
130 | public String getrange(String s, long l, long l1) {
131 | return cluster.getrange(s, l, l1);
132 | }
133 |
134 | @Override
135 | public String getSet(String s, String s1) {
136 | return cluster.getSet(s, s1);
137 | }
138 |
139 | @Override
140 | public Long setnx(String s, String s1) {
141 | return cluster.setnx(s, s1);
142 | }
143 |
144 | @Override
145 | public String setex(String s, int i, String s1) {
146 | return cluster.setex(s, i, s1);
147 | }
148 |
149 | @Override
150 | public String psetex(String s, long l, String s1) {
151 | return cluster.psetex(s, l, s1);
152 | }
153 |
154 | @Override
155 | public Long decrBy(String s, long l) {
156 | return cluster.decrBy(s, l);
157 | }
158 |
159 | @Override
160 | public Long decr(String s) {
161 | return cluster.decr(s);
162 | }
163 |
164 | @Override
165 | public Long incrBy(String s, long l) {
166 | return cluster.incrBy(s, l);
167 | }
168 |
169 | @Override
170 | public Double incrByFloat(String s, double v) {
171 | return cluster.incrByFloat(s, v);
172 | }
173 |
174 | @Override
175 | public Long incr(String s) {
176 | return cluster.incr(s);
177 | }
178 |
179 | @Override
180 | public Long append(String s, String s1) {
181 | return cluster.append(s, s1);
182 | }
183 |
184 | @Override
185 | public String substr(String s, int i, int i1) {
186 | return cluster.substr(s, i, i1);
187 | }
188 |
189 | @Override
190 | public Long hset(String s, String s1, String s2) {
191 | return cluster.hset(s, s1, s2);
192 | }
193 |
194 | @Override
195 | public Long hset(String s, Map map) {
196 | return cluster.hset(s, map);
197 | }
198 |
199 | @Override
200 | public String hget(String s, String s1) {
201 | return cluster.hget(s, s1);
202 | }
203 |
204 | @Override
205 | public Long hsetnx(String s, String s1, String s2) {
206 | return cluster.hsetnx(s, s1, s2);
207 | }
208 |
209 | @Override
210 | public String hmset(String s, Map map) {
211 | return cluster.hmset(s, map);
212 | }
213 |
214 | @Override
215 | public List hmget(String s, String... strings) {
216 | return cluster.hmget(s, strings);
217 | }
218 |
219 | @Override
220 | public Long hincrBy(String s, String s1, long l) {
221 | return cluster.hincrBy(s, s1, l);
222 | }
223 |
224 | @Override
225 | public Double hincrByFloat(String s, String s1, double v) {
226 | return cluster.hincrByFloat(s.getBytes(), s1.getBytes(), v);
227 | }
228 |
229 | @Override
230 | public Boolean hexists(String s, String s1) {
231 | return cluster.hexists(s, s1);
232 | }
233 |
234 | @Override
235 | public Long hdel(String s, String... strings) {
236 | return cluster.hdel(s, strings);
237 | }
238 |
239 | @Override
240 | public Long hlen(String s) {
241 | return cluster.hlen(s);
242 | }
243 |
244 | @Override
245 | public Set hkeys(String s) {
246 | return cluster.hkeys(s);
247 | }
248 |
249 | @Override
250 | public List hvals(String s) {
251 | return cluster.hvals(s);
252 | }
253 |
254 | @Override
255 | public Map hgetAll(String s) {
256 | return cluster.hgetAll(s);
257 | }
258 |
259 | @Override
260 | public Long rpush(String s, String... strings) {
261 | return cluster.rpush(s, strings);
262 | }
263 |
264 | @Override
265 | public Long lpush(String s, String... strings) {
266 | return cluster.lpush(s, strings);
267 | }
268 |
269 | @Override
270 | public Long llen(String s) {
271 | return cluster.llen(s);
272 | }
273 |
274 | @Override
275 | public List lrange(String s, long l, long l1) {
276 | return cluster.lrange(s, l, l1);
277 | }
278 |
279 | @Override
280 | public String ltrim(String s, long l, long l1) {
281 | return cluster.ltrim(s, l, l1);
282 | }
283 |
284 | @Override
285 | public String lindex(String s, long l) {
286 | return cluster.lindex(s, l);
287 | }
288 |
289 | @Override
290 | public String lset(String s, long l, String s1) {
291 | return cluster.lset(s, l, s1);
292 | }
293 |
294 | @Override
295 | public Long lrem(String s, long l, String s1) {
296 | return cluster.lrem(s, l, s1);
297 | }
298 |
299 | @Override
300 | public String lpop(String s) {
301 | return cluster.lpop(s);
302 | }
303 |
304 | @Override
305 | public String rpop(String s) {
306 | return cluster.rpop(s);
307 | }
308 |
309 | @Override
310 | public Long sadd(String s, String... strings) {
311 | return cluster.sadd(s, strings);
312 | }
313 |
314 | @Override
315 | public Set smembers(String s) {
316 | return cluster.smembers(s);
317 | }
318 |
319 | @Override
320 | public Long srem(String s, String... strings) {
321 | return cluster.srem(s, strings);
322 | }
323 |
324 | @Override
325 | public String spop(String s) {
326 | return cluster.spop(s);
327 | }
328 |
329 | @Override
330 | public Set spop(String s, long l) {
331 | return cluster.spop(s, l);
332 | }
333 |
334 | @Override
335 | public Long scard(String s) {
336 | return cluster.scard(s);
337 | }
338 |
339 | @Override
340 | public Boolean sismember(String s, String s1) {
341 | return cluster.sismember(s, s1);
342 | }
343 |
344 | @Override
345 | public String srandmember(String s) {
346 | return cluster.srandmember(s);
347 | }
348 |
349 | @Override
350 | public List srandmember(String s, int i) {
351 | return cluster.srandmember(s, i);
352 | }
353 |
354 | @Override
355 | public Long strlen(String s) {
356 | return cluster.strlen(s);
357 | }
358 |
359 | @Override
360 | public Long zadd(String s, double v, String s1) {
361 | return cluster.zadd(s, v, s1);
362 | }
363 |
364 | @Override
365 | public Long zadd(String s, double v, String s1, ZAddParams zAddParams) {
366 | return cluster.zadd(s, v, s1, zAddParams);
367 | }
368 |
369 | @Override
370 | public Long zadd(String s, Map map) {
371 | return cluster.zadd(s, map);
372 | }
373 |
374 | @Override
375 | public Long zadd(String s, Map map, ZAddParams zAddParams) {
376 | return cluster.zadd(s, map, zAddParams);
377 | }
378 |
379 | @Override
380 | public Set zrange(String s, long l, long l1) {
381 | return cluster.zrange(s, l, l1);
382 | }
383 |
384 | @Override
385 | public Long zrem(String s, String... strings) {
386 | return cluster.zrem(s, strings);
387 | }
388 |
389 | @Override
390 | public Double zincrby(String s, double v, String s1) {
391 | return cluster.zincrby(s, v, s1);
392 | }
393 |
394 | @Override
395 | public Double zincrby(String s, double v, String s1, ZIncrByParams zIncrByParams) {
396 | return cluster.zincrby(s, v, s1, zIncrByParams);
397 | }
398 |
399 | @Override
400 | public Long zrank(String s, String s1) {
401 | return cluster.zrank(s, s1);
402 | }
403 |
404 | @Override
405 | public Long zrevrank(String s, String s1) {
406 | return cluster.zrevrank(s, s1);
407 | }
408 |
409 | @Override
410 | public Set zrevrange(String s, long l, long l1) {
411 | return cluster.zrevrange(s, l, l1);
412 | }
413 |
414 | @Override
415 | public Set zrangeWithScores(String s, long l, long l1) {
416 | return cluster.zrangeWithScores(s, l, l1);
417 | }
418 |
419 | @Override
420 | public Set zrevrangeWithScores(String s, long l, long l1) {
421 | return cluster.zrevrangeWithScores(s, l, l1);
422 | }
423 |
424 | @Override
425 | public Long zcard(String s) {
426 | return cluster.zcard(s);
427 | }
428 |
429 | @Override
430 | public Double zscore(String s, String s1) {
431 | return cluster.zscore(s, s1);
432 | }
433 |
434 | @Override
435 | public List sort(String s) {
436 | return cluster.sort(s);
437 | }
438 |
439 | @Override
440 | public List sort(String s, SortingParams sortingParams) {
441 | return cluster.sort(s, sortingParams);
442 | }
443 |
444 | @Override
445 | public Long zcount(String s, double v, double v1) {
446 | return cluster.zcount(s, v, v1);
447 | }
448 |
449 | @Override
450 | public Long zcount(String s, String s1, String s2) {
451 | return cluster.zcount(s, s1, s1);
452 | }
453 |
454 | @Override
455 | public Set zrangeByScore(String s, double v, double v1) {
456 | return cluster.zrangeByScore(s, v, v1);
457 | }
458 |
459 | @Override
460 | public Set zrangeByScore(String s, String s1, String s2) {
461 | return cluster.zrangeByScore(s, s1, s2);
462 | }
463 |
464 | @Override
465 | public Set zrevrangeByScore(String s, double v, double v1) {
466 | return cluster.zrevrangeByScore(s, v, v1);
467 | }
468 |
469 | @Override
470 | public Set zrangeByScore(String s, double v, double v1, int i, int i1) {
471 | return cluster.zrangeByScore(s, v, v1, i, i1);
472 | }
473 |
474 | @Override
475 | public Set zrevrangeByScore(String s, String s1, String s2) {
476 | return cluster.zrevrangeByScore(s, s1, s2);
477 | }
478 |
479 | @Override
480 | public Set zrangeByScore(String s, String s1, String s2, int i, int i1) {
481 | return cluster.zrangeByScore(s, s1, s2, i, i1);
482 | }
483 |
484 | @Override
485 | public Set zrevrangeByScore(String s, double v, double v1, int i, int i1) {
486 | return cluster.zrevrangeByScore(s, v, v1, i, i1);
487 | }
488 |
489 | @Override
490 | public Set zrangeByScoreWithScores(String s, double v, double v1) {
491 | return cluster.zrangeByScoreWithScores(s, v, v1);
492 | }
493 |
494 | @Override
495 | public Set zrevrangeByScoreWithScores(String s, double v, double v1) {
496 | return cluster.zrevrangeByScoreWithScores(s, v, v1);
497 | }
498 |
499 | @Override
500 | public Set zrangeByScoreWithScores(String s, double v, double v1, int i, int i1) {
501 | return cluster.zrangeByScoreWithScores(s, v, v1, i, i1);
502 | }
503 |
504 | @Override
505 | public Set zrevrangeByScore(String s, String s1, String s2, int i, int i1) {
506 | return cluster.zrevrangeByScore(s, s1, s2, i, i1);
507 | }
508 |
509 | @Override
510 | public Set zrangeByScoreWithScores(String s, String s1, String s2) {
511 | return cluster.zrangeByScoreWithScores(s, s1, s2);
512 | }
513 |
514 | @Override
515 | public Set zrevrangeByScoreWithScores(String s, String s1, String s2) {
516 | return cluster.zrevrangeByScoreWithScores(s, s1, s2);
517 | }
518 |
519 | @Override
520 | public Set zrangeByScoreWithScores(String s, String s1, String s2, int i, int i1) {
521 | return cluster.zrangeByScoreWithScores(s, s1, s2, i , i1);
522 | }
523 |
524 | @Override
525 | public Set zrevrangeByScoreWithScores(String s, double v, double v1, int i, int i1) {
526 | return cluster.zrevrangeByScoreWithScores(s, v, v1, i, i1);
527 | }
528 |
529 | @Override
530 | public Set zrevrangeByScoreWithScores(String s, String s1, String s2, int i, int i1) {
531 | return cluster.zrevrangeByScoreWithScores(s, s1, s2, i, i1);
532 | }
533 |
534 | @Override
535 | public Long zremrangeByRank(String s, long l, long l1) {
536 | return cluster.zremrangeByRank(s, l, l1);
537 | }
538 |
539 | @Override
540 | public Long zremrangeByScore(String s, double v, double v1) {
541 | return cluster.zremrangeByScore(s, v, v1);
542 | }
543 |
544 | @Override
545 | public Long zremrangeByScore(String s, String s1, String s2) {
546 | return cluster.zremrangeByScore(s, s1, s2);
547 | }
548 |
549 | @Override
550 | public Long zlexcount(String s, String s1, String s2) {
551 | return cluster.zlexcount(s, s1, s2);
552 | }
553 |
554 | @Override
555 | public Set zrangeByLex(String s, String s1, String s2) {
556 | return cluster.zrangeByLex(s, s1, s2);
557 | }
558 |
559 | @Override
560 | public Set zrangeByLex(String s, String s1, String s2, int i, int i1) {
561 | return cluster.zrangeByLex(s, s1, s2, i, i1);
562 | }
563 |
564 | @Override
565 | public Set zrevrangeByLex(String s, String s1, String s2) {
566 | return cluster.zrevrangeByLex(s, s1, s2);
567 | }
568 |
569 | @Override
570 | public Set zrevrangeByLex(String s, String s1, String s2, int i, int i1) {
571 | return cluster.zrevrangeByLex(s, s1, s2, i, i1);
572 | }
573 |
574 | @Override
575 | public Long zremrangeByLex(String s, String s1, String s2) {
576 | return cluster.zremrangeByLex(s, s1, s2);
577 | }
578 |
579 | @Override
580 | public Long linsert(String s, ListPosition listPosition, String s1, String s2) {
581 | return cluster.linsert(s, listPosition, s1, s2);
582 | }
583 |
584 | @Override
585 | public Long lpushx(String s, String... strings) {
586 | return cluster.lpushx(s, strings);
587 | }
588 |
589 | @Override
590 | public Long rpushx(String s, String... strings) {
591 | return cluster.rpushx(s, strings);
592 | }
593 |
594 | @Override
595 | public List blpop(int i, String s) {
596 | return cluster.blpop(i, s);
597 | }
598 |
599 | @Override
600 | public List brpop(int i, String s) {
601 | return cluster.brpop(i, s);
602 | }
603 |
604 | @Override
605 | public Long del(String s) {
606 | return cluster.del(s);
607 | }
608 |
609 | @Override
610 | public Long unlink(String s) {
611 | return cluster.unlink(s);
612 | }
613 |
614 | @Override
615 | public String echo(String s) {
616 | return cluster.echo(s);
617 | }
618 |
619 | @Override
620 | public Long move(String s, int i) {
621 | throw new UnsupportedOperationException();
622 | }
623 |
624 | @Override
625 | public Long bitcount(String s) {
626 | return cluster.bitcount(s);
627 | }
628 |
629 | @Override
630 | public Long bitcount(String s, long l, long l1) {
631 | return cluster.bitcount(s, l, l1);
632 | }
633 |
634 | @Override
635 | public Long bitpos(String s, boolean b) {
636 | throw new UnsupportedOperationException();
637 | }
638 |
639 | @Override
640 | public Long bitpos(String s, boolean b, BitPosParams bitPosParams) {
641 | throw new UnsupportedOperationException();
642 | }
643 |
644 | @Override
645 | public ScanResult> hscan(String s, String s1) {
646 | return cluster.hscan(s, s1);
647 | }
648 |
649 | @Override
650 | public ScanResult> hscan(String s, String s1, ScanParams scanParams) {
651 | throw new UnsupportedOperationException();
652 | }
653 |
654 | @Override
655 | public ScanResult sscan(String s, String s1) {
656 | return cluster.sscan(s, s1);
657 | }
658 |
659 | @Override
660 | public ScanResult zscan(String s, String s1) {
661 | return cluster.zscan(s, s1);
662 | }
663 |
664 | @Override
665 | public ScanResult zscan(String s, String s1, ScanParams scanParams) {
666 | return cluster.zscan(s.getBytes(), s1.getBytes(), scanParams);
667 | }
668 |
669 | @Override
670 | public ScanResult sscan(String s, String s1, ScanParams scanParams) {
671 | throw new UnsupportedOperationException();
672 | }
673 |
674 | @Override
675 | public Long pfadd(String s, String... strings) {
676 | return cluster.pfadd(s, strings);
677 | }
678 |
679 | @Override
680 | public long pfcount(String s) {
681 | return cluster.pfcount(s);
682 | }
683 |
684 | @Override
685 | public Long geoadd(String s, double v, double v1, String s1) {
686 | return cluster.geoadd(s, v, v1, s1);
687 | }
688 |
689 | @Override
690 | public Long geoadd(String s, Map map) {
691 | return cluster.geoadd(s, map);
692 | }
693 |
694 | @Override
695 | public Double geodist(String s, String s1, String s2) {
696 | return cluster.geodist(s, s1, s2);
697 | }
698 |
699 | @Override
700 | public Double geodist(String s, String s1, String s2, GeoUnit geoUnit) {
701 | return cluster.geodist(s, s1, s2, geoUnit);
702 | }
703 |
704 | @Override
705 | public List geohash(String s, String... strings) {
706 | return cluster.geohash(s, strings);
707 | }
708 |
709 | @Override
710 | public List geopos(String s, String... strings) {
711 | return cluster.geopos(s, strings);
712 | }
713 |
714 | @Override
715 | public List georadius(String s, double v, double v1, double v2, GeoUnit geoUnit) {
716 | return cluster.georadius(s, v, v1, v2, geoUnit);
717 | }
718 |
719 | @Override
720 | public List georadiusReadonly(String s, double v, double v1, double v2, GeoUnit geoUnit) {
721 | return cluster.georadiusReadonly(s, v, v1, v2, geoUnit);
722 | }
723 |
724 | @Override
725 | public List georadius(String s, double v, double v1, double v2, GeoUnit geoUnit, GeoRadiusParam geoRadiusParam) {
726 | return cluster.georadiusReadonly(s, v, v1, v2, geoUnit, geoRadiusParam);
727 | }
728 |
729 | @Override
730 | public List georadiusReadonly(String s, double v, double v1, double v2, GeoUnit geoUnit, GeoRadiusParam geoRadiusParam) {
731 | return cluster.georadiusReadonly(s, v, v1, v2, geoUnit, geoRadiusParam);
732 | }
733 |
734 | @Override
735 | public List georadiusByMember(String s, String s1, double v, GeoUnit geoUnit) {
736 | return cluster.georadiusByMember(s, s1, v, geoUnit);
737 | }
738 |
739 | @Override
740 | public List georadiusByMemberReadonly(String s, String s1, double v, GeoUnit geoUnit) {
741 | return cluster.georadiusByMemberReadonly(s, s1, v, geoUnit);
742 | }
743 |
744 | @Override
745 | public List georadiusByMember(String s, String s1, double v, GeoUnit geoUnit, GeoRadiusParam geoRadiusParam) {
746 | return cluster.georadiusByMember(s, s1, v, geoUnit, geoRadiusParam);
747 | }
748 |
749 | @Override
750 | public List georadiusByMemberReadonly(String s, String s1, double v, GeoUnit geoUnit, GeoRadiusParam geoRadiusParam) {
751 | return cluster.georadiusByMemberReadonly(s, s1, v, geoUnit, geoRadiusParam);
752 | }
753 |
754 | @Override
755 | public List bitfield(String s, String... strings) {
756 | return cluster.bitfield(s, strings);
757 | }
758 |
759 | @Override
760 | public Long hstrlen(String s, String s1) {
761 | return cluster.hstrlen(s, s1);
762 | }
763 |
764 |
765 | @Override
766 | public Tuple zpopmax(String key) {
767 | return cluster.zpopmax(key);
768 | }
769 |
770 | @Override
771 | public Set zpopmax(String key, int count) {
772 | return cluster.zpopmax(key, count);
773 | }
774 |
775 | @Override
776 | public Tuple zpopmin(String key) {
777 | return cluster.zpopmin(key);
778 | }
779 |
780 | @Override
781 | public Set zpopmin(String key, int count) {
782 | return cluster.zpopmin(key, count);
783 | }
784 |
785 | @Override
786 | public List bitfieldReadonly(String key, String... arguments) {
787 | return cluster.bitfieldReadonly(key, arguments);
788 | }
789 |
790 | @Override
791 | public StreamEntryID xadd(String key, StreamEntryID id, Map hash) {
792 | return cluster.xadd(key, id, hash);
793 | }
794 |
795 | @Override
796 | public StreamEntryID xadd(String key, StreamEntryID id, Map hash, long maxLen, boolean approximateLength) {
797 | return cluster.xadd(key, id, hash, maxLen, approximateLength);
798 | }
799 |
800 | @Override
801 | public Long xlen(String key) {
802 | return cluster.xlen(key);
803 | }
804 |
805 | @Override
806 | public List xrange(String key, StreamEntryID start, StreamEntryID end, int count) {
807 | return cluster.xrange(key, start, end, count);
808 | }
809 |
810 | @Override
811 | public List xrevrange(String key, StreamEntryID end, StreamEntryID start, int count) {
812 | return cluster.xrevrange(key, end, start, count);
813 | }
814 |
815 | @Override
816 | public long xack(String key, String group, StreamEntryID... ids) {
817 | return cluster.xack(key, group, ids);
818 | }
819 |
820 | @Override
821 | public String xgroupCreate(String key, String groupname, StreamEntryID id, boolean makeStream) {
822 | return cluster.xgroupCreate(key, groupname, id, makeStream);
823 | }
824 |
825 | @Override
826 | public String xgroupSetID(String key, String groupname, StreamEntryID id) {
827 | return cluster.xgroupSetID(key, groupname, id);
828 | }
829 |
830 | @Override
831 | public long xgroupDestroy(String key, String groupname) {
832 | return cluster.xgroupDestroy(key, groupname);
833 | }
834 |
835 | @Override
836 | public Long xgroupDelConsumer(String key, String groupname, String consumername) {
837 | return cluster.xgroupDelConsumer(key, groupname, consumername);
838 | }
839 |
840 | @Override
841 | public List xpending(String key, String groupname, StreamEntryID start, StreamEntryID end, int count, String consumername) {
842 | return cluster.xpending(key, groupname, start, end, count, consumername);
843 | }
844 |
845 | @Override
846 | public long xdel(String key, StreamEntryID... ids) {
847 | return cluster.xdel(key, ids);
848 | }
849 |
850 | @Override
851 | public long xtrim(String key, long maxLen, boolean approximate) {
852 | return cluster.xtrim(key, maxLen, approximate);
853 | }
854 |
855 | @Override
856 | public List xclaim(String key, String group, String consumername, long minIdleTime, long newIdleTime, int retries, boolean force, StreamEntryID... ids) {
857 | return cluster.xclaim(key, group, consumername, minIdleTime, newIdleTime, retries, force, ids);
858 | }
859 |
860 | @Override
861 | public StreamInfo xinfoStream(String key) {
862 | throw new UnsupportedOperationException("xinfoStream not supported.");
863 | }
864 |
865 | @Override
866 | public List xinfoGroup(String key) {
867 | throw new UnsupportedOperationException("xinfoGroup not supported.");
868 | }
869 |
870 | @Override
871 | public List xinfoConsumers(String key, String group) {
872 | throw new UnsupportedOperationException("xinfoConsumers not supported");
873 | }
874 | }
875 |
--------------------------------------------------------------------------------
/src/main/java/net/joelinn/quartz/jobstore/mixin/CronTriggerMixin.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.quartz.jobstore.mixin;
2 |
3 | import com.fasterxml.jackson.annotation.JsonIgnore;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import org.quartz.CronExpression;
6 |
7 | /**
8 | * Joe Linn
9 | * 7/15/2014
10 | */
11 | public abstract class CronTriggerMixin extends TriggerMixin{
12 | @JsonIgnore
13 | public abstract String getExpressionSummary();
14 |
15 | @JsonIgnore
16 | public abstract void setCronExpression(CronExpression cron);
17 |
18 | @JsonProperty("cronExpression")
19 | public abstract void setCronExpression(String cronExpression);
20 |
21 | @JsonProperty("cronExpression")
22 | public abstract String getCronExpression();
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/net/joelinn/quartz/jobstore/mixin/HolidayCalendarMixin.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.quartz.jobstore.mixin;
2 |
3 | import com.fasterxml.jackson.annotation.JsonIgnore;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 |
6 | import java.util.Date;
7 | import java.util.SortedSet;
8 | import java.util.TreeSet;
9 |
10 | /**
11 | * @author Joe Linn
12 | * 10/30/2016
13 | */
14 | public class HolidayCalendarMixin {
15 | @JsonProperty
16 | private TreeSet dates;
17 |
18 |
19 | @JsonIgnore
20 | public SortedSet getExcludedDates() {
21 | return null;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/net/joelinn/quartz/jobstore/mixin/JobDetailMixin.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.quartz.jobstore.mixin;
2 |
3 | import com.fasterxml.jackson.annotation.JsonIgnore;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import org.quartz.JobBuilder;
6 | import org.quartz.JobDataMap;
7 | import org.quartz.JobKey;
8 |
9 | /**
10 | * Joe Linn
11 | * 7/15/2014
12 | */
13 | public abstract class JobDetailMixin {
14 | @JsonIgnore
15 | public abstract JobKey getKey();
16 |
17 | @JsonIgnore
18 | public abstract JobDataMap getJobDataMap();
19 |
20 | @JsonIgnore
21 | public abstract JobBuilder getJobBuilder();
22 |
23 | @JsonIgnore
24 | public abstract String getFullName();
25 |
26 | @JsonIgnore
27 | public abstract boolean isPersistJobDataAfterExecution();
28 |
29 | @JsonIgnore
30 | public abstract boolean isConcurrentExectionDisallowed();
31 |
32 | @JsonProperty("durable")
33 | public abstract void setDurability(boolean d);
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/net/joelinn/quartz/jobstore/mixin/TriggerMixin.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.quartz.jobstore.mixin;
2 |
3 | import com.fasterxml.jackson.annotation.JsonIgnore;
4 | import org.quartz.*;
5 |
6 | import java.util.Date;
7 |
8 | /**
9 | * Joe Linn
10 | * 7/15/2014
11 | */
12 | public abstract class TriggerMixin {
13 | @JsonIgnore
14 | public abstract TriggerBuilder getTriggerBuilder();
15 |
16 | @JsonIgnore
17 | public abstract JobDataMap getJobDataMap();
18 |
19 | @JsonIgnore
20 | public abstract JobKey getJobKey();
21 |
22 | @JsonIgnore
23 | public abstract TriggerKey getKey();
24 |
25 | @JsonIgnore
26 | public abstract String getFullName();
27 |
28 | @JsonIgnore
29 | public abstract String getFullJobName();
30 |
31 | @JsonIgnore
32 | public abstract Date getFinalFireTime();
33 |
34 | @JsonIgnore
35 | public abstract ScheduleBuilder getScheduleBuilder();
36 | }
37 |
--------------------------------------------------------------------------------
/src/test/java/net/joelinn/junit/Retry.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.junit;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * @author Joe Linn
10 | * 2/19/2018
11 | */
12 | @Retention(RetentionPolicy.RUNTIME)
13 | @Target(ElementType.METHOD)
14 | public @interface Retry {
15 | int value() default 1;
16 | }
17 |
--------------------------------------------------------------------------------
/src/test/java/net/joelinn/junit/RetryRule.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.junit;
2 |
3 | import org.junit.rules.MethodRule;
4 | import org.junit.runners.model.FrameworkMethod;
5 | import org.junit.runners.model.Statement;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 |
9 | /**
10 | * @author Joe Linn
11 | * 2/19/2018
12 | */
13 | public class RetryRule implements MethodRule {
14 | private static final Logger log = LoggerFactory.getLogger(RetryRule.class);
15 |
16 | @Override
17 | public Statement apply(final Statement base, final FrameworkMethod method, Object target) {
18 | return new Statement() {
19 | @Override
20 | public void evaluate() throws Throwable {
21 | try {
22 | base.evaluate();
23 | } catch (Throwable t) {
24 | Retry retry = method.getAnnotation(Retry.class);
25 | if (retry != null) {
26 | log.warn("Test " + method.getName() + " failed initial run. Retrying.", t);
27 | for (int i = 1; i <= retry.value(); i++) {
28 | try {
29 | base.evaluate();
30 | } catch (Throwable innerThrowable) {
31 | if (i >= retry.value()) {
32 | throw innerThrowable;
33 | } else {
34 | log.warn("Test " + method.getName() + " failed on retry " + i, innerThrowable);
35 | }
36 | }
37 | }
38 | } else {
39 | throw t;
40 | }
41 | }
42 | }
43 | };
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/test/java/net/joelinn/quartz/BaseIntegrationTest.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.quartz;
2 |
3 | import com.google.common.base.Strings;
4 | import net.jodah.concurrentunit.Waiter;
5 | import net.joelinn.quartz.jobstore.RedisJobStore;
6 | import org.junit.After;
7 | import org.junit.Before;
8 | import org.quartz.*;
9 | import org.quartz.impl.StdSchedulerFactory;
10 | import org.quartz.simpl.PropertySettingJobFactory;
11 | import org.slf4j.Logger;
12 | import org.slf4j.LoggerFactory;
13 | import redis.clients.jedis.Jedis;
14 | import redis.clients.jedis.JedisPool;
15 | import redis.clients.jedis.util.Pool;
16 | import redis.embedded.RedisServer;
17 |
18 | import java.util.Properties;
19 | import java.util.concurrent.atomic.AtomicInteger;
20 |
21 | import static net.joelinn.quartz.TestUtils.getPort;
22 |
23 | /**
24 | * @author Joe Linn
25 | * 12/4/2016
26 | */
27 | public abstract class BaseIntegrationTest {
28 | private static final Logger log = LoggerFactory.getLogger(BaseIntegrationTest.class);
29 |
30 | protected RedisServer redisServer;
31 | protected Scheduler scheduler;
32 | protected Pool jedisPool;
33 |
34 | protected int port;
35 | protected static final String HOST = "localhost";
36 |
37 |
38 | @Before
39 | public void setUp() throws Exception {
40 | port = getPort();
41 | redisServer = RedisServer.builder()
42 | .port(port)
43 | .build();
44 | redisServer.start();
45 |
46 | jedisPool = new JedisPool(HOST, port);
47 |
48 |
49 | scheduler = new StdSchedulerFactory(schedulerConfig(HOST, port)).getScheduler();
50 | scheduler.start();
51 | }
52 |
53 |
54 | protected Properties schedulerConfig(String host, int port) {
55 | Properties config = new Properties();
56 | config.setProperty(StdSchedulerFactory.PROP_JOB_STORE_CLASS, RedisJobStore.class.getName());
57 | config.setProperty("org.quartz.jobStore.host", host);
58 | config.setProperty("org.quartz.jobStore.port", String.valueOf(port));
59 | config.setProperty("org.quartz.threadPool.threadCount", "1");
60 | config.setProperty("org.quartz.jobStore.misfireThreshold", "500");
61 | config.setProperty(StdSchedulerFactory.PROP_SCHED_SKIP_UPDATE_CHECK, "true");
62 | return config;
63 | }
64 |
65 |
66 | @After
67 | public void tearDown() throws Exception {
68 | scheduler.shutdown(true);
69 | if (jedisPool != null) {
70 | jedisPool.close();
71 | }
72 | redisServer.stop();
73 | }
74 |
75 |
76 | public static class DataJob implements Job {
77 | protected Pool jedisPool;
78 |
79 | public void setJedisPool(Pool jedisPool) {
80 | this.jedisPool = jedisPool;
81 | }
82 |
83 | @Override
84 | public void execute(JobExecutionContext context) throws JobExecutionException {
85 | String foo = context.getTrigger().getJobDataMap().getString("foo");
86 | if (!Strings.isNullOrEmpty(foo)) {
87 | try (Jedis jedis = jedisPool.getResource()) {
88 | jedis.set("foo", foo);
89 | }
90 | } else {
91 | log.error("Null or empty string retrieved from Redis.");
92 | }
93 | }
94 | }
95 |
96 |
97 | public static class SleepJob implements Job {
98 | @Override
99 | public void execute(JobExecutionContext context) throws JobExecutionException {
100 | try {
101 | Thread.sleep(1500);
102 | } catch (InterruptedException e) {
103 | throw new JobExecutionException("Interrupted while sleeping.", e);
104 | }
105 | }
106 | }
107 |
108 |
109 | @DisallowConcurrentExecution
110 | public static class SingletonSleepJob extends SleepJob {
111 | public static final AtomicInteger currentlyExecuting = new AtomicInteger(0);
112 | public static final AtomicInteger concurrentExecutions = new AtomicInteger(0);
113 |
114 | @Override
115 | public void execute(JobExecutionContext context) throws JobExecutionException {
116 | log.info("Starting job: " + context.getJobDetail().getKey() + " due to trigger " + context.getTrigger().getKey());
117 | if (currentlyExecuting.incrementAndGet() > 1) {
118 | log.error("Concurrent execution detected!!");
119 | concurrentExecutions.incrementAndGet();
120 | throw new JobExecutionException("Concurrent execution not allowed!");
121 | }
122 | try {
123 | Thread.sleep(1000); // add some extra sleep time to ensure that concurrent execution will be attempted
124 | } catch (InterruptedException e) {
125 | throw new JobExecutionException("Interrupted while sleeping.", e);
126 | }
127 | super.execute(context);
128 | currentlyExecuting.decrementAndGet();
129 | }
130 | }
131 |
132 |
133 | protected class CompleteListener implements TriggerListener {
134 | private final Waiter waiter;
135 |
136 | protected CompleteListener(Waiter waiter) {
137 | this.waiter = waiter;
138 | }
139 |
140 | @Override
141 | public String getName() {
142 | return "Inigo Montoya";
143 | }
144 |
145 | @Override
146 | public void triggerFired(Trigger trigger, JobExecutionContext context) {
147 |
148 | }
149 |
150 | @Override
151 | public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
152 | return false;
153 | }
154 |
155 | @Override
156 | public void triggerMisfired(Trigger trigger) {
157 |
158 | }
159 |
160 | @Override
161 | public void triggerComplete(Trigger trigger, JobExecutionContext context, Trigger.CompletedExecutionInstruction triggerInstructionCode) {
162 | waiter.resume();
163 | }
164 | }
165 |
166 |
167 | protected class MisfireListener implements TriggerListener {
168 | private final Waiter waiter;
169 |
170 | protected MisfireListener(Waiter waiter) {
171 | this.waiter = waiter;
172 | }
173 |
174 | @Override
175 | public String getName() {
176 | return "Rugen";
177 | }
178 |
179 | @Override
180 | public void triggerFired(Trigger trigger, JobExecutionContext context) {
181 |
182 | }
183 |
184 | @Override
185 | public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
186 | return false;
187 | }
188 |
189 | @Override
190 | public void triggerMisfired(Trigger trigger) {
191 | waiter.resume();
192 | }
193 |
194 | @Override
195 | public void triggerComplete(Trigger trigger, JobExecutionContext context, Trigger.CompletedExecutionInstruction triggerInstructionCode) {
196 |
197 | }
198 | }
199 |
200 |
201 | protected class RedisJobFactory extends PropertySettingJobFactory {
202 | @Override
203 | protected void setBeanProps(Object obj, JobDataMap data) throws SchedulerException {
204 | data.put("jedisPool", jedisPool);
205 | super.setBeanProps(obj, data);
206 | }
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/src/test/java/net/joelinn/quartz/BaseTest.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.quartz;
2 |
3 | import net.joelinn.quartz.jobstore.RedisJobStore;
4 | import net.joelinn.quartz.jobstore.RedisJobStoreSchema;
5 | import org.junit.After;
6 | import org.junit.Before;
7 | import org.quartz.Calendar;
8 | import org.quartz.*;
9 | import org.quartz.impl.StdSchedulerFactory;
10 | import org.quartz.impl.calendar.WeeklyCalendar;
11 | import org.quartz.impl.triggers.CronTriggerImpl;
12 | import org.quartz.spi.SchedulerSignaler;
13 | import org.slf4j.Logger;
14 | import org.slf4j.LoggerFactory;
15 | import redis.clients.jedis.Jedis;
16 | import redis.clients.jedis.JedisPool;
17 | import redis.clients.jedis.JedisPoolConfig;
18 | import redis.clients.jedis.Protocol;
19 | import redis.clients.jedis.util.Pool;
20 | import redis.embedded.RedisServer;
21 |
22 | import java.io.IOException;
23 | import java.util.*;
24 |
25 | import static net.joelinn.quartz.TestUtils.getPort;
26 | import static org.hamcrest.CoreMatchers.not;
27 | import static org.hamcrest.CoreMatchers.nullValue;
28 | import static org.hamcrest.MatcherAssert.assertThat;
29 | import static org.hamcrest.collection.IsMapContaining.hasKey;
30 | import static org.junit.Assert.assertEquals;
31 | import static org.mockito.Mockito.mock;
32 |
33 | /**
34 | * Joe Linn
35 | * 7/15/2014
36 | */
37 | public abstract class BaseTest {
38 | private static final Logger logger = LoggerFactory.getLogger(BaseTest.class);
39 |
40 | protected RedisServer redisServer;
41 |
42 | protected Pool jedisPool;
43 |
44 | protected RedisJobStore jobStore;
45 |
46 | protected RedisJobStoreSchema schema;
47 |
48 | protected Jedis jedis;
49 |
50 | protected SchedulerSignaler mockScheduleSignaler;
51 |
52 | protected int port;
53 |
54 | protected String host = "localhost";
55 |
56 | @Before
57 | public void setUpRedis() throws IOException, SchedulerConfigException {
58 | port = getPort();
59 | logger.debug("Attempting to start embedded Redis server on port " + port);
60 | redisServer = RedisServer.builder()
61 | .port(port)
62 | .build();
63 | redisServer.start();
64 | final short database = 1;
65 | JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
66 | jedisPoolConfig.setTestOnBorrow(true);
67 | jedisPool = new JedisPool(jedisPoolConfig, host, port, Protocol.DEFAULT_TIMEOUT, null, database);
68 |
69 | jobStore = new RedisJobStore();
70 | jobStore.setHost(host);
71 | jobStore.setLockTimeout(2000);
72 | jobStore.setPort(port);
73 | jobStore.setInstanceId("testJobStore1");
74 | jobStore.setDatabase(database);
75 | mockScheduleSignaler = mock(SchedulerSignaler.class);
76 | jobStore.initialize(null, mockScheduleSignaler);
77 | schema = new RedisJobStoreSchema();
78 |
79 | jedis = jedisPool.getResource();
80 | jedis.flushDB();
81 | }
82 |
83 |
84 | @After
85 | public void tearDownRedis() throws InterruptedException {
86 | jedis.close();
87 | jedisPool.destroy();
88 | redisServer.stop();
89 | }
90 |
91 | protected JobDetail getJobDetail(){
92 | return getJobDetail("testJob", "testGroup");
93 | }
94 |
95 | protected JobDetail getJobDetail(final String name, final String group){
96 | return JobBuilder.newJob(TestJob.class)
97 | .withIdentity(name, group)
98 | .usingJobData("timeout", 42)
99 | .withDescription("I am describing a job!")
100 | .build();
101 | }
102 |
103 | protected CronTriggerImpl getCronTrigger(){
104 | String cron = "0/5 * * * * ?";
105 | return (CronTriggerImpl) TriggerBuilder.newTrigger()
106 | .forJob("testJob", "testGroup")
107 | .withIdentity("testTrigger", "testTriggerGroup")
108 | .withSchedule(CronScheduleBuilder.cronSchedule(cron))
109 | .usingJobData("timeout", 5)
110 | .withDescription("A description!")
111 | .build();
112 | }
113 |
114 | protected CronTriggerImpl getCronTrigger(final String name, final String group, final JobKey jobKey){
115 | return getCronTrigger(name, group, jobKey, "0 * * * * ?");
116 | }
117 |
118 | protected CronTriggerImpl getCronTrigger(final String name, final String group, final JobKey jobKey, String cron){
119 | CronTriggerImpl trigger = (CronTriggerImpl) TriggerBuilder.newTrigger()
120 | .forJob(jobKey)
121 | .withIdentity(name, group)
122 | .withSchedule(CronScheduleBuilder.cronSchedule(cron))
123 | .usingJobData("timeout", 5)
124 | .withDescription("A description!")
125 | .build();
126 | WeeklyCalendar calendar = new WeeklyCalendar();
127 | calendar.setDaysExcluded(new boolean[]{false, false, false, false, false, false, false, false, false});
128 | trigger.computeFirstFireTime(calendar);
129 | trigger.setCalendarName("testCalendar");
130 | return trigger;
131 | }
132 |
133 | protected Calendar getCalendar(){
134 | WeeklyCalendar calendar = new WeeklyCalendar();
135 | // exclude weekends
136 | calendar.setDayExcluded(1, true);
137 | calendar.setDayExcluded(7, true);
138 | calendar.setDescription("Only run on weekdays.");
139 | return calendar;
140 | }
141 |
142 | protected Map> getJobsAndTriggers(int jobGroups, int jobsPerGroup, int triggerGroupsPerJob, int triggersPerGroup){
143 | return getJobsAndTriggers(jobGroups, jobsPerGroup, triggerGroupsPerJob, triggersPerGroup, "0 * * * * ?");
144 | }
145 |
146 | protected Map> getJobsAndTriggers(int jobGroups, int jobsPerGroup, int triggerGroupsPerJob, int triggersPerGroup, String cron){
147 | Map> jobsAndTriggers = new HashMap<>();
148 | for(int jobGroup = 0; jobGroup < jobGroups; jobGroup++){
149 | String jobGroupName = String.format("jobGroup%s", jobGroup);
150 | for(int job = 0; job < jobsPerGroup; job++){
151 | String jobName = String.format("%sjob%s", jobGroupName, job);
152 | JobDetail jobDetail = getJobDetail(jobName, jobGroupName);
153 | Set triggerSet = new HashSet<>();
154 | for(int triggerGroup = 0; triggerGroup < triggerGroupsPerJob; triggerGroup++){
155 | String triggerGroupName = String.format("%striggerGroup%s", jobName, triggerGroup);
156 | for(int trigger = 0; trigger < triggersPerGroup; trigger++){
157 | String triggerName = String.format("%strigger%s", triggerGroupName, trigger);
158 | triggerSet.add(getCronTrigger(triggerName, triggerGroupName, jobDetail.getKey(), cron));
159 | }
160 | }
161 | jobsAndTriggers.put(jobDetail, triggerSet);
162 | }
163 | }
164 | return jobsAndTriggers;
165 | }
166 |
167 | protected void storeJobAndTriggers(JobDetail job, Trigger... triggers) throws JobPersistenceException {
168 | Set triggersSet = new HashSet<>(triggers.length);
169 | Collections.addAll(triggersSet, triggers);
170 | Map> jobsAndTriggers = new HashMap<>();
171 | jobsAndTriggers.put(job, triggersSet);
172 | jobStore.storeJobsAndTriggers(jobsAndTriggers, false);
173 | }
174 |
175 | protected void testJobStore(Properties quartzProperties) throws SchedulerException {
176 | StdSchedulerFactory schedulerFactory = new StdSchedulerFactory();
177 | schedulerFactory.initialize(quartzProperties);
178 |
179 | Scheduler scheduler = schedulerFactory.getScheduler();
180 | scheduler.start();
181 |
182 | JobDetail job = getJobDetail("testJob1", "testJobGroup1");
183 | CronTriggerImpl trigger = getCronTrigger("testTrigger1", "testTriggerGroup1", job.getKey(), "0/5 * * * * ?");
184 |
185 | scheduler.scheduleJob(job, trigger);
186 |
187 | JobDetail retrievedJob = jobStore.retrieveJob(job.getKey());
188 | assertThat(retrievedJob, not(nullValue()));
189 | assertThat(retrievedJob.getJobDataMap(), hasKey("timeout"));
190 |
191 | CronTriggerImpl retrievedTrigger = (CronTriggerImpl) jobStore.retrieveTrigger(trigger.getKey());
192 | assertThat(retrievedTrigger, not(nullValue()));
193 | assertEquals(trigger.getCronExpression(), retrievedTrigger.getCronExpression());
194 |
195 | scheduler.deleteJob(job.getKey());
196 |
197 | assertThat(jobStore.retrieveJob(job.getKey()), nullValue());
198 | assertThat(jobStore.retrieveTrigger(trigger.getKey()), nullValue());
199 |
200 | scheduler.shutdown();
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/src/test/java/net/joelinn/quartz/MultiSchedulerIntegrationTest.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.quartz;
2 |
3 | import net.jodah.concurrentunit.Waiter;
4 | import net.joelinn.junit.Retry;
5 | import net.joelinn.junit.RetryRule;
6 | import net.joelinn.quartz.jobstore.RedisJobStoreSchema;
7 | import org.hamcrest.Matchers;
8 | import org.junit.After;
9 | import org.junit.Before;
10 | import org.junit.Rule;
11 | import org.junit.Test;
12 | import org.quartz.*;
13 | import org.quartz.impl.StdSchedulerFactory;
14 | import org.quartz.impl.matchers.NameMatcher;
15 | import org.quartz.simpl.SimpleInstanceIdGenerator;
16 | import org.slf4j.Logger;
17 | import org.slf4j.LoggerFactory;
18 | import redis.clients.jedis.Jedis;
19 |
20 | import java.util.List;
21 | import java.util.Properties;
22 | import java.util.concurrent.TimeUnit;
23 |
24 | import static junit.framework.TestCase.fail;
25 | import static net.joelinn.quartz.TestUtils.createCronTrigger;
26 | import static net.joelinn.quartz.TestUtils.createJob;
27 | import static org.hamcrest.MatcherAssert.assertThat;
28 | import static org.hamcrest.Matchers.*;
29 |
30 | /**
31 | * @author Joe Linn
32 | * 12/10/2016
33 | */
34 | public class MultiSchedulerIntegrationTest extends BaseIntegrationTest {
35 | private static final Logger log = LoggerFactory.getLogger(MultiSchedulerIntegrationTest.class);
36 |
37 | private static final String KEY_ID = "id";
38 |
39 |
40 | @Rule
41 | public RetryRule retryRule = new RetryRule();
42 |
43 | private Scheduler scheduler2;
44 | private RedisJobStoreSchema schema;
45 |
46 | private static String jobThreadName;
47 | private static Waiter jobStartWaiter;
48 |
49 | @Before
50 | @Override
51 | public void setUp() throws Exception {
52 | super.setUp();
53 | Properties props = schedulerConfig(HOST, port);
54 | props.setProperty(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, "second");
55 | props.setProperty(StdSchedulerFactory.PROP_SCHED_BATCH_TIME_WINDOW, "500");
56 | props.setProperty(StdSchedulerFactory.PROP_SCHED_IDLE_WAIT_TIME, "1000");
57 | scheduler2 = new StdSchedulerFactory(props).getScheduler();
58 | schema = new RedisJobStoreSchema();
59 | }
60 |
61 |
62 | @After
63 | @Override
64 | public void tearDown() throws Exception {
65 | scheduler2.shutdown(true);
66 | super.tearDown();
67 | }
68 |
69 |
70 | @Override
71 | protected Properties schedulerConfig(String host, int port) {
72 | Properties config = super.schedulerConfig(host, port);
73 | config.setProperty("org.quartz.threadPool.threadCount", "2");
74 | config.setProperty("org.quartz.scheduler.instanceId", "AUTO");
75 | config.setProperty(StdSchedulerFactory.PROP_SCHED_INSTANCE_ID_GENERATOR_CLASS, SimpleInstanceIdGenerator.class.getName());
76 | return config;
77 | }
78 |
79 | @Test
80 | @Retry(5)
81 | public void testMultipleSchedulers() throws Exception {
82 | scheduler.setJobFactory(new RedisJobFactory());
83 | scheduler2.setJobFactory(new RedisJobFactory());
84 |
85 | assertThat(scheduler.getSchedulerInstanceId(), notNullValue());
86 | assertThat(scheduler2.getSchedulerInstanceId(), notNullValue());
87 | assertThat(scheduler.getSchedulerInstanceId(), not(equalTo(scheduler2.getSchedulerInstanceId())));
88 |
89 | JobDetail job = createJob(SchedulerIDCheckingJob.class, "testJob", "group");
90 | final String triggerName = "test-trigger";
91 | CronTrigger trigger = createCronTrigger(triggerName, "group", "* * * * * ?");
92 |
93 | Waiter waiter = new Waiter();
94 | scheduler.getListenerManager().addTriggerListener(new CompleteListener(waiter), NameMatcher.triggerNameEquals(triggerName));
95 | scheduler.scheduleJob(job, trigger);
96 |
97 | waiter.await(1500);
98 |
99 | try (Jedis jedis = jedisPool.getResource()) {
100 | assertThat(jedis.get(KEY_ID), equalTo(scheduler.getSchedulerInstanceId()));
101 | }
102 |
103 | scheduler.shutdown(true);
104 | waiter = new Waiter();
105 | scheduler2.getListenerManager().addTriggerListener(new CompleteListener(waiter), NameMatcher.triggerNameEquals(triggerName));
106 | if (log.isDebugEnabled()) {
107 | log.debug("Starting second scheduler.");
108 | }
109 | scheduler2.start();
110 |
111 | waiter.await(1500);
112 |
113 | try (Jedis jedis = jedisPool.getResource()) {
114 | assertThat(jedis.get(KEY_ID), equalTo(scheduler2.getSchedulerInstanceId()));
115 | }
116 |
117 | List extends Trigger> triggers = scheduler2.getTriggersOfJob(job.getKey());
118 | assertThat(triggers, Matchers.hasSize(greaterThan(0)));
119 | for (Trigger t : triggers) {
120 | assertThat(t.getPreviousFireTime(), notNullValue());
121 | }
122 | }
123 |
124 |
125 | @Test
126 | public void testDeadScheduler() throws Exception {
127 | scheduler.setJobFactory(new RedisJobFactory());
128 | scheduler2.setJobFactory(new RedisJobFactory());
129 |
130 | assertThat(scheduler.getSchedulerInstanceId(), notNullValue());
131 | assertThat(scheduler2.getSchedulerInstanceId(), notNullValue());
132 | assertThat(scheduler.getSchedulerInstanceId(), not(equalTo(scheduler2.getSchedulerInstanceId())));
133 |
134 | JobDetail job = createJob(SchedulerIDCheckingJob.class, "testJob", "group")
135 | .getJobBuilder()
136 | .usingJobData("sleep", 15_000L)
137 | .build();
138 | final String triggerName = "test-trigger";
139 | CronTrigger trigger = createCronTrigger(triggerName, "group", "* * * * * ?");
140 |
141 | jobStartWaiter = new Waiter();
142 | scheduler.scheduleJob(job, trigger);
143 | jobStartWaiter.await(1500, TimeUnit.MILLISECONDS);
144 |
145 | scheduler.shutdown(false);
146 | getThreadByName(jobThreadName).interrupt();
147 |
148 | try (Jedis jedis = jedisPool.getResource()) {
149 | assertThat(jedis.get(KEY_ID), equalTo(scheduler.getSchedulerInstanceId()));
150 | assertThat(jedis.exists(schema.lastInstanceActiveTime()), equalTo(true));
151 | assertThat(jedis.hexists(schema.lastInstanceActiveTime(), scheduler.getSchedulerInstanceId()), equalTo(true));
152 | jedis.hset(schema.lastInstanceActiveTime(), scheduler.getSchedulerInstanceId(), String.valueOf(System.currentTimeMillis() - 5 * 60_000));
153 | assertThat("job still blocked", jedis.sismember(schema.blockedJobsSet(), schema.jobHashKey(job.getKey())), equalTo(true));
154 | }
155 |
156 | scheduler2.start();
157 | jobStartWaiter.await(1500, TimeUnit.MILLISECONDS);
158 |
159 | getThreadByName(jobThreadName).interrupt();
160 |
161 | try (Jedis jedis = jedisPool.getResource()) {
162 | assertThat(jedis.get(KEY_ID), equalTo(scheduler2.getSchedulerInstanceId()));
163 | }
164 | }
165 |
166 |
167 | private Thread getThreadByName(String threadName) {
168 | for (Thread t : Thread.getAllStackTraces().keySet()) {
169 | if (t.getName().equals(threadName)) {
170 | return t;
171 | }
172 | }
173 | return null;
174 | }
175 |
176 |
177 | @DisallowConcurrentExecution
178 | public static class SchedulerIDCheckingJob extends DataJob {
179 | @Override
180 | public void execute(JobExecutionContext context) throws JobExecutionException {
181 | jobThreadName = Thread.currentThread().getName();
182 | try {
183 | final String schedulerID = context.getScheduler().getSchedulerInstanceId();
184 | try (Jedis jedis = jedisPool.getResource()) {
185 | if (jedis.setnx(KEY_ID, schedulerID) == 0) {
186 | // we already have an ID stored
187 | final String storedID = jedis.get(KEY_ID);
188 | if (storedID.equals(schedulerID)) {
189 | fail("The same schedule executed the job twice.");
190 | } else {
191 | jedis.set(KEY_ID, schedulerID);
192 | }
193 | }
194 | }
195 | if (log.isDebugEnabled()) {
196 | log.debug("Completed job on behalf of scheduler {} at {}", schedulerID, System.currentTimeMillis());
197 | }
198 | if (jobStartWaiter != null) {
199 | jobStartWaiter.resume();
200 | }
201 | JobDataMap dataMap = context.getMergedJobDataMap();
202 | if (dataMap.containsKey("sleep")) {
203 | try {
204 | Thread.sleep(dataMap.getLong("sleep"));
205 | } catch (InterruptedException e) {
206 | if (log.isDebugEnabled()) {
207 | log.debug("Job interrupted while sleeping.", e);
208 | }
209 | }
210 | }
211 | } catch (SchedulerException e) {
212 | log.error("Unable to obtain scheduler instance ID.", e);
213 | fail("Failed to obtain scheduler instance ID.");
214 | }
215 | }
216 | }
217 | }
218 |
--------------------------------------------------------------------------------
/src/test/java/net/joelinn/quartz/MultiThreadedIntegrationTest.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.quartz;
2 |
3 | import net.jodah.concurrentunit.Waiter;
4 | import org.junit.Test;
5 | import org.quartz.CronTrigger;
6 | import org.quartz.JobDetail;
7 | import org.quartz.impl.matchers.NameMatcher;
8 | import redis.clients.jedis.Jedis;
9 |
10 | import java.util.Properties;
11 |
12 | import static net.joelinn.quartz.TestUtils.createCronTrigger;
13 | import static net.joelinn.quartz.TestUtils.createJob;
14 | import static org.hamcrest.CoreMatchers.equalTo;
15 | import static org.hamcrest.MatcherAssert.assertThat;
16 |
17 | /**
18 | * @author Joe Linn
19 | * 10/4/2016
20 | */
21 | public class MultiThreadedIntegrationTest extends BaseIntegrationTest {
22 |
23 | @Override
24 | protected Properties schedulerConfig(String host, int port) {
25 | Properties config = super.schedulerConfig(host, port);
26 | config.setProperty("org.quartz.threadPool.threadCount", "2");
27 | return config;
28 | }
29 |
30 | @Test
31 | public void testCompleteListener() throws Exception {
32 | final String jobName = "oneJob";
33 | JobDetail jobDetail = createJob(TestJob.class, jobName, "oneGroup");
34 |
35 | final String triggerName = "trigger1";
36 | CronTrigger trigger = createCronTrigger(triggerName, "oneGroup", "* * * * * ?");
37 |
38 | Waiter waiter = new Waiter();
39 | scheduler.scheduleJob(jobDetail, trigger);
40 | scheduler.getListenerManager().addTriggerListener(new CompleteListener(waiter), NameMatcher.triggerNameEquals(triggerName));
41 |
42 | // wait for CompleteListener.triggerComplete() to be called
43 | waiter.await(1500);
44 | }
45 |
46 |
47 | @Test
48 | public void testTriggerData() throws Exception {
49 | final String jobName = "good";
50 | JobDetail jobDetail = createJob(DataJob.class, jobName, "goodGroup");
51 |
52 | final String triggerName = "trigger1";
53 | final String everySecond = "* * * * * ?";
54 | CronTrigger trigger = createCronTrigger(triggerName, "oneGroup", everySecond);
55 | trigger = trigger.getTriggerBuilder()
56 | .usingJobData("foo", "bar")
57 | .build();
58 | scheduler.setJobFactory(new RedisJobFactory());
59 | scheduler.scheduleJob(jobDetail, trigger);
60 | Waiter waiter = new Waiter();
61 | scheduler.getListenerManager().addTriggerListener(new CompleteListener(waiter), NameMatcher.triggerNameEquals(triggerName));
62 |
63 | // wait for CompleteListener.triggerComplete() to be called
64 | waiter.await(1500);
65 |
66 | try (Jedis jedis = jedisPool.getResource()) {
67 | assertThat(jedis.get("foo"), equalTo("bar"));
68 | }
69 | }
70 |
71 |
72 | @Test
73 | public void testDisallowConcurrent() throws Exception {
74 | JobDetail job1 = createJob(SingletonSleepJob.class, "job1", "group1");
75 | CronTrigger trigger1 = createCronTrigger("trigger1", "group1", "* * * * * ?");
76 | CronTrigger trigger2 = createCronTrigger("trigger2", "group2", "* * * * * ?")
77 | .getTriggerBuilder()
78 | .forJob(job1)
79 | .build();
80 |
81 | Waiter waiter = new Waiter();
82 | scheduler.getListenerManager().addTriggerListener(new CompleteListener(waiter), NameMatcher.triggerNameEquals(trigger1.getKey().getName()));
83 | scheduler.scheduleJob(job1, trigger1);
84 | //scheduler.scheduleJob(trigger2);
85 |
86 | waiter.await(6000, 2);
87 |
88 | assertThat(SingletonSleepJob.concurrentExecutions.get(), equalTo(0));
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/test/java/net/joelinn/quartz/RedisJobStoreTest.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.quartz;
2 |
3 | import net.joelinn.quartz.jobstore.RedisJobStore;
4 | import org.junit.Test;
5 | import org.quartz.JobDetail;
6 | import org.quartz.Trigger;
7 |
8 | import java.util.Map;
9 | import java.util.Properties;
10 | import java.util.Set;
11 |
12 | import static org.junit.Assert.assertEquals;
13 |
14 | /**
15 | * Joe Linn
16 | * 7/22/2014
17 | */
18 | public class RedisJobStoreTest extends BaseTest{
19 |
20 | @Test
21 | public void redisJobStoreWithScheduler() throws Exception {
22 | Properties quartzProperties = new Properties();
23 | quartzProperties.setProperty("org.quartz.scheduler.instanceName", "testScheduler");
24 | quartzProperties.setProperty("org.quartz.threadPool.threadCount", "3");
25 | quartzProperties.setProperty("org.quartz.jobStore.class", RedisJobStore.class.getName());
26 | quartzProperties.setProperty("org.quartz.jobStore.host", host);
27 | quartzProperties.setProperty("org.quartz.jobStore.port", Integer.toString(port));
28 | quartzProperties.setProperty("org.quartz.jobStore.lockTimeout", "2000");
29 | quartzProperties.setProperty("org.quartz.jobStore.database", "1");
30 |
31 | testJobStore(quartzProperties);
32 | }
33 |
34 | @Test
35 | public void clearAllSchedulingData() throws Exception {
36 | // create and store some jobs, triggers, and calendars
37 | Map> jobsAndTriggers = getJobsAndTriggers(2, 2, 2, 2);
38 | jobStore.storeJobsAndTriggers(jobsAndTriggers, false);
39 |
40 | // ensure that the jobs, triggers, and calendars were stored
41 | assertEquals(2, (long) jedis.scard(schema.jobGroupsSet()));
42 | assertEquals(4, (long) jedis.scard(schema.jobsSet()));
43 | assertEquals(8, (long) jedis.scard(schema.triggerGroupsSet()));
44 | assertEquals(16, (long) jedis.scard(schema.triggersSet()));
45 |
46 | jobStore.clearAllSchedulingData();
47 |
48 | assertEquals(0, (long) jedis.scard(schema.jobGroupsSet()));
49 | assertEquals(0, (long) jedis.scard(schema.jobsSet()));
50 | assertEquals(0, (long) jedis.scard(schema.triggerGroupsSet()));
51 | assertEquals(0, (long) jedis.scard(schema.triggersSet()));
52 | }
53 |
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/test/java/net/joelinn/quartz/RedisSentinelJobStoreTest.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.quartz;
2 |
3 |
4 | import com.google.common.base.Joiner;
5 | import net.joelinn.quartz.jobstore.RedisJobStore;
6 | import net.joelinn.quartz.jobstore.RedisJobStoreSchema;
7 | import org.junit.After;
8 | import org.junit.Before;
9 | import org.junit.Test;
10 | import org.quartz.SchedulerConfigException;
11 | import org.quartz.spi.SchedulerSignaler;
12 | import redis.clients.jedis.JedisPoolConfig;
13 | import redis.clients.jedis.JedisSentinelPool;
14 | import redis.embedded.RedisCluster;
15 | import redis.embedded.util.JedisUtil;
16 |
17 | import java.io.IOException;
18 | import java.util.Arrays;
19 | import java.util.List;
20 | import java.util.Properties;
21 | import java.util.Set;
22 |
23 | import static net.joelinn.quartz.TestUtils.getPort;
24 | import static org.mockito.Mockito.mock;
25 |
26 | public class RedisSentinelJobStoreTest extends BaseTest {
27 |
28 | private JedisSentinelPool jedisSentinelPool;
29 | private String joinedHosts;
30 | private RedisCluster redisCluster;
31 |
32 | @Before
33 | public void setUpRedis() throws IOException, SchedulerConfigException {
34 | final List sentinels = Arrays.asList(getPort(), getPort());
35 | final List group1 = Arrays.asList(getPort(), getPort());
36 | final List group2 = Arrays.asList(getPort(), getPort());
37 | //creates a cluster with 3 sentinels, quorum size of 2 and 3 replication groups, each with one master and one slave
38 | redisCluster = RedisCluster.builder().sentinelPorts(sentinels).quorumSize(2)
39 | .serverPorts(group1).replicationGroup("master1", 1)
40 | .serverPorts(group2).replicationGroup("master2", 1)
41 | .ephemeralServers().replicationGroup("master3", 1)
42 | .build();
43 | redisCluster.start();
44 |
45 |
46 | Set jedisSentinelHosts = JedisUtil.sentinelHosts(redisCluster);
47 |
48 | joinedHosts = Joiner.on(",").join(jedisSentinelHosts);
49 |
50 | final short database = 1;
51 | JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
52 | jedisPoolConfig.setTestOnBorrow(true);
53 | jedisPoolConfig.setTestOnCreate(true);
54 | jedisPoolConfig.setTestOnReturn(true);
55 | jedisPoolConfig.setMaxWaitMillis(2000);
56 | jedisPoolConfig.setMaxTotal(20);
57 | jedisPool = new JedisSentinelPool("master1", jedisSentinelHosts, jedisPoolConfig);
58 | jobStore = new RedisJobStore();
59 | jobStore.setHost(joinedHosts);
60 | jobStore.setJedisPool(jedisSentinelPool);
61 | jobStore.setLockTimeout(2000);
62 | jobStore.setMasterGroupName("master1");
63 | jobStore.setRedisSentinel(true);
64 | jobStore.setInstanceId("testJobStore1");
65 | jobStore.setDatabase(database);
66 | mockScheduleSignaler = mock(SchedulerSignaler.class);
67 | jobStore.initialize(null, mockScheduleSignaler);
68 | schema = new RedisJobStoreSchema();
69 |
70 | jedis = jedisPool.getResource();
71 | jedis.flushDB();
72 |
73 | }
74 |
75 | @After
76 | public void tearDownRedis() throws InterruptedException {
77 | if (jedis != null) {
78 | jedis.close();
79 | }
80 | if (jedisPool != null) {
81 | jedisPool.close();
82 | }
83 | redisCluster.stop();
84 | }
85 |
86 | @Test
87 | public void redisSentinelJobStoreWithScheduler() throws Exception {
88 | Properties quartzProperties = new Properties();
89 | quartzProperties.setProperty("org.quartz.scheduler.instanceName", "testScheduler");
90 | quartzProperties.setProperty("org.quartz.threadPool.threadCount", "3");
91 | quartzProperties.setProperty("org.quartz.jobStore.class", RedisJobStore.class.getName());
92 | quartzProperties.setProperty("org.quartz.jobStore.host", joinedHosts);
93 | quartzProperties.setProperty("org.quartz.jobStore.redisSentinel", String.valueOf(true));
94 | quartzProperties.setProperty("org.quartz.jobStore.masterGroupName", "master1");
95 | quartzProperties.setProperty("org.quartz.jobStore.lockTimeout", "2000");
96 | quartzProperties.setProperty("org.quartz.jobStore.database", "1");
97 | testJobStore(quartzProperties);
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/test/java/net/joelinn/quartz/SingleThreadedIntegrationTest.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.quartz;
2 |
3 | import net.jodah.concurrentunit.Waiter;
4 | import org.junit.Test;
5 | import org.quartz.CronTrigger;
6 | import org.quartz.JobDetail;
7 | import org.quartz.SimpleTrigger;
8 | import org.quartz.TriggerBuilder;
9 | import org.quartz.impl.matchers.NameMatcher;
10 |
11 | import static net.joelinn.quartz.TestUtils.createCronTrigger;
12 | import static net.joelinn.quartz.TestUtils.createJob;
13 | import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
14 |
15 | /**
16 | * @author Joe Linn
17 | * 12/4/2016
18 | */
19 | public class SingleThreadedIntegrationTest extends BaseIntegrationTest {
20 | @Test
21 | public void testMisfireListener() throws Exception {
22 | final String jobName = "oneJob";
23 | JobDetail jobDetail = createJob(TestJob.class, jobName, "oneGroup");
24 |
25 | final String triggerName = "trigger1";
26 | final String everySecond = "* * * * * ?";
27 | CronTrigger trigger = createCronTrigger(triggerName, "oneGroup", everySecond);
28 |
29 |
30 | JobDetail sleepJob = createJob(SleepJob.class, "sleepJob", "twoGroup");
31 | CronTrigger sleepTrigger = createCronTrigger("sleepTrigger", "twoGroup", everySecond);
32 | Waiter waiter = new Waiter();
33 | scheduler.scheduleJob(sleepJob, sleepTrigger);
34 | scheduler.scheduleJob(jobDetail, trigger);
35 |
36 | scheduler.getListenerManager().addTriggerListener(new MisfireListener(waiter), NameMatcher.triggerNameEquals(triggerName));
37 |
38 | // wait for MisfireListener.triggerMisfired() to be called
39 | waiter.await(2500);
40 | }
41 |
42 |
43 | @Test
44 | public void testSingleExecution() throws Exception {
45 | final String jobName = "oneJob";
46 | JobDetail jobDetail = createJob(TestJob.class, jobName, "oneGroup");
47 |
48 | SimpleTrigger trigger = TriggerBuilder.newTrigger().withSchedule(simpleSchedule().withRepeatCount(0).withIntervalInMilliseconds(200)).build();
49 |
50 | Waiter waiter = new Waiter();
51 | scheduler.getListenerManager().addTriggerListener(new CompleteListener(waiter), NameMatcher.triggerNameEquals(trigger.getKey().getName()));
52 |
53 | scheduler.scheduleJob(jobDetail, trigger);
54 |
55 | waiter.await(2000);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/test/java/net/joelinn/quartz/StoreCalendarTest.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.quartz;
2 |
3 | import com.fasterxml.jackson.core.type.TypeReference;
4 | import com.fasterxml.jackson.databind.ObjectMapper;
5 | import org.junit.Test;
6 | import org.quartz.Calendar;
7 | import org.quartz.JobDetail;
8 | import org.quartz.JobPersistenceException;
9 | import org.quartz.impl.calendar.HolidayCalendar;
10 | import org.quartz.impl.triggers.CronTriggerImpl;
11 |
12 | import java.util.Date;
13 | import java.util.HashMap;
14 | import java.util.List;
15 | import java.util.Map;
16 |
17 | import static org.hamcrest.CoreMatchers.*;
18 | import static org.hamcrest.MatcherAssert.assertThat;
19 | import static org.hamcrest.Matchers.containsInAnyOrder;
20 | import static org.hamcrest.Matchers.hasSize;
21 | import static org.hamcrest.collection.IsMapContaining.hasKey;
22 | import static org.junit.Assert.*;
23 |
24 | /**
25 | * Joe Linn
26 | * 7/17/2014
27 | */
28 | public class StoreCalendarTest extends BaseTest{
29 | @Test
30 | public void storeCalendar() throws Exception {
31 | final String calendarName = "weekdayCalendar";
32 | Calendar calendar = getCalendar();
33 |
34 | jobStore.storeCalendar(calendarName, calendar, false, false);
35 |
36 | final String calendarHashKey = schema.calendarHashKey(calendarName);
37 | Map calendarMap = jedis.hgetAll(calendarHashKey);
38 |
39 | assertThat(calendarMap, hasKey("calendar_class"));
40 | assertEquals(calendar.getClass().getName(), calendarMap.get("calendar_class"));
41 | assertThat(calendarMap, hasKey("calendar_json"));
42 |
43 | ObjectMapper mapper = new ObjectMapper();
44 |
45 | Map calendarJson = mapper.readValue(calendarMap.get("calendar_json"), new TypeReference>() {
46 | });
47 | assertThat(calendarJson, hasKey("description"));
48 | assertEquals("Only run on weekdays.", calendarJson.get("description"));
49 | }
50 |
51 | @Test
52 | public void storeCalendarWithReplace() throws Exception {
53 | final String calendarName = "weekdayCalendar";
54 | Calendar calendar = getCalendar();
55 | jobStore.storeCalendar(calendarName, calendar, true, false);
56 | jobStore.storeCalendar(calendarName, calendar, true, false);
57 | }
58 |
59 | @Test(expected = JobPersistenceException.class)
60 | public void storeCalendarNoReplace() throws Exception {
61 | final String calendarName = "weekdayCalendar";
62 | Calendar calendar = getCalendar();
63 | jobStore.storeCalendar(calendarName, calendar, false, false);
64 | jobStore.storeCalendar(calendarName, calendar, false, false);
65 | }
66 |
67 | @Test
68 | public void retrieveCalendar() throws Exception {
69 | final String calendarName = "weekdayCalendar";
70 | Calendar calendar = getCalendar();
71 | jobStore.storeCalendar(calendarName, calendar, false, false);
72 |
73 | Calendar retrievedCalendar = jobStore.retrieveCalendar(calendarName);
74 |
75 | assertEquals(calendar.getClass(), retrievedCalendar.getClass());
76 | assertEquals(calendar.getDescription(), retrievedCalendar.getDescription());
77 | long currentTime = System.currentTimeMillis();
78 | assertEquals(calendar.getNextIncludedTime(currentTime), retrievedCalendar.getNextIncludedTime(currentTime));
79 | }
80 |
81 | @Test
82 | public void getNumberOfCalendars() throws Exception {
83 | jobStore.storeCalendar("calendar1", getCalendar(), false, false);
84 | jobStore.storeCalendar("calendar1", getCalendar(), true, false);
85 | jobStore.storeCalendar("calendar2", getCalendar(), false, false);
86 |
87 | int numberOfCalendars = jobStore.getNumberOfCalendars();
88 |
89 | assertEquals(2, numberOfCalendars);
90 | }
91 |
92 | @Test
93 | public void getCalendarNames() throws Exception {
94 | List calendarNames = jobStore.getCalendarNames();
95 |
96 | assertThat(calendarNames, not(nullValue()));
97 | assertThat(calendarNames, hasSize(0));
98 |
99 | jobStore.storeCalendar("calendar1", getCalendar(), false, false);
100 | jobStore.storeCalendar("calendar2", getCalendar(), false, false);
101 |
102 | calendarNames = jobStore.getCalendarNames();
103 |
104 | assertThat(calendarNames, hasSize(2));
105 | assertThat(calendarNames, containsInAnyOrder("calendar2", "calendar1"));
106 | }
107 |
108 | @Test
109 | public void removeCalendar() throws Exception {
110 | assertFalse(jobStore.removeCalendar("foo"));
111 |
112 | jobStore.storeCalendar("calendar1", getCalendar(), false, false);
113 |
114 | assertTrue(jobStore.removeCalendar("calendar1"));
115 |
116 | assertThat(jobStore.retrieveCalendar("calendar1"), nullValue());
117 | }
118 |
119 | @Test(expected = JobPersistenceException.class)
120 | public void removeCalendarWithTrigger() throws Exception {
121 | // store trigger and job
122 | JobDetail job = getJobDetail();
123 | jobStore.storeJob(job, false);
124 | CronTriggerImpl trigger1 = getCronTrigger("trigger1", "group1", job.getKey());
125 | jobStore.storeTrigger(trigger1, false);
126 |
127 | jobStore.removeCalendar(trigger1.getCalendarName());
128 | }
129 |
130 | @Test
131 | public void holidayCalendar() throws Exception {
132 | // HolidayCalendar sets the time of any given Date to 00:00:00
133 | java.util.Calendar cal = java.util.Calendar.getInstance();
134 | cal.set(java.util.Calendar.HOUR_OF_DAY, 0);
135 | cal.set(java.util.Calendar.MINUTE, 0);
136 | cal.set(java.util.Calendar.SECOND, 0);
137 | cal.set(java.util.Calendar.MILLISECOND, 0);
138 | final Date excludedDate = cal.getTime();
139 |
140 | HolidayCalendar calendar = new HolidayCalendar();
141 | calendar.addExcludedDate(excludedDate);
142 | final String name = "holidayCalendar";
143 | jobStore.storeCalendar(name, calendar, true, true);
144 |
145 | final String calendarHashKey = schema.calendarHashKey(name);
146 | Map calendarMap = jedis.hgetAll(calendarHashKey);
147 |
148 | assertThat(calendarMap, hasKey("calendar_class"));
149 | assertThat(calendarMap.get("calendar_class"), equalTo(HolidayCalendar.class.getName()));
150 | assertThat(calendarMap, hasKey("calendar_json"));
151 | String json = calendarMap.get("calendar_json");
152 | assertThat(json, containsString("\"dates\":["));
153 | assertThat(json, not(containsString("\"excludedDates\":")));
154 |
155 | Calendar retrieved = jobStore.retrieveCalendar(name);
156 | assertThat(retrieved, notNullValue());
157 | assertThat(retrieved, instanceOf(HolidayCalendar.class));
158 | HolidayCalendar retrievedHoliday = (HolidayCalendar) retrieved;
159 | assertThat(retrievedHoliday.getExcludedDates(), hasItem(excludedDate));
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/src/test/java/net/joelinn/quartz/StoreJobTest.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.quartz;
2 |
3 | import org.junit.Test;
4 | import org.quartz.*;
5 | import org.quartz.impl.matchers.GroupMatcher;
6 | import org.quartz.impl.triggers.CronTriggerImpl;
7 |
8 | import java.util.*;
9 |
10 | import static org.hamcrest.CoreMatchers.not;
11 | import static org.hamcrest.CoreMatchers.nullValue;
12 | import static org.hamcrest.MatcherAssert.assertThat;
13 | import static org.hamcrest.Matchers.containsInAnyOrder;
14 | import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
15 | import static org.hamcrest.collection.IsMapContaining.hasKey;
16 | import static org.junit.Assert.*;
17 |
18 | /**
19 | * Joe Linn
20 | * 7/15/2014
21 | */
22 | public class StoreJobTest extends BaseTest{
23 |
24 | @Test
25 | public void storeJob() throws Exception {
26 | JobDetail testJob = getJobDetail();
27 |
28 | jobStore.storeJob(testJob, false);
29 |
30 | // ensure that the job was stored properly
31 | String jobHashKey = schema.jobHashKey(testJob.getKey());
32 | Map jobMap = jedis.hgetAll(jobHashKey);
33 |
34 | assertNotNull(jobMap);
35 | assertThat(jobMap, hasKey("name"));
36 | assertEquals("testJob", jobMap.get("name"));
37 |
38 | Map jobData = jedis.hgetAll(schema.jobDataMapHashKey(testJob.getKey()));
39 | assertNotNull(jobData);
40 | assertThat(jobData, hasKey("timeout"));
41 | assertEquals("42", jobData.get("timeout"));
42 |
43 | // ensure that job data which is not included in the current map is removed from Redis
44 | testJob.getJobDataMap().remove("timeout");
45 | testJob.getJobDataMap().put("foo", "bar");
46 | jobStore.storeJob(testJob, true);
47 | jobData = jedis.hgetAll(schema.jobDataMapHashKey(testJob.getKey()));
48 | assertNotNull(jobData);
49 | assertThat(jobData, not(hasKey("timeout")));
50 | assertThat(jobData, hasKey("foo"));
51 | }
52 |
53 | @Test(expected = ObjectAlreadyExistsException.class)
54 | public void storeJobNoReplace() throws Exception {
55 | jobStore.storeJob(getJobDetail(), false);
56 | jobStore.storeJob(getJobDetail(), false);
57 | }
58 |
59 | @Test
60 | public void storeJobWithReplace() throws Exception {
61 | jobStore.storeJob(getJobDetail(), true);
62 | jobStore.storeJob(getJobDetail(), true);
63 | }
64 |
65 | @Test
66 | public void retrieveJob() throws Exception {
67 | JobDetail testJob = getJobDetail();
68 | jobStore.storeJob(testJob, false);
69 |
70 | // retrieve the job
71 | JobDetail retrievedJob = jobStore.retrieveJob(testJob.getKey());
72 |
73 | assertEquals(testJob.getJobClass(), retrievedJob.getJobClass());
74 | assertEquals(testJob.getDescription(), retrievedJob.getDescription());
75 | JobDataMap retrievedJobJobDataMap = retrievedJob.getJobDataMap();
76 | for (String key : testJob.getJobDataMap().keySet()) {
77 | assertThat(retrievedJobJobDataMap, hasKey(key));
78 | assertEquals(String.valueOf(testJob.getJobDataMap().get(key)), retrievedJobJobDataMap.get(key));
79 | }
80 | }
81 |
82 | @Test
83 | public void retrieveNonExistentJob() throws Exception {
84 | assertThat(jobStore.retrieveJob(new JobKey("foo", "bar")), nullValue());
85 | }
86 |
87 | @Test
88 | public void removeJob() throws Exception {
89 | // attempt to remove a non-existent job
90 | assertFalse(jobStore.removeJob(JobKey.jobKey("foo", "bar")));
91 |
92 | // create and store a job with multiple triggers
93 | JobDetail job = getJobDetail("job1", "jobGroup1");
94 | CronTriggerImpl trigger1 = getCronTrigger("trigger1", "triggerGroup1", job.getKey());
95 | CronTriggerImpl trigger2 = getCronTrigger("trigger2", "triggerGroup1", job.getKey());
96 | Set triggersSet = new HashSet<>();
97 | triggersSet.add(trigger1);
98 | triggersSet.add(trigger2);
99 | Map> jobsAndTriggers = new HashMap<>();
100 | jobsAndTriggers.put(job, triggersSet);
101 | jobStore.storeJobsAndTriggers(jobsAndTriggers, false);
102 |
103 | assertTrue(jobStore.removeJob(job.getKey()));
104 |
105 | // ensure that the job and all of its triggers were removed
106 | assertThat(jobStore.retrieveJob(job.getKey()), nullValue());
107 | assertThat(jobStore.retrieveTrigger(trigger1.getKey()), nullValue());
108 | assertThat(jobStore.retrieveTrigger(trigger2.getKey()), nullValue());
109 | assertThat(jedis.get(schema.triggerHashKey(trigger1.getKey())), nullValue());
110 | }
111 |
112 | @Test
113 | public void removeJobs() throws Exception {
114 | // create and store some jobs with triggers
115 | Map> jobsAndTriggers = getJobsAndTriggers(2, 2, 2, 2);
116 | jobStore.storeJobsAndTriggers(jobsAndTriggers, false);
117 |
118 | List removeKeys = new ArrayList<>(2);
119 | for (JobDetail jobDetail : new ArrayList<>(jobsAndTriggers.keySet()).subList(0, 1)) {
120 | removeKeys.add(jobDetail.getKey());
121 | }
122 |
123 | assertTrue(jobStore.removeJobs(removeKeys));
124 |
125 | // ensure that only the proper jobs were removed
126 | for (JobKey removeKey : removeKeys) {
127 | assertThat(jobStore.retrieveJob(removeKey), nullValue());
128 | }
129 | for (JobDetail jobDetail : new ArrayList<>(jobsAndTriggers.keySet()).subList(1, 3)) {
130 | assertThat(jobStore.retrieveJob(jobDetail.getKey()), not(nullValue()));
131 | }
132 | }
133 |
134 | @Test
135 | public void getNumberOfJobs() throws Exception {
136 | jobStore.storeJob(getJobDetail("job1", "group1"), false);
137 | jobStore.storeJob(getJobDetail("job2", "group1"), false);
138 | jobStore.storeJob(getJobDetail("job3", "group2"), false);
139 |
140 | int numberOfJobs = jobStore.getNumberOfJobs();
141 |
142 | assertEquals(3, numberOfJobs);
143 | }
144 |
145 | @Test
146 | public void getJobKeys() throws Exception {
147 | jobStore.storeJob(getJobDetail("job1", "group1"), false);
148 | jobStore.storeJob(getJobDetail("job2", "group1"), false);
149 | jobStore.storeJob(getJobDetail("job3", "group2"), false);
150 |
151 | Set jobKeys = jobStore.getJobKeys(GroupMatcher.jobGroupEquals("group1"));
152 |
153 | assertThat(jobKeys, hasSize(2));
154 | assertThat(jobKeys, containsInAnyOrder(new JobKey("job1", "group1"), new JobKey("job2", "group1")));
155 |
156 | jobStore.storeJob(getJobDetail("job4", "awesomegroup1"), false);
157 |
158 | jobKeys = jobStore.getJobKeys(GroupMatcher.jobGroupContains("group"));
159 |
160 | assertThat(jobKeys, hasSize(4));
161 | assertThat(jobKeys, containsInAnyOrder(new JobKey("job1", "group1"), new JobKey("job2", "group1"),
162 | new JobKey("job4", "awesomegroup1"), new JobKey("job3", "group2")));
163 |
164 | jobKeys = jobStore.getJobKeys(GroupMatcher.jobGroupStartsWith("awe"));
165 |
166 | assertThat(jobKeys, hasSize(1));
167 | assertThat(jobKeys, containsInAnyOrder(new JobKey("job4", "awesomegroup1")));
168 |
169 | jobKeys = jobStore.getJobKeys(GroupMatcher.jobGroupEndsWith("1"));
170 |
171 | assertThat(jobKeys, hasSize(3));
172 | assertThat(jobKeys, containsInAnyOrder(new JobKey("job1", "group1"), new JobKey("job2", "group1"),
173 | new JobKey("job4", "awesomegroup1")));
174 | }
175 |
176 | @Test
177 | public void getJobGroupNames() throws Exception {
178 | List jobGroupNames = jobStore.getJobGroupNames();
179 |
180 | assertThat(jobGroupNames, not(nullValue()));
181 | assertThat(jobGroupNames, hasSize(0));
182 |
183 | jobStore.storeJob(getJobDetail("job1", "group1"), false);
184 | jobStore.storeJob(getJobDetail("job2", "group1"), false);
185 | jobStore.storeJob(getJobDetail("job3", "group2"), false);
186 |
187 | jobGroupNames = jobStore.getJobGroupNames();
188 |
189 | assertThat(jobGroupNames, hasSize(2));
190 | assertThat(jobGroupNames, containsInAnyOrder("group1", "group2"));
191 | }
192 |
193 | @Test
194 | public void pauseJob() throws Exception {
195 | // create and store a job with multiple triggers
196 | JobDetail job = getJobDetail("job1", "jobGroup1");
197 | CronTriggerImpl trigger1 = getCronTrigger("trigger1", "triggerGroup1", job.getKey());
198 | CronTriggerImpl trigger2 = getCronTrigger("trigger2", "triggerGroup1", job.getKey());
199 | Set triggersSet = new HashSet<>();
200 | triggersSet.add(trigger1);
201 | triggersSet.add(trigger2);
202 | Map> jobsAndTriggers = new HashMap<>();
203 | jobsAndTriggers.put(job, triggersSet);
204 | jobStore.storeJobsAndTriggers(jobsAndTriggers, false);
205 |
206 | // pause the job
207 | jobStore.pauseJob(job.getKey());
208 |
209 | // ensure that the job's triggers were paused
210 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger1.getKey()));
211 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger2.getKey()));
212 | }
213 |
214 | @Test
215 | public void pauseJobsEquals() throws Exception {
216 | // create and store some jobs with triggers
217 | Map> jobsAndTriggers = getJobsAndTriggers(2, 2, 2, 2);
218 | jobStore.storeJobsAndTriggers(jobsAndTriggers, false);
219 |
220 | // pause jobs from one of the groups
221 | String pausedGroupName = new ArrayList<>(jobsAndTriggers.keySet()).get(0).getKey().getGroup();
222 | jobStore.pauseJobs(GroupMatcher.jobGroupEquals(pausedGroupName));
223 |
224 | // ensure that the appropriate triggers have been paused
225 | for (Map.Entry> entry : jobsAndTriggers.entrySet()) {
226 | for (Trigger trigger : entry.getValue()) {
227 | if(entry.getKey().getKey().getGroup().equals(pausedGroupName)){
228 | Trigger.TriggerState triggerState = jobStore.getTriggerState(trigger.getKey());
229 | assertEquals(Trigger.TriggerState.PAUSED, triggerState);
230 | }
231 | else{
232 | assertEquals(Trigger.TriggerState.NORMAL, jobStore.getTriggerState(trigger.getKey()));
233 | }
234 | }
235 | }
236 | }
237 |
238 | @Test
239 | public void pauseJobsStartsWith() throws Exception {
240 | JobDetail job1 = getJobDetail("job1", "jobGroup1");
241 | storeJobAndTriggers(job1, getCronTrigger("trigger1", "triggerGroup1", job1.getKey()), getCronTrigger("trigger2", "triggerGroup1", job1.getKey()));
242 | JobDetail job2 = getJobDetail("job2", "yobGroup1");
243 | CronTriggerImpl trigger3 = getCronTrigger("trigger3", "triggerGroup3", job2.getKey());
244 | CronTriggerImpl trigger4 = getCronTrigger("trigger4", "triggerGroup4", job2.getKey());
245 | storeJobAndTriggers(job2, trigger3, trigger4);
246 |
247 | // pause jobs with groups beginning with "yob"
248 | Collection pausedJobs = jobStore.pauseJobs(GroupMatcher.jobGroupStartsWith("yob"));
249 | assertThat(pausedJobs, hasSize(1));
250 | assertThat(pausedJobs, containsInAnyOrder("yobGroup1"));
251 |
252 | // ensure that the job was added to the paused jobs set
253 | assertTrue(jedis.sismember(schema.pausedJobGroupsSet(), schema.jobGroupSetKey(job2.getKey())));
254 |
255 | // ensure that the job's triggers have been paused
256 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger3.getKey()));
257 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger4.getKey()));
258 | }
259 |
260 | @Test
261 | public void pauseJobsEndsWith() throws Exception {
262 | JobDetail job1 = getJobDetail("job1", "jobGroup1");
263 | CronTriggerImpl trigger1 = getCronTrigger("trigger1", "triggerGroup1", job1.getKey());
264 | CronTriggerImpl trigger2 = getCronTrigger("trigger2", "triggerGroup1", job1.getKey());
265 | storeJobAndTriggers(job1, trigger1, trigger2);
266 | JobDetail job2 = getJobDetail("job2", "yobGroup2");
267 | CronTriggerImpl trigger3 = getCronTrigger("trigger3", "triggerGroup3", job2.getKey());
268 | CronTriggerImpl trigger4 = getCronTrigger("trigger4", "triggerGroup4", job2.getKey());
269 | storeJobAndTriggers(job2, trigger3, trigger4);
270 |
271 | // pause job groups ending with "1"
272 | Collection pausedJobs = jobStore.pauseJobs(GroupMatcher.jobGroupEndsWith("1"));
273 | assertThat(pausedJobs, hasSize(1));
274 | assertThat(pausedJobs, containsInAnyOrder("jobGroup1"));
275 |
276 | // ensure that the job was added to the paused jobs set
277 | assertTrue(jedis.sismember(schema.pausedJobGroupsSet(), schema.jobGroupSetKey(job1.getKey())));
278 |
279 | // ensure that the job's triggers have been paused
280 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger1.getKey()));
281 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger2.getKey()));
282 | }
283 |
284 | @Test
285 | public void pauseJobsContains() throws Exception {
286 | JobDetail job1 = getJobDetail("job1", "jobGroup1");
287 | CronTriggerImpl trigger1 = getCronTrigger("trigger1", "triggerGroup1", job1.getKey());
288 | CronTriggerImpl trigger2 = getCronTrigger("trigger2", "triggerGroup1", job1.getKey());
289 | storeJobAndTriggers(job1, trigger1, trigger2);
290 | JobDetail job2 = getJobDetail("job2", "yobGroup2");
291 | CronTriggerImpl trigger3 = getCronTrigger("trigger3", "triggerGroup3", job2.getKey());
292 | CronTriggerImpl trigger4 = getCronTrigger("trigger4", "triggerGroup4", job2.getKey());
293 | storeJobAndTriggers(job2, trigger3, trigger4);
294 |
295 | // Pause job groups containing "foo". Should result in no jobs being paused.
296 | Collection pausedJobs = jobStore.pauseJobs(GroupMatcher.jobGroupContains("foo"));
297 | assertThat(pausedJobs, hasSize(0));
298 |
299 | // pause jobs containing "Group"
300 | pausedJobs = jobStore.pauseJobs(GroupMatcher.jobGroupContains("Group"));
301 | assertThat(pausedJobs, hasSize(2));
302 | assertThat(pausedJobs, containsInAnyOrder("jobGroup1", "yobGroup2"));
303 |
304 | // ensure that both jobs were added to the paused jobs set
305 | assertTrue(jedis.sismember(schema.pausedJobGroupsSet(), schema.jobGroupSetKey(job1.getKey())));
306 | assertTrue(jedis.sismember(schema.pausedJobGroupsSet(), schema.jobGroupSetKey(job2.getKey())));
307 |
308 | // ensure that all triggers were paused
309 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger1.getKey()));
310 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger2.getKey()));
311 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger3.getKey()));
312 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger4.getKey()));
313 | }
314 |
315 | @Test
316 | public void resumeJob() throws Exception {
317 | // create and store a job with multiple triggers
318 | JobDetail job = getJobDetail("job1", "jobGroup1");
319 | CronTriggerImpl trigger1 = getCronTrigger("trigger1", "triggerGroup1", job.getKey());
320 | CronTriggerImpl trigger2 = getCronTrigger("trigger2", "triggerGroup1", job.getKey());
321 | storeJobAndTriggers(job, trigger1, trigger2);
322 |
323 | // pause the job
324 | jobStore.pauseJob(job.getKey());
325 |
326 | // ensure that the job's triggers have been paused
327 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger1.getKey()));
328 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger2.getKey()));
329 |
330 | // resume the job
331 | jobStore.resumeJob(job.getKey());
332 |
333 | // ensure that the triggers have been resumed
334 | assertEquals(Trigger.TriggerState.NORMAL, jobStore.getTriggerState(trigger1.getKey()));
335 | assertEquals(Trigger.TriggerState.NORMAL, jobStore.getTriggerState(trigger2.getKey()));
336 | }
337 |
338 | @Test
339 | public void resumeJobsEquals() throws Exception {
340 | // attempt to resume jobs for a non-existent job group
341 | Collection resumedJobGroups = jobStore.resumeJobs(GroupMatcher.jobGroupEquals("foobar"));
342 | assertThat(resumedJobGroups, hasSize(0));
343 |
344 | // store some jobs with triggers
345 | Map> jobsAndTriggers = getJobsAndTriggers(2, 2, 2, 2);
346 | jobStore.storeJobsAndTriggers(jobsAndTriggers, false);
347 |
348 | // pause one of the job groups
349 | String pausedGroupName = new ArrayList<>(jobsAndTriggers.keySet()).get(0).getKey().getGroup();
350 | jobStore.pauseJobs(GroupMatcher.jobGroupEquals(pausedGroupName));
351 |
352 | // ensure that the appropriate triggers have been paused
353 | for (Map.Entry> entry : jobsAndTriggers.entrySet()) {
354 | for (Trigger trigger : entry.getValue()) {
355 | if(entry.getKey().getKey().getGroup().equals(pausedGroupName)){
356 | Trigger.TriggerState triggerState = jobStore.getTriggerState(trigger.getKey());
357 | assertEquals(Trigger.TriggerState.PAUSED, triggerState);
358 | }
359 | else{
360 | assertEquals(Trigger.TriggerState.NORMAL, jobStore.getTriggerState(trigger.getKey()));
361 | }
362 | }
363 | }
364 |
365 | // resume the paused jobs
366 | resumedJobGroups = jobStore.resumeJobs(GroupMatcher.jobGroupEquals(pausedGroupName));
367 |
368 | assertThat(resumedJobGroups, hasSize(1));
369 | assertEquals(pausedGroupName, new ArrayList<>(resumedJobGroups).get(0));
370 |
371 | for (Trigger trigger : new ArrayList<>(jobsAndTriggers.entrySet()).get(0).getValue()) {
372 | assertEquals(Trigger.TriggerState.NORMAL, jobStore.getTriggerState(trigger.getKey()));
373 | }
374 | }
375 |
376 | @Test
377 | public void resumeJobsEndsWith() throws Exception {
378 | Map> jobsAndTriggers = getJobsAndTriggers(2, 2, 2, 2);
379 | jobStore.storeJobsAndTriggers(jobsAndTriggers, false);
380 |
381 | // pause one of the job groups
382 | String pausedGroupName = new ArrayList<>(jobsAndTriggers.keySet()).get(0).getKey().getGroup();
383 | String substring = pausedGroupName.substring(pausedGroupName.length() - 1, pausedGroupName.length());
384 | Collection pausedGroups = jobStore.pauseJobs(GroupMatcher.jobGroupEndsWith(substring));
385 |
386 | assertThat(pausedGroups, hasSize(1));
387 | assertThat(pausedGroups, containsInAnyOrder(pausedGroupName));
388 |
389 | // resume the paused jobs
390 | Collection resumedGroups = jobStore.resumeJobs(GroupMatcher.jobGroupEndsWith(substring));
391 |
392 | assertThat(resumedGroups, hasSize(1));
393 | assertThat(resumedGroups, containsInAnyOrder(pausedGroupName));
394 |
395 | for (Trigger trigger : new ArrayList<>(jobsAndTriggers.entrySet()).get(0).getValue()) {
396 | assertEquals(Trigger.TriggerState.NORMAL, jobStore.getTriggerState(trigger.getKey()));
397 | }
398 | }
399 | }
400 |
--------------------------------------------------------------------------------
/src/test/java/net/joelinn/quartz/StoreTriggerTest.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.quartz;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 | import net.joelinn.quartz.jobstore.RedisJobStoreSchema;
5 | import net.joelinn.quartz.jobstore.AbstractRedisStorage;
6 | import net.joelinn.quartz.jobstore.RedisStorage;
7 | import net.joelinn.quartz.jobstore.RedisTriggerState;
8 | import org.hamcrest.MatcherAssert;
9 | import org.junit.Test;
10 | import org.quartz.*;
11 | import org.quartz.impl.calendar.WeeklyCalendar;
12 | import org.quartz.impl.matchers.GroupMatcher;
13 | import org.quartz.impl.triggers.CronTriggerImpl;
14 | import org.quartz.spi.OperableTrigger;
15 | import org.quartz.spi.SchedulerSignaler;
16 | import org.quartz.spi.TriggerFiredResult;
17 |
18 | import java.util.*;
19 |
20 | import static org.hamcrest.CoreMatchers.equalTo;
21 | import static org.hamcrest.CoreMatchers.instanceOf;
22 | import static org.hamcrest.CoreMatchers.not;
23 | import static org.hamcrest.MatcherAssert.assertThat;
24 | import static org.hamcrest.Matchers.containsInAnyOrder;
25 | import static org.hamcrest.Matchers.notNullValue;
26 | import static org.hamcrest.Matchers.nullValue;
27 | import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
28 | import static org.hamcrest.collection.IsMapContaining.hasKey;
29 | import static org.junit.Assert.*;
30 | import static org.mockito.Mockito.mock;
31 |
32 | /**
33 | * Joe Linn
34 | * 7/15/2014
35 | */
36 | public class StoreTriggerTest extends BaseTest{
37 |
38 | @Test
39 | public void storeTrigger() throws Exception {
40 | CronTriggerImpl trigger = getCronTrigger();
41 | trigger.getJobDataMap().put("foo", "bar");
42 |
43 | jobStore.storeTrigger(trigger, false);
44 |
45 | final String triggerHashKey = schema.triggerHashKey(trigger.getKey());
46 | Map triggerMap = jedis.hgetAll(triggerHashKey);
47 | assertThat(triggerMap, hasKey("description"));
48 | assertEquals(trigger.getDescription(), triggerMap.get("description"));
49 | assertThat(triggerMap, hasKey("trigger_class"));
50 | assertEquals(trigger.getClass().getName(), triggerMap.get("trigger_class"));
51 |
52 | assertTrue("The trigger hash key is not a member of the triggers set.", jedis.sismember(schema.triggersSet(), triggerHashKey));
53 | assertTrue("The trigger group set key is not a member of the trigger group set.", jedis.sismember(schema.triggerGroupsSet(), schema.triggerGroupSetKey(trigger.getKey())));
54 | assertTrue(jedis.sismember(schema.triggerGroupSetKey(trigger.getKey()), triggerHashKey));
55 | assertTrue(jedis.sismember(schema.jobTriggersSetKey(trigger.getJobKey()), triggerHashKey));
56 | String triggerDataMapHashKey = schema.triggerDataMapHashKey(trigger.getKey());
57 | MatcherAssert.assertThat(jedis.exists(triggerDataMapHashKey), equalTo(true));
58 | MatcherAssert.assertThat(jedis.hget(triggerDataMapHashKey, "foo"), equalTo("bar"));
59 | }
60 |
61 | @Test(expected = JobPersistenceException.class)
62 | public void storeTriggerNoReplace() throws Exception {
63 | jobStore.storeTrigger(getCronTrigger(), false);
64 | jobStore.storeTrigger(getCronTrigger(), false);
65 | }
66 |
67 | @Test
68 | public void storeTriggerWithReplace() throws Exception {
69 | jobStore.storeTrigger(getCronTrigger(), true);
70 | jobStore.storeTrigger(getCronTrigger(), true);
71 | }
72 |
73 | @Test
74 | public void retrieveTrigger() throws Exception {
75 | CronTriggerImpl cronTrigger = getCronTrigger();
76 | jobStore.storeJob(getJobDetail(), false);
77 | jobStore.storeTrigger(cronTrigger, false);
78 |
79 | OperableTrigger operableTrigger = jobStore.retrieveTrigger(cronTrigger.getKey());
80 |
81 | assertThat(operableTrigger, instanceOf(CronTriggerImpl.class));
82 | assertThat(operableTrigger.getFireInstanceId(), notNullValue());
83 | CronTriggerImpl retrievedTrigger = (CronTriggerImpl) operableTrigger;
84 |
85 | assertEquals(cronTrigger.getCronExpression(), retrievedTrigger.getCronExpression());
86 | assertEquals(cronTrigger.getTimeZone(), retrievedTrigger.getTimeZone());
87 | assertEquals(cronTrigger.getStartTime(), retrievedTrigger.getStartTime());
88 | }
89 |
90 | @Test
91 | public void removeTrigger() throws Exception {
92 | JobDetail job = getJobDetail();
93 | CronTriggerImpl trigger1 = getCronTrigger("trigger1", "triggerGroup", job.getKey());
94 | trigger1.getJobDataMap().put("foo", "bar");
95 | CronTriggerImpl trigger2 = getCronTrigger("trigger2", "triggerGroup", job.getKey());
96 |
97 | jobStore.storeJob(job, false);
98 | jobStore.storeTrigger(trigger1, false);
99 | jobStore.storeTrigger(trigger2, false);
100 |
101 | jobStore.removeTrigger(trigger1.getKey());
102 |
103 | // ensure that the trigger was removed, but the job was not
104 | assertThat(jobStore.retrieveTrigger(trigger1.getKey()), nullValue());
105 | assertThat(jobStore.retrieveJob(job.getKey()), not(nullValue()));
106 |
107 | // remove the second trigger
108 | jobStore.removeTrigger(trigger2.getKey());
109 |
110 | // ensure that both the trigger and job were removed
111 | assertThat(jobStore.retrieveTrigger(trigger2.getKey()), nullValue());
112 | assertThat(jobStore.retrieveJob(job.getKey()), nullValue());
113 | MatcherAssert.assertThat(jedis.exists(schema.triggerDataMapHashKey(trigger1.getKey())), equalTo(false));
114 | }
115 |
116 | @Test
117 | public void getTriggersForJob() throws Exception {
118 | JobDetail job = getJobDetail();
119 | CronTriggerImpl trigger1 = getCronTrigger("trigger1", "triggerGroup", job.getKey());
120 | CronTriggerImpl trigger2 = getCronTrigger("trigger2", "triggerGroup", job.getKey());
121 |
122 | jobStore.storeJob(job, false);
123 | jobStore.storeTrigger(trigger1, false);
124 | jobStore.storeTrigger(trigger2, false);
125 |
126 | List triggers = jobStore.getTriggersForJob(job.getKey());
127 | assertThat(triggers, hasSize(2));
128 | }
129 |
130 | @Test
131 | public void getNumberOfTriggers() throws Exception {
132 | JobDetail job = getJobDetail();
133 | jobStore.storeTrigger(getCronTrigger("trigger1", "group1", job.getKey()), false);
134 | jobStore.storeTrigger(getCronTrigger("trigger2", "group1", job.getKey()), false);
135 | jobStore.storeTrigger(getCronTrigger("trigger3", "group2", job.getKey()), false);
136 | jobStore.storeTrigger(getCronTrigger("trigger4", "group3", job.getKey()), false);
137 |
138 | int numberOfTriggers = jobStore.getNumberOfTriggers();
139 |
140 | assertEquals(4, numberOfTriggers);
141 | }
142 |
143 | @Test
144 | public void getTriggerKeys() throws Exception {
145 | JobDetail job = getJobDetail();
146 | jobStore.storeTrigger(getCronTrigger("trigger1", "group1", job.getKey()), false);
147 | jobStore.storeTrigger(getCronTrigger("trigger2", "group1", job.getKey()), false);
148 | jobStore.storeTrigger(getCronTrigger("trigger3", "group2", job.getKey()), false);
149 | jobStore.storeTrigger(getCronTrigger("trigger4", "group3", job.getKey()), false);
150 |
151 | Set triggerKeys = jobStore.getTriggerKeys(GroupMatcher.triggerGroupEquals("group1"));
152 |
153 | assertThat(triggerKeys, hasSize(2));
154 | assertThat(triggerKeys, containsInAnyOrder(new TriggerKey("trigger2", "group1"), new TriggerKey("trigger1", "group1")));
155 |
156 | jobStore.storeTrigger(getCronTrigger("trigger4", "triggergroup1", job.getKey()), false);
157 |
158 | triggerKeys = jobStore.getTriggerKeys(GroupMatcher.triggerGroupContains("group"));
159 |
160 | assertThat(triggerKeys, hasSize(5));
161 |
162 | triggerKeys = jobStore.getTriggerKeys(GroupMatcher.triggerGroupEndsWith("1"));
163 |
164 | assertThat(triggerKeys, hasSize(3));
165 | assertThat(triggerKeys, containsInAnyOrder(new TriggerKey("trigger2", "group1"),
166 | new TriggerKey("trigger1", "group1"), new TriggerKey("trigger4", "triggergroup1")));
167 |
168 | triggerKeys = jobStore.getTriggerKeys(GroupMatcher.triggerGroupStartsWith("trig"));
169 |
170 | assertThat(triggerKeys, hasSize(1));
171 | assertThat(triggerKeys, containsInAnyOrder(new TriggerKey("trigger4", "triggergroup1")));
172 | }
173 |
174 | @Test
175 | public void getTriggerGroupNames() throws Exception {
176 | List triggerGroupNames = jobStore.getTriggerGroupNames();
177 |
178 | assertThat(triggerGroupNames, not(nullValue()));
179 | assertThat(triggerGroupNames, hasSize(0));
180 |
181 | JobDetail job = getJobDetail();
182 | jobStore.storeTrigger(getCronTrigger("trigger1", "group1", job.getKey()), false);
183 | jobStore.storeTrigger(getCronTrigger("trigger2", "group1", job.getKey()), false);
184 | jobStore.storeTrigger(getCronTrigger("trigger3", "group2", job.getKey()), false);
185 | jobStore.storeTrigger(getCronTrigger("trigger4", "group3", job.getKey()), false);
186 |
187 | triggerGroupNames = jobStore.getTriggerGroupNames();
188 |
189 | assertThat(triggerGroupNames, hasSize(3));
190 | assertThat(triggerGroupNames, containsInAnyOrder("group3", "group2", "group1"));
191 | }
192 |
193 | @Test
194 | public void getTriggerState() throws Exception {
195 | SchedulerSignaler signaler = mock(SchedulerSignaler.class);
196 | AbstractRedisStorage storageDriver = new RedisStorage(new RedisJobStoreSchema(), new ObjectMapper(), signaler, "scheduler1", 2000);
197 |
198 | // attempt to retrieve the state of a non-existent trigger
199 | Trigger.TriggerState state = jobStore.getTriggerState(new TriggerKey("foobar"));
200 | assertEquals(Trigger.TriggerState.NONE, state);
201 |
202 | // store a trigger
203 | JobDetail job = getJobDetail();
204 | CronTriggerImpl cronTrigger = getCronTrigger("trigger1", "group1", job.getKey());
205 | jobStore.storeTrigger(cronTrigger, false);
206 |
207 | // the newly-stored trigger's state should be NONE
208 | state = jobStore.getTriggerState(cronTrigger.getKey());
209 | assertEquals(Trigger.TriggerState.NORMAL, state);
210 |
211 | // set the trigger's state
212 | storageDriver.setTriggerState(RedisTriggerState.WAITING, 500, schema.triggerHashKey(cronTrigger.getKey()), jedis);
213 |
214 | // the trigger's state should now be NORMAL
215 | state = jobStore.getTriggerState(cronTrigger.getKey());
216 | assertEquals(Trigger.TriggerState.NORMAL, state);
217 | }
218 |
219 | @Test
220 | public void pauseTrigger() throws Exception {
221 | SchedulerSignaler signaler = mock(SchedulerSignaler.class);
222 | AbstractRedisStorage storageDriver = new RedisStorage(new RedisJobStoreSchema(), new ObjectMapper(), signaler, "scheduler1", 2000);
223 |
224 | // store a trigger
225 | JobDetail job = getJobDetail();
226 | CronTriggerImpl cronTrigger = getCronTrigger("trigger1", "group1", job.getKey());
227 | cronTrigger.setNextFireTime(new Date(System.currentTimeMillis()));
228 | jobStore.storeTrigger(cronTrigger, false);
229 |
230 | // set the trigger's state to COMPLETED
231 | storageDriver.setTriggerState(RedisTriggerState.COMPLETED, 500, schema.triggerHashKey(cronTrigger.getKey()), jedis);
232 | jobStore.pauseTrigger(cronTrigger.getKey());
233 |
234 | // trigger's state should not have changed
235 | assertEquals(Trigger.TriggerState.COMPLETE, jobStore.getTriggerState(cronTrigger.getKey()));
236 |
237 | // set the trigger's state to BLOCKED
238 | storageDriver.setTriggerState(RedisTriggerState.BLOCKED, 500, schema.triggerHashKey(cronTrigger.getKey()), jedis);
239 | jobStore.pauseTrigger(cronTrigger.getKey());
240 |
241 | // trigger's state should be PAUSED
242 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(cronTrigger.getKey()));
243 |
244 | // set the trigger's state to ACQUIRED
245 | storageDriver.setTriggerState(RedisTriggerState.ACQUIRED, 500, schema.triggerHashKey(cronTrigger.getKey()), jedis);
246 | jobStore.pauseTrigger(cronTrigger.getKey());
247 |
248 | // trigger's state should be PAUSED
249 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(cronTrigger.getKey()));
250 | }
251 |
252 | @Test
253 | public void pauseTriggersEquals() throws Exception {
254 | // store triggers
255 | JobDetail job = getJobDetail();
256 | jobStore.storeTrigger(getCronTrigger("trigger1", "group1", job.getKey()), false);
257 | jobStore.storeTrigger(getCronTrigger("trigger2", "group1", job.getKey()), false);
258 | jobStore.storeTrigger(getCronTrigger("trigger3", "group2", job.getKey()), false);
259 | jobStore.storeTrigger(getCronTrigger("trigger4", "group3", job.getKey()), false);
260 |
261 | // pause triggers
262 | Collection pausedGroups = jobStore.pauseTriggers(GroupMatcher.triggerGroupEquals("group1"));
263 |
264 | assertThat(pausedGroups, hasSize(1));
265 | assertThat(pausedGroups, containsInAnyOrder("group1"));
266 |
267 | // ensure that the triggers were actually paused
268 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(new TriggerKey("trigger1", "group1")));
269 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(new TriggerKey("trigger2", "group1")));
270 | }
271 |
272 | @Test
273 | public void pauseTriggersStartsWith() throws Exception {
274 | JobDetail job = getJobDetail();
275 | CronTriggerImpl trigger1 = getCronTrigger("trigger1", "group1", job.getKey());
276 | CronTriggerImpl trigger2 = getCronTrigger("trigger1", "group2", job.getKey());
277 | CronTriggerImpl trigger3 = getCronTrigger("trigger1", "foogroup1", job.getKey());
278 | storeJobAndTriggers(job, trigger1, trigger2, trigger3);
279 |
280 | Collection pausedTriggerGroups = jobStore.pauseTriggers(GroupMatcher.triggerGroupStartsWith("group"));
281 |
282 | assertThat(pausedTriggerGroups, hasSize(2));
283 | assertThat(pausedTriggerGroups, containsInAnyOrder("group1", "group2"));
284 |
285 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger1.getKey()));
286 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger2.getKey()));
287 | assertEquals(Trigger.TriggerState.NORMAL, jobStore.getTriggerState(trigger3.getKey()));
288 | }
289 |
290 | @Test
291 | public void pauseTriggersEndsWith() throws Exception {
292 | JobDetail job = getJobDetail();
293 | CronTriggerImpl trigger1 = getCronTrigger("trigger1", "group1", job.getKey());
294 | CronTriggerImpl trigger2 = getCronTrigger("trigger1", "group2", job.getKey());
295 | CronTriggerImpl trigger3 = getCronTrigger("trigger1", "foogroup1", job.getKey());
296 | storeJobAndTriggers(job, trigger1, trigger2, trigger3);
297 |
298 | Collection pausedGroups = jobStore.pauseTriggers(GroupMatcher.triggerGroupEndsWith("oup1"));
299 |
300 | assertThat(pausedGroups, hasSize(2));
301 | assertThat(pausedGroups, containsInAnyOrder("group1", "foogroup1"));
302 |
303 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger1.getKey()));
304 | assertEquals(Trigger.TriggerState.NORMAL, jobStore.getTriggerState(trigger2.getKey()));
305 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger3.getKey()));
306 | }
307 |
308 | @Test
309 | public void resumeTrigger() throws Exception {
310 | // create and store a job and trigger
311 | JobDetail job = getJobDetail();
312 | jobStore.storeJob(job, false);
313 | CronTriggerImpl trigger = getCronTrigger("trigger1", "group1", job.getKey());
314 | trigger.computeFirstFireTime(new WeeklyCalendar());
315 | jobStore.storeTrigger(trigger, false);
316 |
317 | // pause the trigger
318 | jobStore.pauseTrigger(trigger.getKey());
319 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger.getKey()));
320 |
321 | // resume the trigger
322 | jobStore.resumeTrigger(trigger.getKey());
323 | // the trigger state should now be NORMAL
324 | assertEquals(Trigger.TriggerState.NORMAL, jobStore.getTriggerState(trigger.getKey()));
325 |
326 | // attempt to resume the trigger, again
327 | jobStore.resumeTrigger(trigger.getKey());
328 | // the trigger state should not have changed
329 | assertEquals(Trigger.TriggerState.NORMAL, jobStore.getTriggerState(trigger.getKey()));
330 | }
331 |
332 | @Test
333 | public void resumeTriggersEquals() throws Exception {
334 | // store triggers and job
335 | JobDetail job = getJobDetail();
336 | CronTriggerImpl trigger1 = getCronTrigger("trigger1", "group1", job.getKey());
337 | CronTriggerImpl trigger2 = getCronTrigger("trigger2", "group1", job.getKey());
338 | CronTriggerImpl trigger3 = getCronTrigger("trigger3", "group2", job.getKey());
339 | CronTriggerImpl trigger4 = getCronTrigger("trigger4", "group3", job.getKey());
340 | storeJobAndTriggers(job, trigger1, trigger2, trigger3, trigger4);
341 |
342 | // pause triggers
343 | Collection pausedGroups = jobStore.pauseTriggers(GroupMatcher.triggerGroupEquals("group1"));
344 |
345 | assertThat(pausedGroups, hasSize(1));
346 | assertThat(pausedGroups, containsInAnyOrder("group1"));
347 |
348 | // ensure that the triggers were actually paused
349 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(new TriggerKey("trigger1", "group1")));
350 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(new TriggerKey("trigger2", "group1")));
351 |
352 | // resume triggers
353 | Collection resumedGroups = jobStore.resumeTriggers(GroupMatcher.triggerGroupEquals("group1"));
354 |
355 | assertThat(resumedGroups, hasSize(1));
356 | assertThat(resumedGroups, containsInAnyOrder("group1"));
357 |
358 | // ensure that the triggers were resumed
359 | assertEquals(Trigger.TriggerState.NORMAL, jobStore.getTriggerState(new TriggerKey("trigger1", "group1")));
360 | assertEquals(Trigger.TriggerState.NORMAL, jobStore.getTriggerState(new TriggerKey("trigger2", "group1")));
361 | }
362 |
363 | @Test
364 | public void resumeTriggersEndsWith() throws Exception {
365 | JobDetail job = getJobDetail();
366 | CronTriggerImpl trigger1 = getCronTrigger("trigger1", "group1", job.getKey());
367 | CronTriggerImpl trigger2 = getCronTrigger("trigger2", "group1", job.getKey());
368 | CronTriggerImpl trigger3 = getCronTrigger("trigger3", "group2", job.getKey());
369 | CronTriggerImpl trigger4 = getCronTrigger("trigger4", "group3", job.getKey());
370 | storeJobAndTriggers(job, trigger1, trigger2, trigger3, trigger4);
371 |
372 | Collection pausedGroups = jobStore.pauseTriggers(GroupMatcher.triggerGroupEndsWith("1"));
373 |
374 | assertThat(pausedGroups, hasSize(1));
375 | assertThat(pausedGroups, containsInAnyOrder("group1"));
376 |
377 | // ensure that the triggers were actually paused
378 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger1.getKey()));
379 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger2.getKey()));
380 |
381 | // resume triggers
382 | Collection resumedGroups = jobStore.resumeTriggers(GroupMatcher.triggerGroupEndsWith("1"));
383 |
384 | assertThat(resumedGroups, hasSize(1));
385 | assertThat(resumedGroups, containsInAnyOrder("group1"));
386 |
387 | // ensure that the triggers were actually resumed
388 | assertEquals(Trigger.TriggerState.NORMAL, jobStore.getTriggerState(trigger1.getKey()));
389 | assertEquals(Trigger.TriggerState.NORMAL, jobStore.getTriggerState(trigger2.getKey()));
390 | }
391 |
392 | @Test
393 | public void resumeTriggersStartsWith() throws Exception {
394 | JobDetail job = getJobDetail();
395 | CronTriggerImpl trigger1 = getCronTrigger("trigger1", "mygroup1", job.getKey());
396 | CronTriggerImpl trigger2 = getCronTrigger("trigger2", "group1", job.getKey());
397 | CronTriggerImpl trigger3 = getCronTrigger("trigger3", "group2", job.getKey());
398 | CronTriggerImpl trigger4 = getCronTrigger("trigger4", "group3", job.getKey());
399 | storeJobAndTriggers(job, trigger1, trigger2, trigger3, trigger4);
400 |
401 | Collection pausedGroups = jobStore.pauseTriggers(GroupMatcher.triggerGroupStartsWith("my"));
402 |
403 | assertThat(pausedGroups, hasSize(1));
404 | assertThat(pausedGroups, containsInAnyOrder("mygroup1"));
405 |
406 | // ensure that the triggers were actually paused
407 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger1.getKey()));
408 |
409 | // resume triggers
410 | Collection resumedGroups = jobStore.resumeTriggers(GroupMatcher.triggerGroupStartsWith("my"));
411 |
412 | assertThat(resumedGroups, hasSize(1));
413 | assertThat(resumedGroups, containsInAnyOrder("mygroup1"));
414 |
415 | // ensure that the triggers were actually resumed
416 | assertEquals(Trigger.TriggerState.NORMAL, jobStore.getTriggerState(trigger1.getKey()));
417 | }
418 |
419 | @Test
420 | public void getPausedTriggerGroups() throws Exception {
421 | // store triggers
422 | JobDetail job = getJobDetail();
423 | jobStore.storeTrigger(getCronTrigger("trigger1", "group1", job.getKey()), false);
424 | jobStore.storeTrigger(getCronTrigger("trigger2", "group1", job.getKey()), false);
425 | jobStore.storeTrigger(getCronTrigger("trigger3", "group2", job.getKey()), false);
426 | jobStore.storeTrigger(getCronTrigger("trigger4", "group3", job.getKey()), false);
427 |
428 | // pause triggers
429 | Collection pausedGroups = jobStore.pauseTriggers(GroupMatcher.triggerGroupEquals("group1"));
430 | pausedGroups.addAll(jobStore.pauseTriggers(GroupMatcher.triggerGroupEquals("group3")));
431 |
432 | assertThat(pausedGroups, hasSize(2));
433 | assertThat(pausedGroups, containsInAnyOrder("group3", "group1"));
434 |
435 | // retrieve paused trigger groups
436 | Set pausedTriggerGroups = jobStore.getPausedTriggerGroups();
437 | assertThat(pausedTriggerGroups, hasSize(2));
438 | assertThat(pausedTriggerGroups, containsInAnyOrder("group1", "group3"));
439 | }
440 |
441 | @Test
442 | public void pauseAndResumeAll() throws Exception {
443 | // store some jobs with triggers
444 | Map> jobsAndTriggers = getJobsAndTriggers(2, 2, 2, 2);
445 | jobStore.storeJobsAndTriggers(jobsAndTriggers, false);
446 |
447 | // ensure that all triggers are in the NORMAL state
448 | for (Map.Entry> jobDetailSetEntry : jobsAndTriggers.entrySet()) {
449 | for (Trigger trigger : jobDetailSetEntry.getValue()) {
450 | assertEquals(Trigger.TriggerState.NORMAL, jobStore.getTriggerState(trigger.getKey()));
451 | }
452 | }
453 |
454 | jobStore.pauseAll();
455 |
456 | // ensure that all triggers were paused
457 | for (Map.Entry> jobDetailSetEntry : jobsAndTriggers.entrySet()) {
458 | for (Trigger trigger : jobDetailSetEntry.getValue()) {
459 | assertEquals(Trigger.TriggerState.PAUSED, jobStore.getTriggerState(trigger.getKey()));
460 | }
461 | }
462 |
463 | // resume all triggers
464 | jobStore.resumeAll();
465 |
466 | // ensure that all triggers are again in the NORMAL state
467 | for (Map.Entry> jobDetailSetEntry : jobsAndTriggers.entrySet()) {
468 | for (Trigger trigger : jobDetailSetEntry.getValue()) {
469 | assertEquals(Trigger.TriggerState.NORMAL, jobStore.getTriggerState(trigger.getKey()));
470 | }
471 | }
472 | }
473 |
474 | @Test
475 | @SuppressWarnings("unchecked")
476 | public void triggersFired() throws Exception {
477 | // store some jobs with triggers
478 | Map> jobsAndTriggers = getJobsAndTriggers(2, 2, 2, 2, "* * * * * ?");
479 |
480 | // disallow concurrent execution for one of the jobs
481 | Map.Entry> firstEntry = jobsAndTriggers.entrySet().iterator().next();
482 | JobDetail nonConcurrentKey = firstEntry.getKey().getJobBuilder().ofType(TestJobNonConcurrent.class).build();
483 | Set extends Trigger> nonConcurrentTriggers = firstEntry.getValue();
484 | jobsAndTriggers.remove(firstEntry.getKey());
485 | jobsAndTriggers.put(nonConcurrentKey, nonConcurrentTriggers);
486 |
487 | jobStore.storeCalendar("testCalendar", new WeeklyCalendar(), false, true);
488 | jobStore.storeJobsAndTriggers(jobsAndTriggers, false);
489 |
490 | List acquiredTriggers = jobStore.acquireNextTriggers(System.currentTimeMillis() - 1000, 500, 4000);
491 | assertThat(acquiredTriggers, hasSize(13));
492 |
493 | int lockedTriggers = 0;
494 | // ensure that all triggers are in the NORMAL state and have been ACQUIRED
495 | for (Map.Entry> jobDetailSetEntry : jobsAndTriggers.entrySet()) {
496 | for (Trigger trigger : jobDetailSetEntry.getValue()) {
497 | assertEquals(Trigger.TriggerState.NORMAL, jobStore.getTriggerState(trigger.getKey()));
498 | String triggerHashKey = schema.triggerHashKey(trigger.getKey());
499 | if (jobDetailSetEntry.getKey().isConcurrentExectionDisallowed()) {
500 | if (jedis.zscore(schema.triggerStateKey(RedisTriggerState.ACQUIRED), triggerHashKey) != null) {
501 | assertThat("acquired trigger should be locked", jedis.get(schema.triggerLockKey(schema.triggerKey(triggerHashKey))), notNullValue());
502 | lockedTriggers++;
503 | } else {
504 | assertThat("non-acquired trigger should not be locked", jedis.get(schema.triggerLockKey(schema.triggerKey(triggerHashKey))), nullValue());
505 | }
506 | } else {
507 | assertThat(jedis.zscore(schema.triggerStateKey(RedisTriggerState.ACQUIRED), triggerHashKey), not(nullValue()));
508 | }
509 | }
510 | }
511 |
512 | assertThat(lockedTriggers, equalTo(1));
513 |
514 | Set extends OperableTrigger> triggers = (Set extends OperableTrigger>) new ArrayList<>(jobsAndTriggers.entrySet()).get(0).getValue();
515 | List triggerFiredResults = jobStore.triggersFired(new ArrayList<>(triggers));
516 | assertThat("exactly one trigger fired for job with concurrent execution disallowed", triggerFiredResults, hasSize(1));
517 |
518 | triggers = (Set extends OperableTrigger>) new ArrayList<>(jobsAndTriggers.entrySet()).get(1).getValue();
519 | triggerFiredResults = jobStore.triggersFired(new ArrayList<>(triggers));
520 | assertThat("all triggers fired for job with concurrent execution allowed", triggerFiredResults, hasSize(4));
521 | }
522 |
523 | @Test
524 | public void replaceTrigger() throws Exception {
525 | assertFalse(jobStore.replaceTrigger(TriggerKey.triggerKey("foo", "bar"), getCronTrigger()));
526 |
527 | // store triggers and job
528 | JobDetail job = getJobDetail();
529 | CronTriggerImpl trigger1 = getCronTrigger("trigger1", "group1", job.getKey());
530 | CronTriggerImpl trigger2 = getCronTrigger("trigger2", "group1", job.getKey());
531 | storeJobAndTriggers(job, trigger1, trigger2);
532 |
533 | CronTriggerImpl newTrigger = getCronTrigger("newTrigger", "group1", job.getKey());
534 |
535 | assertTrue(jobStore.replaceTrigger(trigger1.getKey(), newTrigger));
536 |
537 | // ensure that the proper trigger was replaced
538 | assertThat(jobStore.retrieveTrigger(trigger1.getKey()), nullValue());
539 |
540 | List jobTriggers = jobStore.getTriggersForJob(job.getKey());
541 |
542 | assertThat(jobTriggers, hasSize(2));
543 | List jobTriggerKeys = new ArrayList<>(jobTriggers.size());
544 | for (OperableTrigger jobTrigger : jobTriggers) {
545 | jobTriggerKeys.add(jobTrigger.getKey());
546 | }
547 |
548 | assertThat(jobTriggerKeys, containsInAnyOrder(trigger2.getKey(), newTrigger.getKey()));
549 | }
550 |
551 | @Test
552 | public void replaceTriggerSingleTriggerNonDurableJob() throws Exception {
553 | // store trigger and job
554 | JobDetail job = getJobDetail();
555 | CronTriggerImpl trigger1 = getCronTrigger("trigger1", "group1", job.getKey());
556 | storeJobAndTriggers(job, trigger1);
557 |
558 | CronTriggerImpl newTrigger = getCronTrigger("newTrigger", "group1", job.getKey());
559 |
560 | assertTrue(jobStore.replaceTrigger(trigger1.getKey(), newTrigger));
561 |
562 | // ensure that the proper trigger was replaced
563 | assertThat(jobStore.retrieveTrigger(trigger1.getKey()), nullValue());
564 |
565 | List jobTriggers = jobStore.getTriggersForJob(job.getKey());
566 |
567 | assertThat(jobTriggers, hasSize(1));
568 |
569 | // ensure that the job still exists
570 | assertThat(jobStore.retrieveJob(job.getKey()), not(nullValue()));
571 | }
572 |
573 | @Test(expected = JobPersistenceException.class)
574 | public void replaceTriggerWithDifferentJob() throws Exception {
575 | // store triggers and job
576 | JobDetail job = getJobDetail();
577 | jobStore.storeJob(job, false);
578 | CronTriggerImpl trigger1 = getCronTrigger("trigger1", "group1", job.getKey());
579 | jobStore.storeTrigger(trigger1, false);
580 | CronTriggerImpl trigger2 = getCronTrigger("trigger2", "group1", job.getKey());
581 | jobStore.storeTrigger(trigger2, false);
582 |
583 | CronTriggerImpl newTrigger = getCronTrigger("newTrigger", "group1", JobKey.jobKey("foo", "bar"));
584 |
585 | jobStore.replaceTrigger(trigger1.getKey(), newTrigger);
586 | }
587 | }
588 |
--------------------------------------------------------------------------------
/src/test/java/net/joelinn/quartz/TestJob.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.quartz;
2 |
3 | import org.quartz.Job;
4 | import org.quartz.JobExecutionContext;
5 | import org.quartz.JobExecutionException;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 |
9 | /**
10 | * Joe Linn
11 | * 7/15/2014
12 | */
13 | public class TestJob implements Job{
14 | private static Logger logger = LoggerFactory.getLogger(TestJob.class);
15 |
16 | protected int timeout;
17 |
18 | public void setTimeout(int timeout) {
19 | this.timeout = timeout;
20 | }
21 |
22 | @Override
23 | public void execute(JobExecutionContext context) throws JobExecutionException {
24 | logger.info(String.format("Test job running with timeout %s", timeout));
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/test/java/net/joelinn/quartz/TestJobNonConcurrent.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.quartz;
2 |
3 | import org.quartz.DisallowConcurrentExecution;
4 |
5 | /**
6 | * User: Joe Linn
7 | * Date: 7/23/2014
8 | * Time: 11:10 AM
9 | */
10 | @DisallowConcurrentExecution
11 | public class TestJobNonConcurrent extends TestJob{
12 | }
13 |
--------------------------------------------------------------------------------
/src/test/java/net/joelinn/quartz/TestJobPersist.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.quartz;
2 |
3 | import org.quartz.PersistJobDataAfterExecution;
4 |
5 | /**
6 | * Joe Linn
7 | * 7/22/2014
8 | */
9 | @PersistJobDataAfterExecution
10 | public class TestJobPersist extends TestJob{
11 | }
12 |
--------------------------------------------------------------------------------
/src/test/java/net/joelinn/quartz/TestUtils.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.quartz;
2 |
3 | import org.quartz.*;
4 |
5 | import java.io.IOException;
6 | import java.net.ServerSocket;
7 |
8 | /**
9 | * @author Joe Linn
10 | * 10/4/2016
11 | */
12 | public class TestUtils {
13 | private TestUtils() {}
14 |
15 | public static int getPort() throws IOException {
16 | try (ServerSocket socket = new ServerSocket(0)) {
17 | socket.setReuseAddress(true);
18 | return socket.getLocalPort();
19 | }
20 | }
21 |
22 |
23 | public static JobDetail createJob(Class extends Job> jobClass, String name, String group) {
24 | return JobBuilder.newJob(jobClass)
25 | .withIdentity(name, group)
26 | .build();
27 | }
28 |
29 |
30 | public static CronTrigger createCronTrigger(String name, String group, String cron) {
31 | return TriggerBuilder.newTrigger()
32 | .withIdentity(name, group)
33 | .withSchedule(CronScheduleBuilder.cronSchedule(cron))
34 | .build();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/test/java/net/joelinn/quartz/TriggeredJobCompleteTest.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.quartz;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 | import org.quartz.JobBuilder;
6 | import org.quartz.JobDetail;
7 | import org.quartz.JobPersistenceException;
8 | import org.quartz.Trigger;
9 | import org.quartz.impl.triggers.CronTriggerImpl;
10 |
11 | import static org.hamcrest.CoreMatchers.not;
12 | import static org.hamcrest.CoreMatchers.nullValue;
13 | import static org.hamcrest.MatcherAssert.assertThat;
14 | import static org.junit.Assert.assertEquals;
15 | import static org.junit.Assert.assertFalse;
16 | import static org.mockito.Mockito.verify;
17 |
18 | /**
19 | * Joe Linn
20 | * 7/22/2014
21 | */
22 | public class TriggeredJobCompleteTest extends BaseTest{
23 | protected JobDetail job;
24 |
25 | protected CronTriggerImpl trigger1;
26 |
27 | protected CronTriggerImpl trigger2;
28 |
29 | @Before
30 | public void setUp() throws JobPersistenceException {
31 | // store a job with some triggers
32 | job = getJobDetail("job1", "jobGroup1");
33 | trigger1 = getCronTrigger("trigger1", "triggerGroup1", job.getKey());
34 | trigger2 = getCronTrigger("trigger2", "triggerGroup1", job.getKey());
35 | storeJobAndTriggers(job, trigger1, trigger2);
36 | }
37 |
38 | @Test
39 | public void triggeredJobCompleteDelete() throws JobPersistenceException {
40 | jobStore.triggeredJobComplete(trigger1, job, Trigger.CompletedExecutionInstruction.DELETE_TRIGGER);
41 |
42 | // ensure that the proper trigger was deleted
43 | assertThat(jobStore.retrieveTrigger(trigger1.getKey()), nullValue());
44 | assertThat(jobStore.retrieveTrigger(trigger2.getKey()), not(nullValue()));
45 |
46 | verify(mockScheduleSignaler).signalSchedulingChange(0L);
47 | }
48 |
49 | @Test
50 | public void triggeredJobCompleteComplete() throws JobPersistenceException {
51 | jobStore.triggeredJobComplete(trigger1, job, Trigger.CompletedExecutionInstruction.SET_TRIGGER_COMPLETE);
52 |
53 | // ensure that neither trigger was deleted
54 | assertThat(jobStore.retrieveTrigger(trigger1.getKey()), not(nullValue()));
55 | assertThat(jobStore.retrieveTrigger(trigger2.getKey()), not(nullValue()));
56 |
57 | // ensure that the proper trigger was set to COMPLETE
58 | assertEquals(Trigger.TriggerState.COMPLETE, jobStore.getTriggerState(trigger1.getKey()));
59 | assertEquals(Trigger.TriggerState.NORMAL, jobStore.getTriggerState(trigger2.getKey()));
60 |
61 | verify(mockScheduleSignaler).signalSchedulingChange(0L);
62 | }
63 |
64 | @Test
65 | public void triggeredJobCompleteError() throws JobPersistenceException {
66 | jobStore.triggeredJobComplete(trigger1, job, Trigger.CompletedExecutionInstruction.SET_TRIGGER_ERROR);
67 |
68 | // ensure that the proper trigger was set to ERROR
69 | assertEquals(Trigger.TriggerState.ERROR, jobStore.getTriggerState(trigger1.getKey()));
70 | assertEquals(Trigger.TriggerState.NORMAL, jobStore.getTriggerState(trigger2.getKey()));
71 |
72 | verify(mockScheduleSignaler).signalSchedulingChange(0L);
73 | }
74 |
75 | @Test
76 | public void triggeredJobCompleteAllError() throws JobPersistenceException {
77 | jobStore.triggeredJobComplete(trigger1, job, Trigger.CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR);
78 |
79 | // ensure that both triggers were set to ERROR
80 | assertEquals(Trigger.TriggerState.ERROR, jobStore.getTriggerState(trigger1.getKey()));
81 | assertEquals(Trigger.TriggerState.ERROR, jobStore.getTriggerState(trigger2.getKey()));
82 |
83 | verify(mockScheduleSignaler).signalSchedulingChange(0L);
84 | }
85 |
86 | @Test
87 | public void triggeredJobCompleteAllComplete() throws JobPersistenceException {
88 | jobStore.triggeredJobComplete(trigger1, job, Trigger.CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_COMPLETE);
89 |
90 | // ensure that both triggers were set to COMPLETE
91 | assertEquals(Trigger.TriggerState.COMPLETE, jobStore.getTriggerState(trigger1.getKey()));
92 | assertEquals(Trigger.TriggerState.COMPLETE, jobStore.getTriggerState(trigger2.getKey()));
93 |
94 | verify(mockScheduleSignaler).signalSchedulingChange(0L);
95 | }
96 |
97 | @Test
98 | public void triggeredJobCompletePersist() throws JobPersistenceException {
99 | JobDetail jobPersist = JobBuilder.newJob(TestJobPersist.class)
100 | .withIdentity("testJobPersist1", "jobGroupPersist1")
101 | .usingJobData("timeout", 42)
102 | .withDescription("I am describing a job!")
103 | .build();
104 | CronTriggerImpl triggerPersist1 = getCronTrigger("triggerPersist1", "triggerPersistGroup1", jobPersist.getKey());
105 | CronTriggerImpl triggerPersist2 = getCronTrigger("triggerPersist2", "triggerPersistGroup1", jobPersist.getKey());
106 | storeJobAndTriggers(jobPersist, triggerPersist1, triggerPersist1);
107 |
108 | jobStore.triggeredJobComplete(triggerPersist1, jobPersist, Trigger.CompletedExecutionInstruction.SET_TRIGGER_COMPLETE);
109 |
110 | assertEquals(Trigger.TriggerState.COMPLETE, jobStore.getTriggerState(triggerPersist1.getKey()));
111 | }
112 |
113 | @Test
114 | public void triggeredJobCompleteNonConcurrent() throws JobPersistenceException {
115 | JobDetail job = JobBuilder.newJob(TestJobNonConcurrent.class)
116 | .withIdentity("testJobNonConcurrent1", "jobGroupNonConcurrent1")
117 | .usingJobData("timeout", 42)
118 | .withDescription("I am describing a job!")
119 | .build();
120 | CronTriggerImpl trigger1 = getCronTrigger("triggerNonConcurrent1", "triggerNonConcurrentGroup1", job.getKey());
121 | CronTriggerImpl trigger2 = getCronTrigger("triggerNonConcurrent2", "triggerNonConcurrentGroup1", job.getKey());
122 | storeJobAndTriggers(job, trigger1, trigger2);
123 |
124 | jobStore.triggeredJobComplete(trigger1, job, Trigger.CompletedExecutionInstruction.SET_TRIGGER_COMPLETE);
125 |
126 | assertEquals(Trigger.TriggerState.COMPLETE, jobStore.getTriggerState(trigger1.getKey()));
127 |
128 | final String jobHashKey = schema.jobHashKey(job.getKey());
129 | assertFalse(jedis.sismember(schema.blockedJobsSet(), jobHashKey));
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/test/java/net/joelinn/quartz/mixin/CronTriggerMixinTest.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.quartz.mixin;
2 |
3 | import com.fasterxml.jackson.core.type.TypeReference;
4 | import com.fasterxml.jackson.databind.ObjectMapper;
5 | import net.joelinn.quartz.jobstore.mixin.CronTriggerMixin;
6 | import org.junit.Before;
7 | import org.junit.Test;
8 | import org.quartz.CronScheduleBuilder;
9 | import org.quartz.CronTrigger;
10 | import org.quartz.TriggerBuilder;
11 | import org.quartz.impl.triggers.CronTriggerImpl;
12 |
13 | import java.util.HashMap;
14 | import java.util.Map;
15 |
16 | import static org.hamcrest.MatcherAssert.assertThat;
17 | import static org.hamcrest.collection.IsMapContaining.hasKey;
18 | import static org.junit.Assert.assertEquals;
19 |
20 | /**
21 | * Joe Linn
22 | * 7/15/2014
23 | */
24 | public class CronTriggerMixinTest {
25 | protected ObjectMapper mapper;
26 |
27 | @Before
28 | public void setUp(){
29 | mapper = new ObjectMapper();
30 | mapper.addMixIn(CronTrigger.class, CronTriggerMixin.class);
31 | }
32 |
33 | @Test
34 | public void serialization(){
35 | String cron = "0/5 * * * * ?";
36 | CronTrigger trigger = TriggerBuilder.newTrigger()
37 | .forJob("testJob", "testGroup")
38 | .withIdentity("testTrigger", "testTriggerGroup")
39 | .withSchedule(CronScheduleBuilder.cronSchedule(cron))
40 | .usingJobData("timeout", 5)
41 | .withDescription("A description!")
42 | .build();
43 |
44 | Map triggerMap = mapper.convertValue(trigger, new TypeReference>() {});
45 |
46 | assertThat(triggerMap, hasKey("name"));
47 | assertEquals("testTrigger", triggerMap.get("name"));
48 | assertThat(triggerMap, hasKey("group"));
49 | assertEquals("testTriggerGroup", triggerMap.get("group"));
50 | assertThat(triggerMap, hasKey("jobName"));
51 | assertEquals("testJob", triggerMap.get("jobName"));
52 |
53 | CronTriggerImpl cronTrigger = mapper.convertValue(triggerMap, CronTriggerImpl.class);
54 |
55 | assertEquals(trigger.getKey().getName(), cronTrigger.getKey().getName());
56 | assertEquals(trigger.getKey().getGroup(), cronTrigger.getKey().getGroup());
57 | assertEquals(trigger.getStartTime(), cronTrigger.getStartTime());
58 | assertEquals(trigger.getCronExpression(), cronTrigger.getCronExpression());
59 | assertEquals(trigger.getTimeZone(), cronTrigger.getTimeZone());
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/test/java/net/joelinn/quartz/mixin/JobDetailMixinTest.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.quartz.mixin;
2 |
3 | import com.fasterxml.jackson.core.type.TypeReference;
4 | import com.fasterxml.jackson.databind.ObjectMapper;
5 | import net.joelinn.quartz.TestJob;
6 | import net.joelinn.quartz.jobstore.mixin.JobDetailMixin;
7 | import org.junit.Before;
8 | import org.junit.Test;
9 | import org.quartz.JobBuilder;
10 | import org.quartz.JobDetail;
11 | import org.quartz.impl.JobDetailImpl;
12 |
13 | import java.util.HashMap;
14 | import java.util.Map;
15 |
16 | import static org.hamcrest.MatcherAssert.assertThat;
17 | import static org.hamcrest.collection.IsMapContaining.hasKey;
18 | import static org.junit.Assert.assertEquals;
19 |
20 | /**
21 | * Joe Linn
22 | * 7/15/2014
23 | */
24 | public class JobDetailMixinTest {
25 | protected ObjectMapper mapper;
26 |
27 | @Before
28 | public void setUp(){
29 | mapper = new ObjectMapper();
30 | mapper.addMixIn(JobDetail.class, JobDetailMixin.class);
31 | }
32 |
33 | @Test
34 | public void serializeJobDetail() throws Exception {
35 | JobDetail testJob = JobBuilder.newJob(TestJob.class)
36 | .withIdentity("testJob", "testGroup")
37 | .usingJobData("timeout", 42)
38 | .withDescription("I am describing a job!")
39 | .build();
40 |
41 | String json = mapper.writeValueAsString(testJob);
42 | Map jsonMap = mapper.readValue(json, new TypeReference>() {});
43 |
44 | assertThat(jsonMap, hasKey("name"));
45 | assertEquals(testJob.getKey().getName(), jsonMap.get("name"));
46 | assertThat(jsonMap, hasKey("group"));
47 | assertEquals(testJob.getKey().getGroup(), jsonMap.get("group"));
48 | assertThat(jsonMap, hasKey("jobClass"));
49 | assertEquals(testJob.getJobClass().getName(), jsonMap.get("jobClass"));
50 |
51 | JobDetailImpl jobDetail = mapper.readValue(json, JobDetailImpl.class);
52 |
53 | assertEquals(testJob.getKey().getName(), jobDetail.getKey().getName());
54 | assertEquals(testJob.getKey().getGroup(), jobDetail.getKey().getGroup());
55 | assertEquals(testJob.getJobClass(), jobDetail.getJobClass());
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/test/java/net/joelinn/quartz/mixin/SimpleTriggerMixinTest.java:
--------------------------------------------------------------------------------
1 | package net.joelinn.quartz.mixin;
2 |
3 | import com.fasterxml.jackson.core.type.TypeReference;
4 | import com.fasterxml.jackson.databind.ObjectMapper;
5 | import net.joelinn.quartz.jobstore.mixin.TriggerMixin;
6 | import org.junit.Before;
7 | import org.junit.Test;
8 | import org.quartz.SimpleScheduleBuilder;
9 | import org.quartz.SimpleTrigger;
10 | import org.quartz.TriggerBuilder;
11 | import org.quartz.impl.triggers.SimpleTriggerImpl;
12 |
13 | import java.util.HashMap;
14 | import java.util.Map;
15 |
16 | import static org.hamcrest.MatcherAssert.assertThat;
17 | import static org.hamcrest.collection.IsMapContaining.hasKey;
18 | import static org.junit.Assert.assertEquals;
19 |
20 | /**
21 | * Joe Linn
22 | * 7/15/2014
23 | */
24 | public class SimpleTriggerMixinTest {
25 | protected ObjectMapper mapper;
26 |
27 | @Before
28 | public void setUp(){
29 | mapper = new ObjectMapper();
30 | mapper.addMixIn(SimpleTrigger.class, TriggerMixin.class);
31 | }
32 |
33 | @Test
34 | public void serialization(){
35 | SimpleTrigger trigger = TriggerBuilder.newTrigger()
36 | .forJob("testJob", "testGroup")
37 | .withIdentity("testTrigger", "testTriggerGroup")
38 | .usingJobData("timeout", 5)
39 | .withDescription("A description!")
40 | .withSchedule(SimpleScheduleBuilder.repeatHourlyForever())
41 | .build();
42 |
43 | Map triggerMap = mapper.convertValue(trigger, new TypeReference>() {});
44 |
45 | assertThat(triggerMap, hasKey("name"));
46 | assertEquals("testTrigger", triggerMap.get("name"));
47 | assertThat(triggerMap, hasKey("group"));
48 | assertEquals("testTriggerGroup", triggerMap.get("group"));
49 | assertThat(triggerMap, hasKey("jobName"));
50 | assertEquals("testJob", triggerMap.get("jobName"));
51 |
52 | SimpleTriggerImpl simpleTrigger = mapper.convertValue(triggerMap, SimpleTriggerImpl.class);
53 |
54 | assertEquals(trigger.getKey().getName(), simpleTrigger.getKey().getName());
55 | assertEquals(trigger.getKey().getGroup(), simpleTrigger.getKey().getGroup());
56 | assertEquals(trigger.getStartTime(), simpleTrigger.getStartTime());
57 | assertEquals(trigger.getRepeatInterval(), simpleTrigger.getRepeatInterval());
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/test/resources/logback-test.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %C{5}:%L - %msg%n
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------