• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

evolvedbinary / elemental / 982

29 Apr 2025 08:34PM UTC coverage: 56.409% (+0.007%) from 56.402%
982

push

circleci

adamretter
[feature] Improve README.md badges

28451 of 55847 branches covered (50.94%)

Branch coverage included in aggregate %.

77468 of 131924 relevant lines covered (58.72%)

0.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

46.67
/exist-core/src/main/java/org/exist/scheduler/impl/QuartzSchedulerImpl.java
1
/*
2
 * Elemental
3
 * Copyright (C) 2024, Evolved Binary Ltd
4
 *
5
 * admin@evolvedbinary.com
6
 * https://www.evolvedbinary.com | https://www.elemental.xyz
7
 *
8
 * Use of this software is governed by the Business Source License 1.1
9
 * included in the LICENSE file and at www.mariadb.com/bsl11.
10
 *
11
 * Change Date: 2028-04-27
12
 *
13
 * On the date above, in accordance with the Business Source License, use
14
 * of this software will be governed by the Apache License, Version 2.0.
15
 *
16
 * Additional Use Grant: Production use of the Licensed Work for a permitted
17
 * purpose. A Permitted Purpose is any purpose other than a Competing Use.
18
 * A Competing Use means making the Software available to others in a commercial
19
 * product or service that: substitutes for the Software; substitutes for any
20
 * other product or service we offer using the Software that exists as of the
21
 * date we make the Software available; or offers the same or substantially
22
 * similar functionality as the Software.
23
 *
24
 * NOTE: Parts of this file contain code from 'The eXist-db Authors'.
25
 *       The original license header is included below.
26
 *
27
 * =====================================================================
28
 *
29
 * eXist-db Open Source Native XML Database
30
 * Copyright (C) 2001 The eXist-db Authors
31
 *
32
 * info@exist-db.org
33
 * http://www.exist-db.org
34
 *
35
 * This library is free software; you can redistribute it and/or
36
 * modify it under the terms of the GNU Lesser General Public
37
 * License as published by the Free Software Foundation; either
38
 * version 2.1 of the License, or (at your option) any later version.
39
 *
40
 * This library is distributed in the hope that it will be useful,
41
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
42
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
43
 * Lesser General Public License for more details.
44
 *
45
 * You should have received a copy of the GNU Lesser General Public
46
 * License along with this library; if not, write to the Free Software
47
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
48
 */
49
package org.exist.scheduler.impl;
50

51
import java.io.IOException;
52
import java.io.InputStream;
53
import java.nio.file.Files;
54
import java.nio.file.Path;
55
import java.util.*;
56
import java.util.Calendar;
57
import java.util.stream.Collectors;
58

59
import com.evolvedbinary.j8fu.Either;
60
import org.apache.logging.log4j.LogManager;
61
import org.apache.logging.log4j.Logger;
62
import org.exist.scheduler.*;
63
import org.exist.scheduler.Scheduler;
64
import org.exist.security.Subject;
65
import org.exist.storage.BrokerPool;
66
import org.exist.storage.BrokerPoolService;
67
import org.exist.storage.BrokerPoolServiceException;
68
import org.exist.storage.SystemTask;
69
import org.exist.util.Configuration;
70

71
import org.exist.util.ConfigurationHelper;
72
import org.quartz.*;
73

74
import static org.exist.util.ThreadUtils.nameInstanceSchedulerThread;
75
import static org.exist.util.ThreadUtils.nameInstanceThread;
76
import static org.exist.util.ThreadUtils.newInstanceSubThreadGroup;
77
import static org.quartz.CronScheduleBuilder.cronSchedule;
78
import static org.quartz.JobBuilder.newJob;
79
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
80
import static org.quartz.TriggerBuilder.newTrigger;
81

82
import static org.exist.scheduler.JobDescription.*;
83
import static org.quartz.impl.StdSchedulerFactory.*;
84

85
import org.quartz.impl.StdSchedulerFactory;
86
import org.quartz.impl.matchers.GroupMatcher;
87

88
import javax.annotation.Nullable;
89

90
/**
91
 * A Scheduler to trigger Startup, System and User defined jobs.
92
 *
93
 * @author <a href="mailto:adam@existsolutions.com">Adam Retter</a>
94
 * @author <a href="mailto:andrzej@chaeron.com">Andrzej Taramina</a>
95
 */
96
public class QuartzSchedulerImpl implements Scheduler, BrokerPoolService {
97

98
    private final static Logger LOG = LogManager.getLogger(QuartzSchedulerImpl.class);
1✔
99

100
    private final static String PROPERTIES_FILE_NAME = "quartz.properties";
101

102
    // the real scheduler
103
    private org.quartz.Scheduler scheduler;
104

105
    private final BrokerPool brokerPool;
106
    private Configuration config;
107

108
    public QuartzSchedulerImpl(final BrokerPool brokerpool) {
1✔
109
        this.brokerPool = brokerpool;
1✔
110
    }
1✔
111

112
    @Override
113
    public void configure(final Configuration configuration) throws BrokerPoolServiceException {
114
        this.config = configuration;
1✔
115
    }
1✔
116

117
    @Override
118
    public void prepare(final BrokerPool brokerPool) throws BrokerPoolServiceException {
119

120
        // NOTE: we create the scheduler in a separate thread with its own thread-group so that the thread group is used by Quartz
121
        final ThreadGroup instanceQuartzThreadGroup = newInstanceSubThreadGroup(brokerPool, "scheduler.quartz-simple-thread-pool");
1✔
122
        final QuartzSchedulerCreator creator = new QuartzSchedulerCreator(getQuartzProperties());
1✔
123
        final Thread schedulerCreatorThread = new Thread(instanceQuartzThreadGroup, creator, nameInstanceThread(brokerPool, "prepare-quartz-scheduler"));
1✔
124
        schedulerCreatorThread.start();
1✔
125

126
        try {
127
            schedulerCreatorThread.join();
1✔
128
            this.scheduler = creator
1✔
129
                    .getScheduler()
1✔
130
                    .valueOrThrow(e -> new BrokerPoolServiceException("Unable to create Scheduler: " + e.getMessage(), e));
1✔
131

132
        } catch (final InterruptedException e) {
1✔
133
            // restore interrupted state
134
            Thread.currentThread().interrupt();
×
135
            throw new BrokerPoolServiceException("Unable to create Scheduler: " + e.getMessage(), e);
×
136
        }
137
    }
1✔
138
    @Override
139
    public void startMultiUser(final BrokerPool brokerPool) throws BrokerPoolServiceException {
140
        run(); // start running all the defined jobs
1✔
141
    }
1✔
142

143
    private final static Properties defaultQuartzProperties = new Properties();
1✔
144

145
    static {
146
        defaultQuartzProperties.setProperty(PROP_SCHED_RMI_EXPORT, "false");
1✔
147
        defaultQuartzProperties.setProperty(PROP_SCHED_RMI_PROXY, "false");
1✔
148
        defaultQuartzProperties.setProperty(PROP_SCHED_WRAP_JOB_IN_USER_TX, "false");
1✔
149
        defaultQuartzProperties.setProperty(PROP_THREAD_POOL_CLASS, "org.quartz.simpl.SimpleThreadPool");
1✔
150
        defaultQuartzProperties.setProperty("org.quartz.threadPool.threadCount", "4");
1✔
151
        defaultQuartzProperties.setProperty("org.quartz.threadPool.threadPriority", "5");
1✔
152
        defaultQuartzProperties.setProperty("org.quartz.threadPool.threadsInheritGroupOfInitializingThread", "true");
1✔
153
        defaultQuartzProperties.setProperty("org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread", "true");
1✔
154
        defaultQuartzProperties.setProperty(PROP_JOB_STORE_CLASS, "org.quartz.simpl.RAMJobStore");
1✔
155
        defaultQuartzProperties.setProperty("org.quartz.jobStore.misfireThreshold", "60000");
1✔
156
    }
1✔
157

158
    private Properties getQuartzProperties() {
159
        //try and load the properties for quartz from the conf directory
160
        Path f = ConfigurationHelper.lookup(PROPERTIES_FILE_NAME);
1✔
161
        if (!Files.isReadable(f)) {
1!
162
            f = f.getParent().resolve("etc").resolve(PROPERTIES_FILE_NAME);
1✔
163
        }
164

165
        final Properties properties = new Properties();
1✔
166
        if (Files.isReadable(f)) {
1!
167
            try (final InputStream is = Files.newInputStream(f)) {
×
168
                properties.load(is);
×
169
                LOG.info("Successfully loaded Quartz Scheduler properties from: {}", f.normalize().toAbsolutePath());
×
170
            } catch (final IOException e) {
×
171
                LOG.warn("Could not load Quartz Scheduler properties, will use defaults. {}", e.getMessage(), e);
×
172
                properties.putAll(defaultQuartzProperties);
×
173
            }
174
        } else {
×
175
            LOG.warn("Could not load Quartz Scheduler properties, will use defaults.");
1✔
176
            properties.putAll(defaultQuartzProperties);
1✔
177
        }
178

179
        // always set the scheduler name
180
        properties.setProperty(PROP_SCHED_INSTANCE_NAME, nameInstanceSchedulerThread(brokerPool, "quartz-scheduler"));
1✔
181

182
        // always set the thread prefix for the thread pool
183
        final String schedulerThreadNamePrefix = nameInstanceSchedulerThread(brokerPool, "quartz-worker");
1✔
184
        properties.setProperty(PROP_THREAD_POOL_PREFIX + ".threadNamePrefix", schedulerThreadNamePrefix);
1✔
185

186
        return properties;
1✔
187
    }
188

189
    protected org.quartz.Scheduler getScheduler() {
190
        return scheduler;
1✔
191
    }
192

193
    @Override
194
    public void run() {
195
        try {
196
            setupConfiguredJobs();
1✔
197
            getScheduler().start();
1✔
198
        } catch(final SchedulerException se) {
1✔
199
            LOG.error("Unable to start the Scheduler: {}", se.getMessage(), se);
×
200
        }
201
    }
1✔
202

203
    /**
204
     * Shutdown the running Scheduler.
205
     *
206
     * Asynchronous method. use isShutdown() to determine if the Scheduler has Shutdown
207
     *
208
     * @param  waitForJobsToComplete Should we wait for currently executing jobs
209
     * to complete before shutting down?
210
     */
211
    @Override
212
    public void shutdown(final boolean waitForJobsToComplete) {
213
        try {
214
            getScheduler().shutdown(waitForJobsToComplete);
1✔
215
        } catch(final SchedulerException se) {
1✔
216
            LOG.warn("Unable to shutdown the Scheduler:{}", se.getMessage(), se);
×
217
        }
218
    }
1✔
219

220
    @Override
221
    public boolean isShutdown() {
222
        try {
223
            return getScheduler().isShutdown();
×
224
        } catch(final SchedulerException se) {
×
225
            LOG.warn("Unable to determine the status of the Scheduler: {}", se.getMessage(), se);
×
226
        }
227
        return false;
×
228
    }
229

230
    /**
231
     * Create Periodic Job
232
     *
233
     * @param period  The period, in milliseconds.
234
     * @param job     The job to trigger after each period
235
     * @param delay   &lt;= 0, start now, otherwise start in specified number of milliseconds
236
     *
237
     * @return  true if the job was successfully scheduled, false otherwise
238
     */
239
    @Override
240
    public boolean createPeriodicJob(final long period, final JobDescription job, final long delay) {
241
        return createPeriodicJob(period, job, delay, null, SimpleTrigger.REPEAT_INDEFINITELY);
1✔
242
    }
243

244
    /**
245
     * Create Periodic Job
246
     *
247
     * @param   period  The period, in milliseconds.
248
     * @param   job     The job to trigger after each period
249
     * @param   delay   &lt;= 0, start now, otherwise start in specified number of milliseconds
250
     * @param   params  Any parameters to pass to the job
251
     *
252
     * @return  true if the job was successfully scheduled, false otherwise
253
     */
254
    @Override
255
    public boolean createPeriodicJob(final long period, final JobDescription job, final long delay, final Properties params) {
256
        return createPeriodicJob(period, job, delay, params, SimpleTrigger.REPEAT_INDEFINITELY);
1✔
257
    }
258

259
    /**
260
     * Create Periodic Job
261
     *
262
     * @param   period       The period, in milliseconds.
263
     * @param   job          The job to trigger after each period
264
     * @param   delay        &lt;= 0, start now, otherwise start in specified number of milliseconds
265
     * @param   params       Any parameters to pass to the job
266
     * @param   repeatCount  Number of times to repeat this job.
267
     *
268
     * @return  true if the job was successfully scheduled, false otherwise
269
     */
270
    @Override
271
    public boolean createPeriodicJob(final long period, final JobDescription job, final long delay, final Properties params, final int repeatCount) {
272
        return createPeriodicJob(period, job, delay, params, repeatCount, true);
1✔
273
    }
274

275
    /**
276
     * Create Periodic Job
277
     *
278
     * @param   period       The period, in milliseconds.
279
     * @param   job          The job to trigger after each period
280
     * @param   delay        &lt;= 0, start now, otherwise start in specified number of milliseconds
281
     * @param   params       Any parameters to pass to the job
282
     * @param   repeatCount  Number of times to repeat this job.
283
     * @param   unschedule   Unschedule job on XPathException?
284
     *
285
     * @return  true if the job was successfully scheduled, false otherwise
286
     */
287
    @Override
288
    public boolean createPeriodicJob(final long period, final JobDescription job, final long delay, final Properties params, final int repeatCount, final boolean unschedule) {
289
        //Create the job details
290
        final JobDetail jobDetail = newJob((Class<? extends Job>)job.getClass())
1✔
291
            .withIdentity(job.getName(), job.getGroup()).build();
1✔
292
        
293
        //Setup the job's data map
294
        final JobDataMap jobDataMap = jobDetail.getJobDataMap();
1✔
295
        setupJobDataMap(job, jobDataMap, params, unschedule);
1✔
296

297
        //setup a trigger for the job, millisecond based
298
        final TriggerBuilder triggerBuilder = newTrigger()
1✔
299
            .withIdentity(job.getName() + " Trigger", job.getGroup())
1✔
300
            .withSchedule(
1✔
301
                simpleSchedule()
1✔
302
                    .withIntervalInMilliseconds(period)
1✔
303
                    .withRepeatCount(repeatCount)
1✔
304
            );
305

306
        //when should the trigger start
307
        final Trigger trigger;
308
        if(delay <= 0) {
1✔
309
            //start now
310
            trigger = triggerBuilder.startNow().build();
1✔
311
        } else {
1✔
312
            //start after period
313
            final Calendar start = Calendar.getInstance();
1✔
314
            start.add(Calendar.MILLISECOND, (int)delay);
1✔
315
            final Date triggerStart = start.getTime();
1✔
316
            trigger = triggerBuilder.startAt(triggerStart).build();
1✔
317
        }
318
        
319
        //schedule the job
320
        try {
321
            getScheduler().scheduleJob(jobDetail, trigger);
1✔
322
        } catch(final SchedulerException se) {
1✔
323
            //Failed to schedule Job
324
            LOG.error("Failed to schedule periodic job '{}': {}", job.getName(), se.getMessage(), se);
×
325
            return false ;
×
326
        }
327
        //Successfully scheduled Job
328
        return true;
1✔
329
    }
330

331
    /**
332
     * Create Cron Job
333
     *
334
     * @param   cronExpression  The Cron scheduling expression
335
     * @param   job             The job to trigger after each period
336
     *
337
     * @return  true if the job was successfully scheduled, false otherwise
338
     */
339
    @Override
340
    public boolean createCronJob(final String cronExpression, final JobDescription job) {
341
        return createCronJob(cronExpression, job, null);
×
342
    }
343

344
    /**
345
     * Create Cron Job
346
     *
347
     * @param   cronExpression  The Cron scheduling expression
348
     * @param   job             The job to trigger after each period
349
     * @param   params          Any parameters to pass to the job
350
     *
351
     * @return  true if the job was successfully scheduled, false otherwise
352
     */
353
    @Override
354
    public boolean createCronJob(final String cronExpression, final JobDescription job, final Properties params) {
355
        return createCronJob(cronExpression, job, params, true);
×
356
    }
357

358
    /**
359
     * Create Cron Job
360
     *
361
     * @param   cronExpression  The Cron scheduling expression
362
     * @param   job             The job to trigger after each period
363
     * @param   params          Any parameters to pass to the job
364
     * @param   unschedule   Unschedule job on XPathException?.
365
     *
366
     * @return  true if the job was successfully scheduled, false otherwise
367
     */
368
    @Override
369
    public boolean createCronJob(final String cronExpression, final JobDescription job, final Properties params, final boolean unschedule) {
370
        //Create the job details
371
        final JobDetail jobDetail = newJob((Class<? extends Job>)job.getClass())
×
372
            .withIdentity(job.getName(), job.getGroup()).build();
×
373
        
374
        //Setup the job's data map
375
        final JobDataMap jobDataMap = jobDetail.getJobDataMap();
×
376
        setupJobDataMap(job, jobDataMap, params, unschedule);
×
377
        
378
        try {
379
            //setup a trigger for the job, Cron based
380
            final Trigger trigger = newTrigger()
×
381
                .withIdentity(job.getName() + " Trigger", job.getGroup())
×
382
                .withSchedule(cronSchedule(cronExpression)).build();
×
383
            
384
            //schedule the job
385
            getScheduler().scheduleJob(jobDetail, trigger);
×
386
        } catch(final SchedulerException se) {
×
387
            //Failed to schedule Job
388
            LOG.error("Failed to schedule cron job '{}': {}", job.getName(), se.getMessage(), se);
×
389
            return false;
×
390
        }
391
        //Successfully scheduled Job
392
        return true;
×
393
    }
394

395
    /**
396
     * Removes a Job from the Scheduler.
397
     *
398
     * @param   jobName   The name of the Job
399
     * @param   jobGroup  The group that the Job was Scheduled in
400
     *
401
     * @return  true if the job was deleted, false otherwise
402
     */
403
    @Override
404
    public boolean deleteJob(final String jobName, final String jobGroup) {
405
        boolean deletedJob = false;
×
406
        try {
407
            deletedJob = getScheduler().deleteJob(new JobKey(jobName, jobGroup));
×
408
        } catch(final SchedulerException se) {
×
409
            LOG.error("Failed to delete job '{}': {}", jobName, se.getMessage(), se);
×
410
        }
411
        return deletedJob;
×
412
    }
413

414
    /**
415
     * Pauses a Job with the Scheduler.
416
     *
417
     * @param   jobName   The name of the Job
418
     * @param   jobGroup  The group that the Job was Scheduled in
419
     *
420
     * @return  true if the job was paused, false otherwise
421
     */
422
    @Override
423
    public boolean pauseJob(final String jobName, final String jobGroup) {
424
        try {
425
            getScheduler().pauseJob(new JobKey(jobName, jobGroup));
×
426
            return true;
×
427
        } catch(final SchedulerException se) {
×
428
            LOG.error("Failed to pause job '{}': {}", jobName, se.getMessage(), se);
×
429
        }
430
        return false;
×
431
    }
432

433
    /**
434
     * Resume a Job with the Scheduler.
435
     *
436
     * @param   jobName   The name of the Job
437
     * @param   jobGroup  The group that the Job was Scheduled in
438
     *
439
     * @return  true if the job was resumed, false otherwise
440
     */
441
    @Override
442
    public boolean resumeJob(final String jobName, final String jobGroup) {
443
        try {
444
            getScheduler().resumeJob(new JobKey(jobName, jobGroup));
×
445
            return true;
×
446
        } catch(final SchedulerException se) {
×
447
            LOG.error("Failed to resume job '{}': {}", jobName, se.getMessage(), se);
×
448
        }
449
        return false;
×
450
    }
451

452
    /**
453
     * Gets the names of the Job groups.
454
     *
455
     * @return  String array of the Job group names
456
     */
457
    @Override
458
    public List<String> getJobGroupNames() {
459
        final List<String> jobNames = new ArrayList<>();
×
460
        try {
461
            jobNames.addAll(getScheduler().getJobGroupNames());
×
462
        } catch(final SchedulerException se) {
×
463
            LOG.error("Failed to get job group names: {}", se.getMessage(), se);
×
464
        }
465
        return jobNames;
×
466
    }
467

468
    /**
469
     * Gets information about currently Scheduled Jobs.
470
     *
471
     * @return  An array of ScheduledJobInfo
472
     */
473
    @Override
474
    public List<ScheduledJobInfo> getScheduledJobs() {
475
        final List<ScheduledJobInfo> scheduledJobs = new ArrayList<>();
1✔
476
        try {
477
            //get the trigger groups
478
            for(final String triggerGroupName : getScheduler().getTriggerGroupNames()) {
1✔
479
                //get the trigger names for the trigger group
480
                for(final TriggerKey triggerKey : getScheduler().getTriggerKeys(GroupMatcher.triggerGroupEquals(triggerGroupName))) {
1✔
481
                    //add information about the job to the result
482
                    scheduledJobs.add(new ScheduledJobInfo(getScheduler(), getScheduler().getTrigger(triggerKey)));
1✔
483
                }
484
            }
485
        } catch(final SchedulerException se) {
1✔
486
            LOG.error("Failed to get scheduled jobs: {}", se.getMessage(), se);
×
487
        }
488
        return scheduledJobs;
1✔
489
    }
490

491
    /**
492
     * Gets information about currently Executing Jobs.
493
     *
494
     * @return  An array of ScheduledJobInfo
495
     */
496
    @Override
497
    public ScheduledJobInfo[] getExecutingJobs() {
498
        ScheduledJobInfo result[] = null;
×
499
        try {
500
            final List<ScheduledJobInfo> jobs = getScheduler()
×
501
                .getCurrentlyExecutingJobs()
×
502
                .stream()
×
503
                .map(jobExecutionCtx -> new ScheduledJobInfo(getScheduler(), jobExecutionCtx.getTrigger()))
×
504
                .collect(Collectors.toList());
×
505

506
            result = new ScheduledJobInfo[jobs.size()];
×
507
            jobs.toArray(result);
×
508
        } catch(final SchedulerException se) {
×
509
            LOG.error("Failed to get executing jobs: {}", se.getMessage(), se);
×
510
        }
511
        return result;
×
512
    }
513

514
    /**
515
     * Set's up all the jobs that are listed in conf.xml and loaded through org.exist.util.Configuration.
516
     */
517
    @Override
518
    public void setupConfiguredJobs() {
519
        final JobConfig[] jobList = (JobConfig[])config.getProperty(JobConfig.PROPERTY_SCHEDULER_JOBS);
1✔
520
        
521
        if(jobList == null) {
1!
522
            return;
1✔
523
        }
524
        
525
        for(final JobConfig jobConfig : jobList) {
×
526
            JobDescription job = null;
×
527
            if(jobConfig.getResourceName().startsWith("/db/") || jobConfig.getResourceName().indexOf(':') > 0) {
×
528
                if(jobConfig.getType().equals(JobType.SYSTEM)) {
×
529
                    LOG.error("System jobs may only be written in Java");
×
530
                } else {
×
531
                    //create an XQuery job
532
                    final Subject guestUser = brokerPool.getSecurityManager().getGuestSubject();
×
533
                    job = new UserXQueryJob(jobConfig.getJobName(), jobConfig.getResourceName(), guestUser);
×
534
                    try {
535
                        // check if a job with the same name is already registered
536
                        if(getScheduler().getJobDetail(new JobKey(job.getName(), UserJob.JOB_GROUP)) != null) {
×
537
                            // yes, try to make the job's name unique
538
                            job.setName(job.getName() + job.hashCode());
×
539
                        }
540
                        
541
                    } catch(final SchedulerException e) {
×
542
                        LOG.error("Unable to set job name: {}", e.getMessage(), e);
×
543
                    }
544
                }
545
                
546
            } else {
×
547
                //create a Java job
548
                try {
549
                    final Class<?> jobClass = Class.forName(jobConfig.getResourceName());
×
550
                    final Object jobObject = jobClass.newInstance();
×
551
                    if(jobConfig.getType().equals(JobType.SYSTEM)) {
×
552
                        if(jobObject instanceof SystemTask task) {
×
553
                            task.configure(config, jobConfig.getParameters());
×
554
                            job = new SystemTaskJobImpl(jobConfig.getJobName(), task);
×
555
                        } else {
×
556
                            LOG.error("System jobs must extend SystemTask");
×
557
                            // throw exception? will be handled nicely
558
                        }
559
                        
560
                    } else {
×
561
                        if(jobObject instanceof JobDescription) {
×
562
                            job = (JobDescription)jobObject;
×
563
                            if(jobConfig.getJobName() != null) {
×
564
                                job.setName(jobConfig.getJobName());
×
565
                            }
566
                        } else {
×
567
                            LOG.error("Startup job {}  must extend org.exist.scheduler.StartupJob", jobConfig.getJobName());
×
568
                            // throw exception? will be handled nicely
569
                        }
570
                    }
571
                    
572
                } catch(final Exception e) { // Throwable?
×
573
                    LOG.error("Unable to schedule '{}' job {}: {}", jobConfig.getType(), jobConfig.getResourceName(), e.getMessage(), e);
×
574
                }
575
            }
576
            
577
            //if there is a job, schedule it
578
            if(job != null) {
×
579
                //timed job
580
                //trigger is Cron or period?
581
                if(jobConfig.getSchedule().indexOf(' ') > -1) {
×
582
                    //schedule job with Cron trigger
583
                    createCronJob(jobConfig.getSchedule(), job, jobConfig.getParameters());
×
584
                } else {
×
585
                    //schedule job with periodic trigger
586
                    createPeriodicJob(Long.parseLong(jobConfig.getSchedule()), job, jobConfig.getDelay(), jobConfig.getParameters(), jobConfig.getRepeat(), jobConfig.unscheduleOnException());
×
587
                }
588
            }
589
        }
590
    }
×
591

592
    /**
593
     * Sets up the Job's Data Map.
594
     *
595
     * @param  job         The Job
596
     * @param  jobDataMap  The Job's Data Map
597
     * @param  params      Any parameters for the job
598
     */
599
    private void setupJobDataMap(final JobDescription job, final JobDataMap jobDataMap, final Properties params, final boolean unschedule) {
600
        //if this is a system job, store the BrokerPool in the job's data map
601
        jobDataMap.put(DATABASE, brokerPool);
1✔
602
        //if this is a system task job, store the SystemTask in the job's data map
603
        if(job instanceof SystemTaskJobImpl) {
1✔
604
            jobDataMap.put(SYSTEM_TASK, ((SystemTaskJobImpl)job).getSystemTask());
1✔
605
        }
606
        //if this is a users XQuery job, store the XQuery resource and user in the job's data map
607
        if(job instanceof UserXQueryJob) {
1!
608
            jobDataMap.put(XQUERY_SOURCE, ((UserXQueryJob)job).getXQueryResource());
×
609
            jobDataMap.put(ACCOUNT, ((UserXQueryJob)job).getUser());
×
610
        }
611
        //copy any parameters into the job's data map
612
        if(params != null) {
1✔
613
            jobDataMap.put(PARAMS, params);
1✔
614
        }
615
        //Store the value of the unschedule setting
616
        jobDataMap.put(UNSCHEDULE, Boolean.valueOf(unschedule));
1✔
617
    }
1✔
618

619
    /**
620
     * Creates a new Scheduler
621
     */
622
    private static class QuartzSchedulerCreator implements Runnable {
623
        private final Properties quartzProperties;
624
        @Nullable
625
        private volatile Either<SchedulerException, org.quartz.Scheduler> scheduler = null;
1✔
626

627
        public QuartzSchedulerCreator(final Properties quartzProperties) {
1✔
628
            this.quartzProperties = quartzProperties;
1✔
629
        }
1✔
630

631
        @Nullable
632
        public Either<SchedulerException, org.quartz.Scheduler> getScheduler() {
633
            return scheduler;
1✔
634
        }
635

636
        @Override
637
        public void run() {
638
            try {
639
                final SchedulerFactory schedulerFactory = new StdSchedulerFactory(quartzProperties);
1✔
640
                this.scheduler = Either.Right(schedulerFactory.getScheduler());
1✔
641
            } catch(final SchedulerException e) {
1✔
642
                this.scheduler = Either.Left(e);
×
643
            }
644
        }
1✔
645
    }
646
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc