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