1 package com.atlassian.scheduler.quartz2;
2
3 import com.atlassian.scheduler.SchedulerRuntimeException;
4 import com.atlassian.scheduler.SchedulerServiceException;
5 import com.atlassian.scheduler.config.JobId;
6 import com.atlassian.scheduler.config.JobRunnerKey;
7 import com.atlassian.scheduler.config.RunMode;
8 import com.atlassian.scheduler.core.AbstractSchedulerService;
9 import com.atlassian.scheduler.quartz2.spi.Quartz2SchedulerConfiguration;
10 import com.atlassian.util.concurrent.LazyReference;
11 import com.google.common.base.Supplier;
12 import com.google.common.base.Suppliers;
13 import org.quartz.JobBuilder;
14 import org.quartz.JobDetail;
15 import org.quartz.JobKey;
16 import org.quartz.Scheduler;
17 import org.quartz.SchedulerException;
18 import org.quartz.Trigger;
19 import org.quartz.TriggerBuilder;
20 import org.quartz.TriggerKey;
21 import org.quartz.impl.StdSchedulerFactory;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
24
25 import javax.annotation.Nullable;
26 import java.util.Collection;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Properties;
30 import java.util.Set;
31
32 import static com.atlassian.scheduler.config.RunMode.RUN_LOCALLY;
33 import static com.atlassian.scheduler.config.RunMode.RUN_ONCE_PER_CLUSTER;
34 import static com.atlassian.util.concurrent.Assertions.notNull;
35 import static org.quartz.JobKey.jobKey;
36 import static org.quartz.TriggerKey.triggerKey;
37 import static org.quartz.impl.matchers.GroupMatcher.jobGroupEquals;
38
39
40
41
42
43
44 class Quartz2SchedulerFacade {
45 private static final Logger LOG = LoggerFactory.getLogger(Quartz2SchedulerFacade.class);
46
47 static final String QUARTZ_JOB_GROUP = "SchedulerServiceJobs";
48 static final String QUARTZ_TRIGGER_GROUP = "SchedulerServiceTriggers";
49 static final String QUARTZ_PARAMETERS_KEY = "parameters";
50
51 private final Supplier<Scheduler> quartzRef;
52
53 private Quartz2SchedulerFacade(final Supplier<Scheduler> quartzRef) {
54 this.quartzRef = quartzRef;
55 }
56
57
58
59
60
61 static Quartz2SchedulerFacade createLocal(final AbstractSchedulerService schedulerService,
62 final Scheduler scheduler) throws SchedulerServiceException {
63 notNull("scheduler", scheduler);
64 return createFacade(schedulerService, scheduler, RUN_LOCALLY);
65 }
66
67
68
69
70 static Quartz2SchedulerFacade createClustered(final AbstractSchedulerService schedulerService,
71 final Scheduler scheduler) throws SchedulerServiceException {
72 notNull("scheduler", scheduler);
73 return createFacade(schedulerService, scheduler, RUN_ONCE_PER_CLUSTER);
74 }
75
76
77
78
79 private static Quartz2SchedulerFacade createFacade(final AbstractSchedulerService schedulerService,
80 final Scheduler scheduler,
81 final RunMode runMode) throws SchedulerServiceException {
82 try {
83 configureScheduler(scheduler, schedulerService, runMode);
84 final Supplier<Scheduler> quartzRef = Suppliers.ofInstance(scheduler);
85 return new Quartz2SchedulerFacade(quartzRef);
86 } catch (SchedulerException se) {
87 throw checked("Unable to configure the underlying Quartz scheduler", se);
88 }
89 }
90
91
92
93
94
95 static Quartz2SchedulerFacade createLocal(final AbstractSchedulerService schedulerService,
96 final Quartz2SchedulerConfiguration config) throws SchedulerServiceException {
97 notNull("config", config);
98 final Properties localSettings = notNull("config.getLocalSettings()", config.getLocalSettings());
99 return createFacade(schedulerService, localSettings, RUN_LOCALLY);
100 }
101
102
103
104
105 static Quartz2SchedulerFacade createClustered(final AbstractSchedulerService schedulerService,
106 final Quartz2SchedulerConfiguration config) throws SchedulerServiceException {
107 notNull("config", config);
108 final Properties clusteredSettings = notNull("config.getClusteredSettings()", config.getClusteredSettings());
109 return createFacade(schedulerService, clusteredSettings, RUN_ONCE_PER_CLUSTER);
110 }
111
112
113
114
115 private static Quartz2SchedulerFacade createFacade(final AbstractSchedulerService schedulerService,
116 final Properties customConfig, final RunMode runMode)
117 throws SchedulerServiceException {
118 try {
119 final Properties config = new Properties();
120 for (String key : customConfig.stringPropertyNames()) {
121 config.setProperty(key, customConfig.getProperty(key));
122 }
123
124
125 config.setProperty("org.quartz.scheduler.skipUpdateCheck", "true");
126
127 final StdSchedulerFactory schedulerFactory = new StdSchedulerFactory(config);
128 final Supplier<Scheduler> quartzRef = createQuartzRef(schedulerService, runMode, schedulerFactory);
129 return new Quartz2SchedulerFacade(quartzRef);
130 } catch (SchedulerException se) {
131 throw checked("Unable to create the underlying Quartz scheduler", se);
132 }
133 }
134
135
136 private static Supplier<Scheduler> createQuartzRef(final AbstractSchedulerService schedulerService,
137 final RunMode runMode,
138 final StdSchedulerFactory schedulerFactory) {
139 return new LazyReference<Scheduler>() {
140 @Override
141 protected Scheduler create() throws Exception {
142
143
144
145
146
147
148 final Thread thd = Thread.currentThread();
149 final ClassLoader originalContextClassLoader = thd.getContextClassLoader();
150 try {
151 thd.setContextClassLoader(schedulerService.getClass().getClassLoader());
152 final Scheduler quartz = schedulerFactory.getScheduler();
153 configureScheduler(quartz, schedulerService, runMode);
154 return quartz;
155 } finally {
156 thd.setContextClassLoader(originalContextClassLoader);
157 }
158 }
159 };
160 }
161
162 static void configureScheduler(final Scheduler quartz,
163 final AbstractSchedulerService schedulerService,
164 final RunMode runMode) throws SchedulerException {
165 quartz.setJobFactory(new Quartz2JobFactory(schedulerService, runMode));
166 }
167
168
169 @Nullable
170 Trigger getTrigger(final JobId jobId) {
171 try {
172 return getScheduler().getTrigger(jobId(jobId));
173 } catch (SchedulerException se) {
174 logWarn("Error getting quartz trigger for '{}'", jobId, se);
175 return null;
176 }
177 }
178
179 @Nullable
180 JobDetail getQuartzJob(final JobRunnerKey jobRunnerKey) {
181 try {
182 return getScheduler().getJobDetail(jobRunnerKey(jobRunnerKey));
183 } catch (SchedulerException se) {
184 logWarn("Error getting quartz job details for '{}'", jobRunnerKey, se);
185 return null;
186 }
187 }
188
189 boolean hasAnyTriggers(final JobRunnerKey jobRunnerKey) {
190 return !getTriggersOfJob(jobRunnerKey).isEmpty();
191 }
192
193 Collection<JobRunnerKey> getJobRunnerKeys() {
194 try {
195 final Set<JobKey> jobKeys = getScheduler().getJobKeys(jobGroupEquals(QUARTZ_JOB_GROUP));
196 final Set<JobRunnerKey> jobRunnerKeys = new HashSet<JobRunnerKey>((int) (jobKeys.size() * 1.25));
197 for (JobKey jobKey : jobKeys) {
198 jobRunnerKeys.add(JobRunnerKey.of(jobKey.getName()));
199 }
200 return jobRunnerKeys;
201 } catch (SchedulerException se) {
202 throw unchecked("Could not get the triggers from Quartz", se);
203 }
204 }
205
206 List<? extends Trigger> getTriggersOfJob(final JobRunnerKey jobRunnerKey) {
207 try {
208 return getScheduler().getTriggersOfJob(jobRunnerKey(jobRunnerKey));
209 } catch (SchedulerException se) {
210 throw unchecked("Could not get the triggers from Quartz", se);
211 }
212 }
213
214 boolean deleteTrigger(final JobId jobId) {
215 try {
216 return getScheduler().unscheduleJob(jobId(jobId));
217 } catch (SchedulerException se) {
218 logWarn("Error removing Quartz trigger for '{}'", jobId, se);
219 return false;
220 }
221 }
222
223 boolean deleteJob(final JobRunnerKey jobRunnerKey) {
224 try {
225 return getScheduler().deleteJob(jobRunnerKey(jobRunnerKey));
226 } catch (SchedulerException se) {
227 logWarn("Error removing Quartz job for '{}'", jobRunnerKey, se);
228 return false;
229 }
230 }
231
232 private void scheduleJob(final TriggerBuilder<?> trigger) throws SchedulerServiceException {
233 try {
234 getScheduler().scheduleJob(trigger.build());
235 } catch (SchedulerException se) {
236 throw checked("Unable to create the Quartz trigger", se);
237 }
238 }
239
240
241 void scheduleJob(final JobRunnerKey jobRunnerKey, final TriggerBuilder<?> trigger) throws SchedulerServiceException {
242 if (getQuartzJob(jobRunnerKey) != null) {
243 scheduleJob(trigger.forJob(jobRunnerKey(jobRunnerKey)));
244 return;
245 }
246
247 try {
248 final JobDetail quartzJob = JobBuilder.newJob()
249 .withIdentity(jobRunnerKey(jobRunnerKey))
250 .ofType(Quartz2Job.class)
251 .storeDurably(false)
252 .build();
253 getScheduler().scheduleJob(quartzJob, trigger.build());
254 } catch (SchedulerException se) {
255 throw checked("Unable to create the Quartz job and trigger", se);
256 }
257 }
258
259 boolean unscheduleJob(final JobId jobId) {
260 final Trigger trigger = getTrigger(jobId);
261 if (trigger == null) {
262 return false;
263 }
264 final JobRunnerKey jobRunnerKey = JobRunnerKey.of(trigger.getJobKey().getName());
265 if (deleteTrigger(jobId) && !hasAnyTriggers(jobRunnerKey)) {
266 deleteJob(jobRunnerKey);
267 }
268 return true;
269 }
270
271 void start() throws SchedulerServiceException {
272 try {
273 getScheduler().start();
274 } catch (SchedulerException se) {
275 throw checked("Quartz scheduler refused to start", se);
276 }
277 }
278
279 void standby() throws SchedulerServiceException {
280 try {
281 getScheduler().standby();
282 } catch (SchedulerException se) {
283 throw checked("Quartz scheduler refused to enter standby mode", se);
284 }
285 }
286
287 void shutdown() {
288 try {
289 getScheduler().shutdown();
290 } catch (SchedulerException se) {
291 LOG.error("Error shutting down internal scheduler", se);
292 }
293 }
294
295 private Scheduler getScheduler() {
296 try {
297 return quartzRef.get();
298 } catch (LazyReference.InitializationException ex) {
299 throw unchecked("Error creating underlying Quartz scheduler", ex.getCause());
300 }
301 }
302
303
304
305
306
307
308
309
310
311
312 private static void logWarn(String message, Object arg, Throwable e) {
313 if (LOG.isDebugEnabled()) {
314 LOG.warn(message, arg, e);
315 } else {
316 LOG.warn(message + ": {}", arg, e.toString());
317 }
318 }
319
320 static TriggerKey jobId(JobId jobId) {
321 return triggerKey(jobId.toString(), QUARTZ_TRIGGER_GROUP);
322 }
323
324 static JobKey jobRunnerKey(JobRunnerKey jobRunnerKey) {
325 return jobKey(jobRunnerKey.toString(), QUARTZ_JOB_GROUP);
326 }
327
328 private static SchedulerServiceException checked(final String message, Throwable e) {
329 return new SchedulerServiceException(message, e);
330 }
331
332 private static SchedulerRuntimeException unchecked(final String message, Throwable e) {
333 return new SchedulerRuntimeException(message, e);
334 }
335 }