1
2 /*
3 * Copyright 2004-2005 OpenSymphony
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6 * use this file except in compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 * License for the specific language governing permissions and limitations
15 * under the License.
16 *
17 */
18
19 /*
20 * Previously Copyright (c) 2001-2004 James House
21 */
22 package org.quartz.core;
23
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.rmi.RemoteException;
27 import java.rmi.registry.LocateRegistry;
28 import java.rmi.registry.Registry;
29 import java.rmi.server.UnicastRemoteObject;
30 import java.util.ArrayList;
31 import java.util.Date;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.LinkedList;
35 import java.util.List;
36 import java.util.Properties;
37 import java.util.Random;
38 import java.util.Set;
39
40 import org.apache.commons.logging.Log;
41 import org.apache.commons.logging.LogFactory;
42 import org.quartz.Calendar;
43 import org.quartz.InterruptableJob;
44 import org.quartz.Job;
45 import org.quartz.JobDataMap;
46 import org.quartz.JobDetail;
47 import org.quartz.JobExecutionContext;
48 import org.quartz.JobExecutionException;
49 import org.quartz.JobListener;
50 import org.quartz.JobPersistenceException;
51 import org.quartz.ObjectAlreadyExistsException;
52 import org.quartz.Scheduler;
53 import org.quartz.SchedulerContext;
54 import org.quartz.SchedulerException;
55 import org.quartz.SchedulerListener;
56 import org.quartz.listeners.SchedulerListenerSupport;
57 import org.quartz.Trigger;
58 import org.quartz.TriggerListener;
59 import org.quartz.UnableToInterruptJobException;
60 import org.quartz.impl.SchedulerRepository;
61 import org.quartz.simpl.SimpleJobFactory;
62 import org.quartz.spi.JobFactory;
63 import org.quartz.spi.SchedulerPlugin;
64 import org.quartz.spi.SchedulerSignaler;
65
66 /**
67 * <p>
68 * This is the heart of Quartz, an indirect implementation of the <code>{@link org.quartz.Scheduler}</code>
69 * interface, containing methods to schedule <code>{@link org.quartz.Job}</code>s,
70 * register <code>{@link org.quartz.JobListener}</code> instances, etc.
71 * </p>// TODO: more docs...
72 *
73 * @see org.quartz.Scheduler
74 * @see org.quartz.core.QuartzSchedulerThread
75 * @see org.quartz.spi.JobStore
76 * @see org.quartz.spi.ThreadPool
77 *
78 * @author James House
79 */
80 public class QuartzScheduler implements RemotableQuartzScheduler {
81
82 /*
83 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
84 *
85 * Constants.
86 *
87 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
88 */
89
90 private static String VERSION_MAJOR = "UNKNOWN";
91 private static String VERSION_MINOR = "UNKNOWN";
92 private static String VERSION_ITERATION = "UNKNOWN";
93
94 static {
95 Properties props = new Properties();
96 try {
97 InputStream is =
98 QuartzScheduler.class.getResourceAsStream("/build.properties");
99 if(is != null) {
100 props.load(is);
101 VERSION_MAJOR = props.getProperty("version.major");
102 VERSION_MINOR = props.getProperty("version.minor");
103 VERSION_ITERATION = props.getProperty("version.iter");
104 }
105 } catch (IOException e) {
106 (LogFactory.getLog(QuartzScheduler.class)).error(
107 "Error loading version info from build.properties.", e);
108 }
109 }
110
111
112 /*
113 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
114 *
115 * Data members.
116 *
117 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
118 */
119
120 private QuartzSchedulerResources resources;
121
122 private QuartzSchedulerThread schedThread;
123
124 private ThreadGroup threadGroup;
125
126 private SchedulerContext context = new SchedulerContext();
127
128 private HashMap jobListeners = new HashMap(10);
129
130 private HashMap globalJobListeners = new HashMap(10);
131
132 private HashMap triggerListeners = new HashMap(10);
133
134 private HashMap globalTriggerListeners = new HashMap(10);
135
136 private ArrayList schedulerListeners = new ArrayList(10);
137
138 private JobFactory jobFactory = new SimpleJobFactory();
139
140 ExecutingJobsManager jobMgr = null;
141
142 ErrorLogger errLogger = null;
143
144 private SchedulerSignaler signaler;
145
146 private Random random = new Random();
147
148 private ArrayList holdToPreventGC = new ArrayList(5);
149
150 private boolean signalOnSchedulingChange = true;
151
152 private boolean closed = false;
153
154 private Date initialStart = null;
155
156 private final Log log = LogFactory.getLog(getClass());
157
158 /*
159 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
160 *
161 * Constructors.
162 *
163 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
164 */
165
166 /**
167 * <p>
168 * Create a <code>QuartzScheduler</code> with the given configuration
169 * properties.
170 * </p>
171 *
172 * @see QuartzSchedulerResources
173 */
174 public QuartzScheduler(QuartzSchedulerResources resources,
175 SchedulingContext ctxt, long idleWaitTime, long dbRetryInterval)
176 throws SchedulerException {
177 this.resources = resources;
178 try {
179 bind();
180 } catch (Exception re) {
181 throw new SchedulerException(
182 "Unable to bind scheduler to RMI Registry.", re);
183 }
184
185 if (resources.getJMXExport()) {
186 try {
187 registerJMX();
188 } catch (Exception e) {
189 throw new SchedulerException(
190 "Unable to register scheduler with MBeanServer.", e);
191 }
192 }
193
194 this.schedThread = new QuartzSchedulerThread(this, resources, ctxt);
195 if (idleWaitTime > 0) {
196 this.schedThread.setIdleWaitTime(idleWaitTime);
197 }
198 if (dbRetryInterval > 0) {
199 this.schedThread.setDbFailureRetryInterval(dbRetryInterval);
200 }
201
202 jobMgr = new ExecutingJobsManager();
203 addGlobalJobListener(jobMgr);
204 errLogger = new ErrorLogger();
205 addSchedulerListener(errLogger);
206
207 signaler = new SchedulerSignalerImpl(this);
208
209 getLog().info("Quartz Scheduler v." + getVersion() + " created.");
210 }
211
212 /*
213 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
214 *
215 * Interface.
216 *
217 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
218 */
219
220 public String getVersion() {
221 return getVersionMajor() + "." + getVersionMinor() + "."
222 + getVersionIteration();
223 }
224
225 public static String getVersionMajor() {
226 return VERSION_MAJOR;
227 }
228
229 public static String getVersionMinor() {
230 return VERSION_MINOR;
231 }
232
233 public static String getVersionIteration() {
234 return VERSION_ITERATION;
235 }
236
237 public SchedulerSignaler getSchedulerSignaler() {
238 return signaler;
239 }
240
241 public Log getLog() {
242 return log;
243 }
244
245 /**
246 * Register the scheduler in the local MBeanServer.
247 */
248 private void registerJMX() throws Exception {
249 org.apache.commons.modeler.Registry registry =
250 org.apache.commons.modeler.Registry.getRegistry(null, null);
251
252 String jmxObjectName = resources.getJMXObjectName();
253
254 registry.registerComponent(this, jmxObjectName, null);
255
256 getLog().info("Scheduler registered with local MBeanServer under name '" + jmxObjectName + "'");
257 }
258
259 /**
260 * Unregister the scheduler from the local MBeanServer.
261 */
262 private void unregisterJMX() throws Exception {
263 org.apache.commons.modeler.Registry registry =
264 org.apache.commons.modeler.Registry.getRegistry(null, null);
265
266 String jmxObjectName = resources.getJMXObjectName();
267
268 registry.unregisterComponent(jmxObjectName);
269
270 getLog().info("Scheduler unregistered from name '" + jmxObjectName + "' in the local MBeanServer.");
271 }
272
273 /**
274 * <p>
275 * Bind the scheduler to an RMI registry.
276 * </p>
277 */
278 private void bind() throws RemoteException {
279 String host = resources.getRMIRegistryHost();
280 // don't export if we're not configured to do so...
281 if (host == null || host.length() == 0) {
282 return;
283 }
284
285 RemotableQuartzScheduler exportable = null;
286
287 if(resources.getRMIServerPort() > 0) {
288 exportable = (RemotableQuartzScheduler) UnicastRemoteObject
289 .exportObject(this, resources.getRMIServerPort());
290 } else {
291 exportable = (RemotableQuartzScheduler) UnicastRemoteObject
292 .exportObject(this);
293 }
294
295 Registry registry = null;
296
297 if (resources.getRMICreateRegistryStrategy().equals(
298 QuartzSchedulerResources.CREATE_REGISTRY_AS_NEEDED)) {
299 try {
300 // First try to get an existing one, instead of creating it,
301 // since if
302 // we're in a web-app being 'hot' re-depoloyed, then the JVM
303 // still
304 // has the registry that we created above the first time...
305 registry = LocateRegistry.getRegistry(resources
306 .getRMIRegistryPort());
307 registry.list();
308 } catch (Exception e) {
309 registry = LocateRegistry.createRegistry(resources
310 .getRMIRegistryPort());
311 }
312 } else if (resources.getRMICreateRegistryStrategy().equals(
313 QuartzSchedulerResources.CREATE_REGISTRY_ALWAYS)) {
314 try {
315 registry = LocateRegistry.createRegistry(resources
316 .getRMIRegistryPort());
317 } catch (Exception e) {
318 // Fall back to an existing one, instead of creating it, since
319 // if
320 // we're in a web-app being 'hot' re-depoloyed, then the JVM
321 // still
322 // has the registry that we created above the first time...
323 registry = LocateRegistry.getRegistry(resources
324 .getRMIRegistryPort());
325 }
326 } else {
327 registry = LocateRegistry.getRegistry(resources
328 .getRMIRegistryHost(), resources.getRMIRegistryPort());
329 }
330
331 String bindName = resources.getRMIBindName();
332
333 registry.rebind(bindName, exportable);
334
335 getLog().info("Scheduler bound to RMI registry under name '" + bindName + "'");
336 }
337
338 /**
339 * <p>
340 * Un-bind the scheduler from an RMI registry.
341 * </p>
342 */
343 private void unBind() throws RemoteException {
344 String host = resources.getRMIRegistryHost();
345 // don't un-export if we're not configured to do so...
346 if (host == null || host.length() == 0) {
347 return;
348 }
349
350 Registry registry = LocateRegistry.getRegistry(resources
351 .getRMIRegistryHost(), resources.getRMIRegistryPort());
352
353 String bindName = resources.getRMIBindName();
354
355 try {
356 registry.unbind(bindName);
357 UnicastRemoteObject.unexportObject(this, true);
358 } catch (java.rmi.NotBoundException nbe) {
359 }
360
361 getLog().info("Scheduler un-bound from name '" + bindName + "' in RMI registry");
362 }
363
364 /**
365 * <p>
366 * Returns the name of the <code>QuartzScheduler</code>.
367 * </p>
368 */
369 public String getSchedulerName() {
370 return resources.getName();
371 }
372
373 /**
374 * <p>
375 * Returns the instance Id of the <code>QuartzScheduler</code>.
376 * </p>
377 */
378 public String getSchedulerInstanceId() {
379 return resources.getInstanceId();
380 }
381
382 /**
383 * <p>
384 * Returns the name of the <code>QuartzScheduler</code>.
385 * </p>
386 */
387 public ThreadGroup getSchedulerThreadGroup() {
388 if (threadGroup == null) {
389 threadGroup = new ThreadGroup("QuartzScheduler:"
390 + getSchedulerName());
391 if (resources.getMakeSchedulerThreadDaemon()) {
392 threadGroup.setDaemon(true);
393 }
394 }
395
396 return threadGroup;
397 }
398
399 public void addNoGCObject(Object obj) {
400 holdToPreventGC.add(obj);
401 }
402
403 public boolean removeNoGCObject(Object obj) {
404 return holdToPreventGC.remove(obj);
405 }
406
407 /**
408 * <p>
409 * Returns the <code>SchedulerContext</code> of the <code>Scheduler</code>.
410 * </p>
411 */
412 public SchedulerContext getSchedulerContext() throws SchedulerException {
413 return context;
414 }
415
416 public boolean isSignalOnSchedulingChange() {
417 return signalOnSchedulingChange;
418 }
419
420 public void setSignalOnSchedulingChange(boolean signalOnSchedulingChange) {
421 this.signalOnSchedulingChange = signalOnSchedulingChange;
422 }
423
424 ///////////////////////////////////////////////////////////////////////////
425 ///
426 /// Schedululer State Management Methods
427 ///
428 ///////////////////////////////////////////////////////////////////////////
429
430 /**
431 * <p>
432 * Starts the <code>QuartzScheduler</code>'s threads that fire <code>{@link org.quartz.Trigger}s</code>.
433 * </p>
434 *
435 * <p>
436 * All <code>{@link org.quartz.Trigger}s</code> that have misfired will
437 * be passed to the appropriate TriggerListener(s).
438 * </p>
439 */
440 public void start() throws SchedulerException {
441
442 if (closed) {
443 throw new SchedulerException(
444 "The Scheduler cannot be restarted after shutdown() has been called.");
445 }
446
447 if (initialStart == null) {
448 initialStart = new Date();
449 this.resources.getJobStore().schedulerStarted();
450 startPlugins();
451 }
452
453 schedThread.togglePause(false);
454
455 getLog().info(
456 "Scheduler " + resources.getUniqueIdentifier() + " started.");
457 }
458
459 /**
460 * <p>
461 * Temporarily halts the <code>QuartzScheduler</code>'s firing of <code>{@link org.quartz.Trigger}s</code>.
462 * </p>
463 *
464 * <p>
465 * The scheduler is not destroyed, and can be re-started at any time.
466 * </p>
467 */
468 public void standby() {
469 schedThread.togglePause(true);
470 getLog().info(
471 "Scheduler " + resources.getUniqueIdentifier() + " paused.");
472 }
473
474 /**
475 * <p>
476 * Reports whether the <code>Scheduler</code> is paused.
477 * </p>
478 */
479 public boolean isInStandbyMode() {
480 return schedThread.isPaused();
481 }
482
483 public Date runningSince() {
484 return initialStart;
485 }
486
487 public int numJobsExecuted() {
488 return jobMgr.getNumJobsFired();
489 }
490
491 public Class getJobStoreClass() {
492 return resources.getJobStore().getClass();
493 }
494
495 public boolean supportsPersistence() {
496 return resources.getJobStore().supportsPersistence();
497 }
498
499 public Class getThreadPoolClass() {
500 return resources.getThreadPool().getClass();
501 }
502
503 public int getThreadPoolSize() {
504 return resources.getThreadPool().getPoolSize();
505 }
506
507 /**
508 * <p>
509 * Halts the <code>QuartzScheduler</code>'s firing of <code>{@link org.quartz.Trigger}s</code>,
510 * and cleans up all resources associated with the QuartzScheduler.
511 * Equivalent to <code>shutdown(false)</code>.
512 * </p>
513 *
514 * <p>
515 * The scheduler cannot be re-started.
516 * </p>
517 */
518 public void shutdown() {
519 shutdown(false);
520 }
521
522 /**
523 * <p>
524 * Halts the <code>QuartzScheduler</code>'s firing of <code>{@link org.quartz.Trigger}s</code>,
525 * and cleans up all resources associated with the QuartzScheduler.
526 * </p>
527 *
528 * <p>
529 * The scheduler cannot be re-started.
530 * </p>
531 *
532 * @param waitForJobsToComplete
533 * if <code>true</code> the scheduler will not allow this method
534 * to return until all currently executing jobs have completed.
535 */
536 public void shutdown(boolean waitForJobsToComplete) {
537
538 if(closed == true) {
539 return;
540 }
541
542 getLog().info(
543 "Scheduler " + resources.getUniqueIdentifier()
544 + " shutting down.");
545 standby();
546
547 closed = true;
548
549 schedThread.halt();
550
551 resources.getThreadPool().shutdown(waitForJobsToComplete);
552
553 if (waitForJobsToComplete) {
554 while (jobMgr.getNumJobsCurrentlyExecuting() > 0) {
555 try {
556 Thread.sleep(100);
557 } catch (Exception ignore) {
558 }
559 }
560 }
561
562 // Scheduler thread may have be waiting for the fire time of an acquired
563 // trigger and need time to release the trigger once halted, so make sure
564 // the thread is dead before continuing to shutdown the job store.
565 try {
566 schedThread.join();
567 } catch (InterruptedException ignore) {
568 }
569
570 resources.getJobStore().shutdown();
571
572 notifySchedulerListenersShutdown();
573
574 shutdownPlugins();
575
576 SchedulerRepository.getInstance().remove(resources.getName());
577
578 holdToPreventGC.clear();
579
580 try {
581 unBind();
582 } catch (RemoteException re) {
583 }
584
585 if (resources.getJMXExport()) {
586 try {
587 unregisterJMX();
588 } catch (Exception e) {
589 }
590 }
591
592 getLog().info(
593 "Scheduler " + resources.getUniqueIdentifier()
594 + " shutdown complete.");
595 }
596
597 /**
598 * <p>
599 * Reports whether the <code>Scheduler</code> has been shutdown.
600 * </p>
601 */
602 public boolean isShutdown() {
603 return closed;
604 }
605
606 public void validateState() throws SchedulerException {
607 if (isShutdown()) {
608 throw new SchedulerException("The Scheduler has been shutdown.");
609 }
610
611 // other conditions to check (?)
612 }
613
614 /**
615 * <p>
616 * Return a list of <code>JobExecutionContext</code> objects that
617 * represent all currently executing Jobs in this Scheduler instance.
618 * </p>
619 *
620 * <p>
621 * This method is not cluster aware. That is, it will only return Jobs
622 * currently executing in this Scheduler instance, not across the entire
623 * cluster.
624 * </p>
625 *
626 * <p>
627 * Note that the list returned is an 'instantaneous' snap-shot, and that as
628 * soon as it's returned, the true list of executing jobs may be different.
629 * </p>
630 */
631 public List getCurrentlyExecutingJobs() {
632 return jobMgr.getExecutingJobs();
633 }
634
635 ///////////////////////////////////////////////////////////////////////////
636 ///
637 /// Scheduling-related Methods
638 ///
639 ///////////////////////////////////////////////////////////////////////////
640
641 /**
642 * <p>
643 * Add the <code>{@link org.quartz.Job}</code> identified by the given
644 * <code>{@link org.quartz.JobDetail}</code> to the Scheduler, and
645 * associate the given <code>{@link org.quartz.Trigger}</code> with it.
646 * </p>
647 *
648 * <p>
649 * If the given Trigger does not reference any <code>Job</code>, then it
650 * will be set to reference the Job passed with it into this method.
651 * </p>
652 *
653 * @throws SchedulerException
654 * if the Job or Trigger cannot be added to the Scheduler, or
655 * there is an internal Scheduler error.
656 */
657 public Date scheduleJob(SchedulingContext ctxt, JobDetail jobDetail,
658 Trigger trigger) throws SchedulerException {
659 validateState();
660
661 if (jobDetail == null) {
662 throw new SchedulerException("JobDetail cannot be null",
663 SchedulerException.ERR_CLIENT_ERROR);
664 }
665
666 if (trigger == null) {
667 throw new SchedulerException("Trigger cannot be null",
668 SchedulerException.ERR_CLIENT_ERROR);
669 }
670
671 jobDetail.validate();
672
673 if (trigger.getJobName() == null) {
674 trigger.setJobName(jobDetail.getName());
675 trigger.setJobGroup(jobDetail.getGroup());
676 } else if (trigger.getJobName() != null
677 && !trigger.getJobName().equals(jobDetail.getName())) {
678 throw new SchedulerException(
679 "Trigger does not reference given job!",
680 SchedulerException.ERR_CLIENT_ERROR);
681 } else if (trigger.getJobGroup() != null
682 && !trigger.getJobGroup().equals(jobDetail.getGroup())) {
683 throw new SchedulerException(
684 "Trigger does not reference given job!",
685 SchedulerException.ERR_CLIENT_ERROR);
686 }
687
688 trigger.validate();
689
690 Calendar cal = null;
691 if (trigger.getCalendarName() != null) {
692 cal = resources.getJobStore().retrieveCalendar(ctxt,
693 trigger.getCalendarName());
694 }
695 Date ft = trigger.computeFirstFireTime(cal);
696
697 if (ft == null) {
698 throw new SchedulerException(
699 "Based on configured schedule, the given trigger will never fire.",
700 SchedulerException.ERR_CLIENT_ERROR);
701 }
702
703 resources.getJobStore().storeJobAndTrigger(ctxt, jobDetail, trigger);
704 notifySchedulerThread();
705 notifySchedulerListenersSchduled(trigger);
706
707 return ft;
708 }
709
710 /**
711 * <p>
712 * Schedule the given <code>{@link org.quartz.Trigger}</code> with the
713 * <code>Job</code> identified by the <code>Trigger</code>'s settings.
714 * </p>
715 *
716 * @throws SchedulerException
717 * if the indicated Job does not exist, or the Trigger cannot be
718 * added to the Scheduler, or there is an internal Scheduler
719 * error.
720 */
721 public Date scheduleJob(SchedulingContext ctxt, Trigger trigger)
722 throws SchedulerException {
723 validateState();
724
725 if (trigger == null) {
726 throw new SchedulerException("Trigger cannot be null",
727 SchedulerException.ERR_CLIENT_ERROR);
728 }
729
730 trigger.validate();
731
732 Calendar cal = null;
733 if (trigger.getCalendarName() != null) {
734 cal = resources.getJobStore().retrieveCalendar(ctxt,
735 trigger.getCalendarName());
736 if(cal == null) {
737 throw new SchedulerException(
738 "Calendar not found: " + trigger.getCalendarName(),
739 SchedulerException.ERR_PERSISTENCE_CALENDAR_DOES_NOT_EXIST);
740 }
741 }
742 Date ft = trigger.computeFirstFireTime(cal);
743
744 if (ft == null) {
745 throw new SchedulerException(
746 "Based on configured schedule, the given trigger will never fire.",
747 SchedulerException.ERR_CLIENT_ERROR);
748 }
749
750 resources.getJobStore().storeTrigger(ctxt, trigger, false);
751 notifySchedulerThread();
752 notifySchedulerListenersSchduled(trigger);
753
754 return ft;
755 }
756
757 /**
758 * <p>
759 * Add the given <code>Job</code> to the Scheduler - with no associated
760 * <code>Trigger</code>. The <code>Job</code> will be 'dormant' until
761 * it is scheduled with a <code>Trigger</code>, or <code>Scheduler.triggerJob()</code>
762 * is called for it.
763 * </p>
764 *
765 * <p>
766 * The <code>Job</code> must by definition be 'durable', if it is not,
767 * SchedulerException will be thrown.
768 * </p>
769 *
770 * @throws SchedulerException
771 * if there is an internal Scheduler error, or if the Job is not
772 * durable, or a Job with the same name already exists, and
773 * <code>replace</code> is <code>false</code>.
774 */
775 public void addJob(SchedulingContext ctxt, JobDetail jobDetail,
776 boolean replace) throws SchedulerException {
777 validateState();
778
779 if (!jobDetail.isDurable() && !replace) {
780 throw new SchedulerException(
781 "Jobs added with no trigger must be durable.",
782 SchedulerException.ERR_CLIENT_ERROR);
783 }
784
785 resources.getJobStore().storeJob(ctxt, jobDetail, replace);
786 }
787
788 /**
789 * <p>
790 * Delete the identified <code>Job</code> from the Scheduler - and any
791 * associated <code>Trigger</code>s.
792 * </p>
793 *
794 * @return true if the Job was found and deleted.
795 * @throws SchedulerException
796 * if there is an internal Scheduler error.
797 */
798 public boolean deleteJob(SchedulingContext ctxt, String jobName,
799 String groupName) throws SchedulerException {
800 validateState();
801
802 if(groupName == null) {
803 groupName = Scheduler.DEFAULT_GROUP;
804 }
805
806 return resources.getJobStore().removeJob(ctxt, jobName, groupName);
807 }
808
809 /**
810 * <p>
811 * Remove the indicated <code>{@link org.quartz.Trigger}</code> from the
812 * scheduler.
813 * </p>
814 */
815 public boolean unscheduleJob(SchedulingContext ctxt, String triggerName,
816 String groupName) throws SchedulerException {
817 validateState();
818
819 if(groupName == null) {
820 groupName = Scheduler.DEFAULT_GROUP;
821 }
822
823 if (resources.getJobStore().removeTrigger(ctxt, triggerName, groupName)) {
824 notifySchedulerThread();
825 notifySchedulerListenersUnschduled(triggerName, groupName);
826 } else {
827 return false;
828 }
829
830 return true;
831 }
832
833
834 /**
835 * <p>
836 * Remove (delete) the <code>{@link org.quartz.Trigger}</code> with the
837 * given name, and store the new given one - which must be associated
838 * with the same job.
839 * </p>
840 *
841 * @param triggerName
842 * The name of the <code>Trigger</code> to be removed.
843 * @param groupName
844 * The group name of the <code>Trigger</code> to be removed.
845 * @param newTrigger
846 * The new <code>Trigger</code> to be stored.
847 * @return <code>null</code> if a <code>Trigger</code> with the given
848 * name & group was not found and removed from the store, otherwise
849 * the first fire time of the newly scheduled trigger.
850 */
851 public Date rescheduleJob(SchedulingContext ctxt, String triggerName,
852 String groupName, Trigger newTrigger) throws SchedulerException {
853 validateState();
854
855 if(groupName == null) {
856 groupName = Scheduler.DEFAULT_GROUP;
857 }
858
859 newTrigger.validate();
860
861 Calendar cal = null;
862 if (newTrigger.getCalendarName() != null) {
863 cal = resources.getJobStore().retrieveCalendar(ctxt,
864 newTrigger.getCalendarName());
865 }
866 Date ft = newTrigger.computeFirstFireTime(cal);
867
868 if (ft == null) {
869 throw new SchedulerException(
870 "Based on configured schedule, the given trigger will never fire.",
871 SchedulerException.ERR_CLIENT_ERROR);
872 }
873
874 if (resources.getJobStore().replaceTrigger(ctxt, triggerName, groupName, newTrigger)) {
875 notifySchedulerThread();
876 notifySchedulerListenersUnschduled(triggerName, groupName);
877 notifySchedulerListenersSchduled(newTrigger);
878 } else {
879 return null;
880 }
881
882 return ft;
883
884 }
885
886
887 private String newTriggerId() {
888 long r = random.nextLong();
889 if (r < 0) {
890 r = -r;
891 }
892 return "MT_"
893 + Long.toString(r, 30 + (int) (System.currentTimeMillis() % 7));
894 }
895
896 /**
897 * <p>
898 * Trigger the identified <code>{@link org.quartz.Job}</code> (execute it
899 * now) - with a non-volatile trigger.
900 * </p>
901 */
902 public void triggerJob(SchedulingContext ctxt, String jobName,
903 String groupName, JobDataMap data) throws SchedulerException {
904 validateState();
905
906 if(groupName == null) {
907 groupName = Scheduler.DEFAULT_GROUP;
908 }
909
910 Trigger trig = new org.quartz.SimpleTrigger(newTriggerId(),
911 Scheduler.DEFAULT_MANUAL_TRIGGERS, jobName, groupName,
912 new Date(), null, 0, 0);
913 trig.setVolatility(false);
914 trig.computeFirstFireTime(null);
915 if(data != null) {
916 trig.setJobDataMap(data);
917 }
918
919 boolean collision = true;
920 while (collision) {
921 try {
922 resources.getJobStore().storeTrigger(ctxt, trig, false);
923 collision = false;
924 } catch (ObjectAlreadyExistsException oaee) {
925 trig.setName(newTriggerId());
926 }
927 }
928
929 notifySchedulerThread();
930 notifySchedulerListenersSchduled(trig);
931 }
932
933 /**
934 * <p>
935 * Trigger the identified <code>{@link org.quartz.Job}</code> (execute it
936 * now) - with a volatile trigger.
937 * </p>
938 */
939 public void triggerJobWithVolatileTrigger(SchedulingContext ctxt,
940 String jobName, String groupName, JobDataMap data) throws SchedulerException {
941 validateState();
942
943 if(groupName == null) {
944 groupName = Scheduler.DEFAULT_GROUP;
945 }
946
947 Trigger trig = new org.quartz.SimpleTrigger(newTriggerId(),
948 Scheduler.DEFAULT_MANUAL_TRIGGERS, jobName, groupName,
949 new Date(), null, 0, 0);
950 trig.setVolatility(true);
951 trig.computeFirstFireTime(null);
952 if(data != null) {
953 trig.setJobDataMap(data);
954 }
955
956 boolean collision = true;
957 while (collision) {
958 try {
959 resources.getJobStore().storeTrigger(ctxt, trig, false);
960 collision = false;
961 } catch (ObjectAlreadyExistsException oaee) {
962 trig.setName(newTriggerId());
963 }
964 }
965
966 notifySchedulerThread();
967 notifySchedulerListenersSchduled(trig);
968 }
969
970 /**
971 * <p>
972 * Pause the <code>{@link Trigger}</code> with the given name.
973 * </p>
974 *
975 */
976 public void pauseTrigger(SchedulingContext ctxt, String triggerName,
977 String groupName) throws SchedulerException {
978 validateState();
979
980 if(groupName == null) {
981 groupName = Scheduler.DEFAULT_GROUP;
982 }
983
984 resources.getJobStore().pauseTrigger(ctxt, triggerName, groupName);
985 notifySchedulerThread();
986 notifySchedulerListenersPausedTrigger(triggerName, groupName);
987 }
988
989 /**
990 * <p>
991 * Pause all of the <code>{@link Trigger}s</code> in the given group.
992 * </p>
993 *
994 */
995 public void pauseTriggerGroup(SchedulingContext ctxt, String groupName)
996 throws SchedulerException {
997 validateState();
998
999 if(groupName == null) {
1000 groupName = Scheduler.DEFAULT_GROUP;
1001 }
1002
1003 resources.getJobStore().pauseTriggerGroup(ctxt, groupName);
1004 notifySchedulerThread();
1005 notifySchedulerListenersPausedTrigger(null, groupName);
1006 }
1007
1008 /**
1009 * <p>
1010 * Pause the <code>{@link org.quartz.JobDetail}</code> with the given
1011 * name - by pausing all of its current <code>Trigger</code>s.
1012 * </p>
1013 *
1014 */
1015 public void pauseJob(SchedulingContext ctxt, String jobName,
1016 String groupName) throws SchedulerException {
1017 validateState();
1018
1019 if(groupName == null) {
1020 groupName = Scheduler.DEFAULT_GROUP;
1021 }
1022
1023 resources.getJobStore().pauseJob(ctxt, jobName, groupName);
1024 notifySchedulerThread();
1025 notifySchedulerListenersPausedJob(jobName, groupName);
1026 }
1027
1028 /**
1029 * <p>
1030 * Pause all of the <code>{@link org.quartz.JobDetail}s</code> in the
1031 * given group - by pausing all of their <code>Trigger</code>s.
1032 * </p>
1033 *
1034 */
1035 public void pauseJobGroup(SchedulingContext ctxt, String groupName)
1036 throws SchedulerException {
1037 validateState();
1038
1039 if(groupName == null) {
1040 groupName = Scheduler.DEFAULT_GROUP;
1041 }
1042
1043 resources.getJobStore().pauseJobGroup(ctxt, groupName);
1044 notifySchedulerThread();
1045 notifySchedulerListenersPausedJob(null, groupName);
1046 }
1047
1048 /**
1049 * <p>
1050 * Resume (un-pause) the <code>{@link Trigger}</code> with the given
1051 * name.
1052 * </p>
1053 *
1054 * <p>
1055 * If the <code>Trigger</code> missed one or more fire-times, then the
1056 * <code>Trigger</code>'s misfire instruction will be applied.
1057 * </p>
1058 *
1059 */
1060 public void resumeTrigger(SchedulingContext ctxt, String triggerName,
1061 String groupName) throws SchedulerException {
1062 validateState();
1063
1064 if(groupName == null) {
1065 groupName = Scheduler.DEFAULT_GROUP;
1066 }
1067
1068 resources.getJobStore().resumeTrigger(ctxt, triggerName, groupName);
1069 notifySchedulerThread();
1070 notifySchedulerListenersResumedTrigger(triggerName, groupName);
1071 }
1072
1073 /**
1074 * <p>
1075 * Resume (un-pause) all of the <code>{@link Trigger}s</code> in the
1076 * given group.
1077 * </p>
1078 *
1079 * <p>
1080 * If any <code>Trigger</code> missed one or more fire-times, then the
1081 * <code>Trigger</code>'s misfire instruction will be applied.
1082 * </p>
1083 *
1084 */
1085 public void resumeTriggerGroup(SchedulingContext ctxt, String groupName)
1086 throws SchedulerException {
1087 validateState();
1088
1089 if(groupName == null) {
1090 groupName = Scheduler.DEFAULT_GROUP;
1091 }
1092
1093 resources.getJobStore().resumeTriggerGroup(ctxt, groupName);
1094 notifySchedulerThread();
1095 notifySchedulerListenersResumedTrigger(null, groupName);
1096 }
1097
1098 public Set getPausedTriggerGroups(SchedulingContext ctxt) throws SchedulerException {
1099 return resources.getJobStore().getPausedTriggerGroups(ctxt);
1100 }
1101
1102 /**
1103 * <p>
1104 * Resume (un-pause) the <code>{@link org.quartz.JobDetail}</code> with
1105 * the given name.
1106 * </p>
1107 *
1108 * <p>
1109 * If any of the <code>Job</code>'s<code>Trigger</code> s missed one
1110 * or more fire-times, then the <code>Trigger</code>'s misfire
1111 * instruction will be applied.
1112 * </p>
1113 *
1114 */
1115 public void resumeJob(SchedulingContext ctxt, String jobName,
1116 String groupName) throws SchedulerException {
1117 validateState();
1118
1119 if(groupName == null) {
1120 groupName = Scheduler.DEFAULT_GROUP;
1121 }
1122
1123 resources.getJobStore().resumeJob(ctxt, jobName, groupName);
1124 notifySchedulerThread();
1125 notifySchedulerListenersResumedJob(jobName, groupName);
1126 }
1127
1128 /**
1129 * <p>
1130 * Resume (un-pause) all of the <code>{@link org.quartz.JobDetail}s</code>
1131 * in the given group.
1132 * </p>
1133 *
1134 * <p>
1135 * If any of the <code>Job</code> s had <code>Trigger</code> s that
1136 * missed one or more fire-times, then the <code>Trigger</code>'s
1137 * misfire instruction will be applied.
1138 * </p>
1139 *
1140 */
1141 public void resumeJobGroup(SchedulingContext ctxt, String groupName)
1142 throws SchedulerException {
1143 validateState();
1144
1145 if(groupName == null) {
1146 groupName = Scheduler.DEFAULT_GROUP;
1147 }
1148
1149 resources.getJobStore().resumeJobGroup(ctxt, groupName);
1150 notifySchedulerThread();
1151 notifySchedulerListenersResumedJob(null, groupName);
1152 }
1153
1154 /**
1155 * <p>
1156 * Pause all triggers - equivalent of calling <code>pauseTriggerGroup(group)</code>
1157 * on every group.
1158 * </p>
1159 *
1160 * <p>
1161 * When <code>resumeAll()</code> is called (to un-pause), trigger misfire
1162 * instructions WILL be applied.
1163 * </p>
1164 *
1165 * @see #resumeAll(SchedulingContext)
1166 * @see #pauseTriggerGroup(SchedulingContext, String)
1167 * @see #standby()
1168 */
1169 public void pauseAll(SchedulingContext ctxt) throws SchedulerException {
1170 validateState();
1171
1172 resources.getJobStore().pauseAll(ctxt);
1173 notifySchedulerThread();
1174 notifySchedulerListenersPausedTrigger(null, null);
1175 }
1176
1177 /**
1178 * <p>
1179 * Resume (un-pause) all triggers - equivalent of calling <code>resumeTriggerGroup(group)</code>
1180 * on every group.
1181 * </p>
1182 *
1183 * <p>
1184 * If any <code>Trigger</code> missed one or more fire-times, then the
1185 * <code>Trigger</code>'s misfire instruction will be applied.
1186 * </p>
1187 *
1188 * @see #pauseAll(SchedulingContext)
1189 */
1190 public void resumeAll(SchedulingContext ctxt) throws SchedulerException {
1191 validateState();
1192
1193 resources.getJobStore().resumeAll(ctxt);
1194 notifySchedulerThread();
1195 notifySchedulerListenersResumedTrigger(null, null);
1196 }
1197
1198 /**
1199 * <p>
1200 * Get the names of all known <code>{@link org.quartz.Job}</code> groups.
1201 * </p>
1202 */
1203 public String[] getJobGroupNames(SchedulingContext ctxt)
1204 throws SchedulerException {
1205 validateState();
1206
1207 return resources.getJobStore().getJobGroupNames(ctxt);
1208 }
1209
1210 /**
1211 * <p>
1212 * Get the names of all the <code>{@link org.quartz.Job}s</code> in the
1213 * given group.
1214 * </p>
1215 */
1216 public String[] getJobNames(SchedulingContext ctxt, String groupName)
1217 throws SchedulerException {
1218 validateState();
1219
1220 if(groupName == null) {
1221 groupName = Scheduler.DEFAULT_GROUP;
1222 }
1223
1224 return resources.getJobStore().getJobNames(ctxt, groupName);
1225 }
1226
1227 /**
1228 * <p>
1229 * Get all <code>{@link Trigger}</code> s that are associated with the
1230 * identified <code>{@link org.quartz.JobDetail}</code>.
1231 * </p>
1232 */
1233 public Trigger[] getTriggersOfJob(SchedulingContext ctxt, String jobName,
1234 String groupName) throws SchedulerException {
1235 validateState();
1236
1237 if(groupName == null) {
1238 groupName = Scheduler.DEFAULT_GROUP;
1239 }
1240
1241 return resources.getJobStore().getTriggersForJob(ctxt, jobName,
1242 groupName);
1243 }
1244
1245 /**
1246 * <p>
1247 * Get the names of all known <code>{@link org.quartz.Trigger}</code>
1248 * groups.
1249 * </p>
1250 */
1251 public String[] getTriggerGroupNames(SchedulingContext ctxt)
1252 throws SchedulerException {
1253 validateState();
1254
1255 return resources.getJobStore().getTriggerGroupNames(ctxt);
1256 }
1257
1258 /**
1259 * <p>
1260 * Get the names of all the <code>{@link org.quartz.Trigger}s</code> in
1261 * the given group.
1262 * </p>
1263 */
1264 public String[] getTriggerNames(SchedulingContext ctxt, String groupName)
1265 throws SchedulerException {
1266 validateState();
1267
1268 if(groupName == null) {
1269 groupName = Scheduler.DEFAULT_GROUP;
1270 }
1271
1272 return resources.getJobStore().getTriggerNames(ctxt, groupName);
1273 }
1274
1275 /**
1276 * <p>
1277 * Get the <code>{@link JobDetail}</code> for the <code>Job</code>
1278 * instance with the given name and group.
1279 * </p>
1280 */
1281 public JobDetail getJobDetail(SchedulingContext ctxt, String jobName,
1282 String jobGroup) throws SchedulerException {
1283 validateState();
1284
1285 if(jobGroup == null) {
1286 jobGroup = Scheduler.DEFAULT_GROUP;
1287 }
1288
1289 return resources.getJobStore().retrieveJob(ctxt, jobName, jobGroup);
1290 }
1291
1292 /**
1293 * <p>
1294 * Get the <code>{@link Trigger}</code> instance with the given name and
1295 * group.
1296 * </p>
1297 */
1298 public Trigger getTrigger(SchedulingContext ctxt, String triggerName,
1299 String triggerGroup) throws SchedulerException {
1300 validateState();
1301
1302 if(triggerGroup == null) {
1303 triggerGroup = Scheduler.DEFAULT_GROUP;
1304 }
1305
1306 return resources.getJobStore().retrieveTrigger(ctxt, triggerName,
1307 triggerGroup);
1308 }
1309
1310 /**
1311 * <p>
1312 * Get the current state of the identified <code>{@link Trigger}</code>.
1313 * </p>
1314 *
1315 * @see Trigger#STATE_NORMAL
1316 * @see Trigger#STATE_PAUSED
1317 * @see Trigger#STATE_COMPLETE
1318 * @see Trigger#STATE_ERROR
1319 */
1320 public int getTriggerState(SchedulingContext ctxt, String triggerName,
1321 String triggerGroup) throws SchedulerException {
1322 validateState();
1323
1324 if(triggerGroup == null) {
1325 triggerGroup = Scheduler.DEFAULT_GROUP;
1326 }
1327
1328 return resources.getJobStore().getTriggerState(ctxt, triggerName,
1329 triggerGroup);
1330 }
1331
1332 /**
1333 * <p>
1334 * Add (register) the given <code>Calendar</code> to the Scheduler.
1335 * </p>
1336 *
1337 * @throws SchedulerException
1338 * if there is an internal Scheduler error, or a Calendar with
1339 * the same name already exists, and <code>replace</code> is
1340 * <code>false</code>.
1341 */
1342 public void addCalendar(SchedulingContext ctxt, String calName,
1343 Calendar calendar, boolean replace, boolean updateTriggers) throws SchedulerException {
1344 validateState();
1345
1346 resources.getJobStore().storeCalendar(ctxt, calName, calendar, replace, updateTriggers);
1347 }
1348
1349 /**
1350 * <p>
1351 * Delete the identified <code>Calendar</code> from the Scheduler.
1352 * </p>
1353 *
1354 * @return true if the Calendar was found and deleted.
1355 * @throws SchedulerException
1356 * if there is an internal Scheduler error.
1357 */
1358 public boolean deleteCalendar(SchedulingContext ctxt, String calName)
1359 throws SchedulerException {
1360 validateState();
1361
1362 return resources.getJobStore().removeCalendar(ctxt, calName);
1363 }
1364
1365 /**
1366 * <p>
1367 * Get the <code>{@link Calendar}</code> instance with the given name.
1368 * </p>
1369 */
1370 public Calendar getCalendar(SchedulingContext ctxt, String calName)
1371 throws SchedulerException {
1372 validateState();
1373
1374 return resources.getJobStore().retrieveCalendar(ctxt, calName);
1375 }
1376
1377 /**
1378 * <p>
1379 * Get the names of all registered <code>{@link Calendar}s</code>.
1380 * </p>
1381 */
1382 public String[] getCalendarNames(SchedulingContext ctxt)
1383 throws SchedulerException {
1384 validateState();
1385
1386 return resources.getJobStore().getCalendarNames(ctxt);
1387 }
1388
1389 /**
1390 * <p>
1391 * Add the given <code>{@link org.quartz.JobListener}</code> to the
1392 * <code>Scheduler</code>'s<i>global</i> list.
1393 * </p>
1394 *
1395 * <p>
1396 * Listeners in the 'global' list receive notification of execution events
1397 * for ALL <code>{@link org.quartz.Job}</code>s.
1398 * </p>
1399 */
1400 public void addGlobalJobListener(JobListener jobListener) {
1401 if (jobListener.getName() == null
1402 || jobListener.getName().length() == 0) {
1403 throw new IllegalArgumentException(
1404 "JobListener name cannot be empty.");
1405 }
1406
1407 synchronized (globalJobListeners) {
1408 globalJobListeners.put(jobListener.getName(), jobListener);
1409 }
1410 }
1411
1412 /**
1413 * <p>
1414 * Add the given <code>{@link org.quartz.JobListener}</code> to the
1415 * <code>Scheduler</code>'s list, of registered <code>JobListener</code>s.
1416 */
1417 public void addJobListener(JobListener jobListener) {
1418 if (jobListener.getName() == null
1419 || jobListener.getName().length() == 0) {
1420 throw new IllegalArgumentException(
1421 "JobListener name cannot be empty.");
1422 }
1423
1424 synchronized (jobListeners) {
1425 jobListeners.put(jobListener.getName(), jobListener);
1426 }
1427 }
1428
1429 /**
1430 * <p>
1431 * Remove the given <code>{@link org.quartz.JobListener}</code> from the
1432 * <code>Scheduler</code>'s list of <i>global</i> listeners.
1433 * </p>
1434 *
1435 * @return true if the identifed listener was found in the list, and
1436 * removed.
1437 *
1438 * @deprecated Use <code>{@link #removeGlobalJobListener(String)}</code>
1439 */
1440 public boolean removeGlobalJobListener(JobListener jobListener) {
1441 return removeGlobalJobListener((jobListener == null) ? null : jobListener.getName());
1442 }
1443
1444 /**
1445 * <p>
1446 * Remove the identifed <code>{@link JobListener}</code> from the <code>Scheduler</code>'s
1447 * list of <i>global</i> listeners.
1448 * </p>
1449 *
1450 * @return true if the identifed listener was found in the list, and
1451 * removed.
1452 */
1453 public boolean removeGlobalJobListener(String name) {
1454 synchronized (globalJobListeners) {
1455 return (globalJobListeners.remove(name) != null);
1456 }
1457 }
1458
1459 /**
1460 * <p>
1461 * Remove the identifed <code>{@link org.quartz.JobListener}</code> from
1462 * the <code>Scheduler</code>'s list of registered listeners.
1463 * </p>
1464 *
1465 * @return true if the identifed listener was found in the list, and
1466 * removed.
1467 */
1468 public boolean removeJobListener(String name) {
1469 synchronized (jobListeners) {
1470 return (jobListeners.remove(name) != null);
1471 }
1472 }
1473
1474 /**
1475 * <p>
1476 * Get a List containing all of the <code>{@link org.quartz.JobListener}</code>
1477 * s in the <code>Scheduler</code>'s<i>global</i> list.
1478 * </p>
1479 */
1480 public List getGlobalJobListeners() {
1481 synchronized (globalJobListeners) {
1482 return new LinkedList(globalJobListeners.values());
1483 }
1484 }
1485
1486 /**
1487 * <p>
1488 * Get a Set containing the names of all the <i>non-global</i><code>{@link org.quartz.JobListener}</code>
1489 * s registered with the <code>Scheduler</code>.
1490 * </p>
1491 */
1492 public Set getJobListenerNames() {
1493 synchronized (jobListeners) {
1494 return new HashSet(jobListeners.keySet());
1495 }
1496 }
1497
1498 /**
1499 * <p>
1500 * Get the <i>global</i><code>{@link org.quartz.JobListener}</code>
1501 * that has the given name.
1502 * </p>
1503 */
1504 public JobListener getGlobalJobListener(String name) {
1505 synchronized (globalJobListeners) {
1506 return (JobListener)globalJobListeners.get(name);
1507 }
1508 }
1509
1510 /**
1511 * <p>
1512 * Get the <i>non-global</i><code>{@link org.quartz.JobListener}</code>
1513 * that has the given name.
1514 * </p>
1515 */
1516 public JobListener getJobListener(String name) {
1517 synchronized (jobListeners) {
1518 return (JobListener) jobListeners.get(name);
1519 }
1520 }
1521
1522 /**
1523 * <p>
1524 * Add the given <code>{@link org.quartz.TriggerListener}</code> to the
1525 * <code>Scheduler</code>'s<i>global</i> list.
1526 * </p>
1527 *
1528 * <p>
1529 * Listeners in the 'global' list receive notification of execution events
1530 * for ALL <code>{@link org.quartz.Trigger}</code>s.
1531 * </p>
1532 */
1533 public void addGlobalTriggerListener(TriggerListener triggerListener) {
1534 if (triggerListener.getName() == null
1535 || triggerListener.getName().length() == 0) {
1536 throw new IllegalArgumentException(
1537 "TriggerListener name cannot be empty.");
1538 }
1539
1540 synchronized (globalTriggerListeners) {
1541 globalTriggerListeners.put(triggerListener.getName(), triggerListener);
1542 }
1543 }
1544
1545 /**
1546 * <p>
1547 * Add the given <code>{@link org.quartz.TriggerListener}</code> to the
1548 * <code>Scheduler</code>'s list, of registered <code>TriggerListener</code>s.
1549 */
1550 public void addTriggerListener(TriggerListener triggerListener) {
1551 if (triggerListener.getName() == null
1552 || triggerListener.getName().length() == 0) {
1553 throw new IllegalArgumentException(
1554 "TriggerListener name cannot be empty.");
1555 }
1556
1557 synchronized (triggerListeners) {
1558 triggerListeners.put(triggerListener.getName(), triggerListener);
1559 }
1560 }
1561
1562 /**
1563 * <p>
1564 * Remove the given <code>{@link org.quartz.TriggerListener}</code> from
1565 * the <code>Scheduler</code>'s list of <i>global</i> listeners.
1566 * </p>
1567 *
1568 * @return true if the identifed listener was found in the list, and
1569 * removed.
1570 *
1571 * @deprecated Use <code>{@link #removeGlobalTriggerListener(String)}</code>
1572 */
1573 public boolean removeGlobalTriggerListener(TriggerListener triggerListener) {
1574 return removeGlobalTriggerListener((triggerListener == null) ? null : triggerListener.getName());
1575 }
1576
1577 /**
1578 * <p>
1579 * Remove the identifed <code>{@link TriggerListener}</code> from the <code>Scheduler</code>'s
1580 * list of <i>global</i> listeners.
1581 * </p>
1582 *
1583 * @return true if the identifed listener was found in the list, and
1584 * removed.
1585 */
1586 public boolean removeGlobalTriggerListener(String name) {
1587 synchronized (globalTriggerListeners) {
1588 return (globalTriggerListeners.remove(name) != null);
1589 }
1590 }
1591
1592 /**
1593 * <p>
1594 * Remove the identifed <code>{@link org.quartz.TriggerListener}</code>
1595 * from the <code>Scheduler</code>'s list of registered listeners.
1596 * </p>
1597 *
1598 * @return true if the identifed listener was found in the list, and
1599 * removed.
1600 */
1601 public boolean removeTriggerListener(String name) {
1602 synchronized (triggerListeners) {
1603 return (triggerListeners.remove(name) != null);
1604 }
1605 }
1606
1607 /**
1608 * <p>
1609 * Get a list containing all of the <code>{@link org.quartz.TriggerListener}</code>
1610 * s in the <code>Scheduler</code>'s<i>global</i> list.
1611 * </p>
1612 */
1613 public List getGlobalTriggerListeners() {
1614 synchronized (globalTriggerListeners) {
1615 return new LinkedList(globalTriggerListeners.values());
1616 }
1617 }
1618
1619 /**
1620 * <p>
1621 * Get a Set containing the names of all the <i>non-global</i><code>{@link org.quartz.TriggerListener}</code>
1622 * s registered with the <code>Scheduler</code>.
1623 * </p>
1624 */
1625 public Set getTriggerListenerNames() {
1626 synchronized (triggerListeners) {
1627 return new HashSet(triggerListeners.keySet());
1628 }
1629 }
1630
1631 /**
1632 * <p>
1633 * Get the <i>global</i><code>{@link TriggerListener}</code> that
1634 * has the given name.
1635 * </p>
1636 */
1637 public TriggerListener getGlobalTriggerListener(String name) {
1638 synchronized (globalTriggerListeners) {
1639 return (TriggerListener)globalTriggerListeners.get(name);
1640 }
1641 }
1642
1643 /**
1644 * <p>
1645 * Get the <i>non-global</i><code>{@link org.quartz.TriggerListener}</code>
1646 * that has the given name.
1647 * </p>
1648 */
1649 public TriggerListener getTriggerListener(String name) {
1650 synchronized (triggerListeners) {
1651 return (TriggerListener) triggerListeners.get(name);
1652 }
1653 }
1654
1655 /**
1656 * <p>
1657 * Register the given <code>{@link SchedulerListener}</code> with the
1658 * <code>Scheduler</code>.
1659 * </p>
1660 */
1661 public void addSchedulerListener(SchedulerListener schedulerListener) {
1662 synchronized (schedulerListeners) {
1663 schedulerListeners.add(schedulerListener);
1664 }
1665 }
1666
1667 /**
1668 * <p>
1669 * Remove the given <code>{@link SchedulerListener}</code> from the
1670 * <code>Scheduler</code>.
1671 * </p>
1672 *
1673 * @return true if the identifed listener was found in the list, and
1674 * removed.
1675 */
1676 public boolean removeSchedulerListener(SchedulerListener schedulerListener) {
1677 synchronized (schedulerListeners) {
1678 return schedulerListeners.remove(schedulerListener);
1679 }
1680 }
1681
1682 /**
1683 * <p>
1684 * Get a List containing all of the <code>{@link SchedulerListener}</code>
1685 * s registered with the <code>Scheduler</code>.
1686 * </p>
1687 */
1688 public List getSchedulerListeners() {
1689 synchronized (schedulerListeners) {
1690 return (List)schedulerListeners.clone();
1691 }
1692 }
1693
1694 protected void notifyJobStoreJobComplete(SchedulingContext ctxt,
1695 Trigger trigger, JobDetail detail, int instCode)
1696 throws JobPersistenceException {
1697
1698 resources.getJobStore().triggeredJobComplete(ctxt, trigger, detail,
1699 instCode);
1700 }
1701
1702 protected void notifyJobStoreJobVetoed(SchedulingContext ctxt,
1703 Trigger trigger, JobDetail detail, int instCode)
1704 throws JobPersistenceException {
1705
1706 resources.getJobStore().triggeredJobComplete(ctxt, trigger, detail, instCode);
1707 }
1708
1709 protected void notifySchedulerThread() {
1710 if (isSignalOnSchedulingChange()) {
1711 schedThread.signalSchedulingChange();
1712 }
1713 }
1714
1715 private List buildTriggerListenerList(String[] additionalLstnrs)
1716 throws SchedulerException {
1717 List triggerListeners = getGlobalTriggerListeners();
1718 for (int i = 0; i < additionalLstnrs.length; i++) {
1719 TriggerListener tl = getTriggerListener(additionalLstnrs[i]);
1720
1721 if (tl != null) {
1722 triggerListeners.add(tl);
1723 } else {
1724 throw new SchedulerException("TriggerListener '"
1725 + additionalLstnrs[i] + "' not found.",
1726 SchedulerException.ERR_TRIGGER_LISTENER_NOT_FOUND);
1727 }
1728 }
1729
1730 return triggerListeners;
1731 }
1732
1733 private List buildJobListenerList(String[] additionalLstnrs)
1734 throws SchedulerException {
1735 List jobListeners = getGlobalJobListeners();
1736 for (int i = 0; i < additionalLstnrs.length; i++) {
1737 JobListener jl = getJobListener(additionalLstnrs[i]);
1738
1739 if (jl != null) {
1740 jobListeners.add(jl);
1741 } else {
1742 throw new SchedulerException("JobListener '"
1743 + additionalLstnrs[i] + "' not found.",
1744 SchedulerException.ERR_JOB_LISTENER_NOT_FOUND);
1745 }
1746 }
1747
1748 return jobListeners;
1749 }
1750
1751 public boolean notifyTriggerListenersFired(JobExecutionContext jec)
1752 throws SchedulerException {
1753 // build a list of all trigger listeners that are to be notified...
1754 List triggerListeners = buildTriggerListenerList(jec.getTrigger()
1755 .getTriggerListenerNames());
1756
1757 boolean vetoedExecution = false;
1758
1759 // notify all trigger listeners in the list
1760 java.util.Iterator itr = triggerListeners.iterator();
1761 while (itr.hasNext()) {
1762 TriggerListener tl = (TriggerListener) itr.next();
1763 try {
1764 tl.triggerFired(jec.getTrigger(), jec);
1765
1766 if(tl.vetoJobExecution(jec.getTrigger(), jec)) {
1767 vetoedExecution = true;
1768 }
1769 } catch (Exception e) {
1770 SchedulerException se = new SchedulerException(
1771 "TriggerListener '" + tl.getName()
1772 + "' threw exception: " + e.getMessage(), e);
1773 se.setErrorCode(SchedulerException.ERR_TRIGGER_LISTENER);
1774 throw se;
1775 }
1776 }
1777
1778 return vetoedExecution;
1779 }
1780
1781
1782 public void notifyTriggerListenersMisfired(Trigger trigger)
1783 throws SchedulerException {
1784 // build a list of all trigger listeners that are to be notified...
1785 List triggerListeners = buildTriggerListenerList(trigger
1786 .getTriggerListenerNames());
1787
1788 // notify all trigger listeners in the list
1789 java.util.Iterator itr = triggerListeners.iterator();
1790 while (itr.hasNext()) {
1791 TriggerListener tl = (TriggerListener) itr.next();
1792 try {
1793 tl.triggerMisfired(trigger);
1794 } catch (Exception e) {
1795 SchedulerException se = new SchedulerException(
1796 "TriggerListener '" + tl.getName()
1797 + "' threw exception: " + e.getMessage(), e);
1798 se.setErrorCode(SchedulerException.ERR_TRIGGER_LISTENER);
1799 throw se;
1800 }
1801 }
1802 }
1803
1804 public void notifyTriggerListenersComplete(JobExecutionContext jec,
1805 int instCode) throws SchedulerException {
1806 // build a list of all trigger listeners that are to be notified...
1807 List triggerListeners = buildTriggerListenerList(jec.getTrigger()
1808 .getTriggerListenerNames());
1809
1810 // notify all trigger listeners in the list
1811 java.util.Iterator itr = triggerListeners.iterator();
1812 while (itr.hasNext()) {
1813 TriggerListener tl = (TriggerListener) itr.next();
1814 try {
1815 tl.triggerComplete(jec.getTrigger(), jec, instCode);
1816 } catch (Exception e) {
1817 SchedulerException se = new SchedulerException(
1818 "TriggerListener '" + tl.getName()
1819 + "' threw exception: " + e.getMessage(), e);
1820 se.setErrorCode(SchedulerException.ERR_TRIGGER_LISTENER);
1821 throw se;
1822 }
1823 }
1824 }
1825
1826 public void notifyJobListenersToBeExecuted(JobExecutionContext jec)
1827 throws SchedulerException {
1828 // build a list of all job listeners that are to be notified...
1829 List jobListeners = buildJobListenerList(jec.getJobDetail()
1830 .getJobListenerNames());
1831
1832 // notify all job listeners
1833 java.util.Iterator itr = jobListeners.iterator();
1834 while (itr.hasNext()) {
1835 JobListener jl = (JobListener) itr.next();
1836 try {
1837 jl.jobToBeExecuted(jec);
1838 } catch (Exception e) {
1839 SchedulerException se = new SchedulerException(
1840 "JobListener '" + jl.getName() + "' threw exception: "
1841 + e.getMessage(), e);
1842 se.setErrorCode(SchedulerException.ERR_JOB_LISTENER);
1843 throw se;
1844 }
1845 }
1846 }
1847
1848 public void notifyJobListenersWasVetoed(JobExecutionContext jec)
1849 throws SchedulerException {
1850 // build a list of all job listeners that are to be notified...
1851 List jobListeners = buildJobListenerList(jec.getJobDetail()
1852 .getJobListenerNames());
1853
1854 // notify all job listeners
1855 java.util.Iterator itr = jobListeners.iterator();
1856 while (itr.hasNext()) {
1857 JobListener jl = (JobListener) itr.next();
1858 try {
1859 jl.jobExecutionVetoed(jec);
1860 } catch (Exception e) {
1861 SchedulerException se = new SchedulerException(
1862 "JobListener '" + jl.getName() + "' threw exception: "
1863 + e.getMessage(), e);
1864 se.setErrorCode(SchedulerException.ERR_JOB_LISTENER);
1865 throw se;
1866 }
1867 }
1868 }
1869
1870 public void notifyJobListenersWasExecuted(JobExecutionContext jec,
1871 JobExecutionException je) throws SchedulerException {
1872 // build a list of all job listeners that are to be notified...
1873 List jobListeners = buildJobListenerList(jec.getJobDetail()
1874 .getJobListenerNames());
1875
1876 // notify all job listeners
1877 java.util.Iterator itr = jobListeners.iterator();
1878 while (itr.hasNext()) {
1879 JobListener jl = (JobListener) itr.next();
1880 try {
1881 jl.jobWasExecuted(jec, je);
1882 } catch (Exception e) {
1883 SchedulerException se = new SchedulerException(
1884 "JobListener '" + jl.getName() + "' threw exception: "
1885 + e.getMessage(), e);
1886 se.setErrorCode(SchedulerException.ERR_JOB_LISTENER);
1887 throw se;
1888 }
1889 }
1890 }
1891
1892 public void notifySchedulerListenersError(String msg, SchedulerException se) {
1893 // build a list of all scheduler listeners that are to be notified...
1894 List schedListeners = getSchedulerListeners();
1895
1896 // notify all scheduler listeners
1897 java.util.Iterator itr = schedListeners.iterator();
1898 while (itr.hasNext()) {
1899 SchedulerListener sl = (SchedulerListener) itr.next();
1900 try {
1901 sl.schedulerError(msg, se);
1902 } catch (Exception e) {
1903 getLog()
1904 .error(
1905 "Error while notifying SchedulerListener of error: ",
1906 e);
1907 getLog().error(
1908 " Original error (for notification) was: " + msg, se);
1909 }
1910 }
1911 }
1912
1913 public void notifySchedulerListenersSchduled(Trigger trigger) {
1914 // build a list of all scheduler listeners that are to be notified...
1915 List schedListeners = getSchedulerListeners();
1916
1917 // notify all scheduler listeners
1918 java.util.Iterator itr = schedListeners.iterator();
1919 while (itr.hasNext()) {
1920 SchedulerListener sl = (SchedulerListener) itr.next();
1921 try {
1922 sl.jobScheduled(trigger);
1923 } catch (Exception e) {
1924 getLog().error(
1925 "Error while notifying SchedulerListener of scheduled job."
1926 + " Triger=" + trigger.getFullName(), e);
1927 }
1928 }
1929 }
1930
1931 public void notifySchedulerListenersUnschduled(String triggerName,
1932 String triggerGroup) {
1933 // build a list of all scheduler listeners that are to be notified...
1934 List schedListeners = getSchedulerListeners();
1935
1936 // notify all scheduler listeners
1937 java.util.Iterator itr = schedListeners.iterator();
1938 while (itr.hasNext()) {
1939 SchedulerListener sl = (SchedulerListener) itr.next();
1940 try {
1941 sl.jobUnscheduled(triggerName, triggerGroup);
1942 } catch (Exception e) {
1943 getLog().error(
1944 "Error while notifying SchedulerListener of unscheduled job."
1945 + " Triger=" + triggerGroup + "."
1946 + triggerName, e);
1947 }
1948 }
1949 }
1950
1951 public void notifySchedulerListenersFinalized(Trigger trigger) {
1952 // build a list of all scheduler listeners that are to be notified...
1953 List schedListeners = getSchedulerListeners();
1954
1955 // notify all scheduler listeners
1956 java.util.Iterator itr = schedListeners.iterator();
1957 while (itr.hasNext()) {
1958 SchedulerListener sl = (SchedulerListener) itr.next();
1959 try {
1960 sl.triggerFinalized(trigger);
1961 } catch (Exception e) {
1962 getLog().error(
1963 "Error while notifying SchedulerListener of finalized trigger."
1964 + " Triger=" + trigger.getFullName(), e);
1965 }
1966 }
1967 }
1968
1969 public void notifySchedulerListenersPausedTrigger(String name, String group) {
1970 // build a list of all job listeners that are to be notified...
1971 List schedListeners = getSchedulerListeners();
1972
1973 // notify all scheduler listeners
1974 java.util.Iterator itr = schedListeners.iterator();
1975 while (itr.hasNext()) {
1976 SchedulerListener sl = (SchedulerListener) itr.next();
1977 try {
1978 sl.triggersPaused(name, group);
1979 } catch (Exception e) {
1980 getLog().error(
1981 "Error while notifying SchedulerListener of paused trigger/group."
1982 + " Triger=" + group + "." + name, e);
1983 }
1984 }
1985 }
1986
1987 public void notifySchedulerListenersResumedTrigger(String name, String group) {
1988 // build a list of all job listeners that are to be notified...
1989 List schedListeners = getSchedulerListeners();
1990
1991 // notify all scheduler listeners
1992 java.util.Iterator itr = schedListeners.iterator();
1993 while (itr.hasNext()) {
1994 SchedulerListener sl = (SchedulerListener) itr.next();
1995 try {
1996 sl.triggersResumed(name, group);
1997 } catch (Exception e) {
1998 getLog().error(
1999 "Error while notifying SchedulerListener of resumed trigger/group."
2000 + " Triger=" + group + "." + name, e);
2001 }
2002 }
2003 }
2004
2005 public void notifySchedulerListenersPausedJob(String name, String group) {
2006 // build a list of all job listeners that are to be notified...
2007 List schedListeners = getSchedulerListeners();
2008
2009 // notify all scheduler listeners
2010 java.util.Iterator itr = schedListeners.iterator();
2011 while (itr.hasNext()) {
2012 SchedulerListener sl = (SchedulerListener) itr.next();
2013 try {
2014 sl.jobsPaused(name, group);
2015 } catch (Exception e) {
2016 getLog().error(
2017 "Error while notifying SchedulerListener of paused job/group."
2018 + " Job=" + group + "." + name, e);
2019 }
2020 }
2021 }
2022
2023 public void notifySchedulerListenersResumedJob(String name, String group) {
2024 // build a list of all job listeners that are to be notified...
2025 List schedListeners = getSchedulerListeners();
2026
2027 // notify all scheduler listeners
2028 java.util.Iterator itr = schedListeners.iterator();
2029 while (itr.hasNext()) {
2030 SchedulerListener sl = (SchedulerListener) itr.next();
2031 try {
2032 sl.jobsResumed(name, group);
2033 } catch (Exception e) {
2034 getLog().error(
2035 "Error while notifying SchedulerListener of resumed job/group."
2036 + " Job=" + group + "." + name, e);
2037 }
2038 }
2039 }
2040
2041 public void notifySchedulerListenersShutdown() {
2042 // build a list of all job listeners that are to be notified...
2043 List schedListeners = getSchedulerListeners();
2044
2045 // notify all scheduler listeners
2046 java.util.Iterator itr = schedListeners.iterator();
2047 while (itr.hasNext()) {
2048 SchedulerListener sl = (SchedulerListener) itr.next();
2049 try {
2050 sl.schedulerShutdown();
2051 } catch (Exception e) {
2052 getLog().error(
2053 "Error while notifying SchedulerListener of shutdown.",
2054 e);
2055 }
2056 }
2057 }
2058
2059 public void setJobFactory(JobFactory factory) throws SchedulerException {
2060
2061 if(factory == null) {
2062 throw new IllegalArgumentException("JobFactory cannot be set to null!");
2063 }
2064
2065 getLog().info("JobFactory set to: " + factory);
2066
2067 this.jobFactory = factory;
2068 }
2069
2070 public JobFactory getJobFactory() {
2071 return jobFactory;
2072 }
2073
2074
2075 /**
2076 * Interrupt all instances of the identified InterruptableJob executing in
2077 * this Scheduler instance.
2078 *
2079 * <p>
2080 * This method is not cluster aware. That is, it will only interrupt
2081 * instances of the identified InterruptableJob currently executing in this
2082 * Scheduler instance, not across the entire cluster.
2083 * </p>
2084 *
2085 * @see org.quartz.core.RemotableQuartzScheduler#interrupt(org.quartz.core.SchedulingContext, java.lang.String, java.lang.String)
2086 */
2087 public boolean interrupt(SchedulingContext ctxt, String jobName, String groupName) throws UnableToInterruptJobException {
2088
2089 if(groupName == null) {
2090 groupName = Scheduler.DEFAULT_GROUP;
2091 }
2092
2093 List jobs = getCurrentlyExecutingJobs();
2094 java.util.Iterator it = jobs.iterator();
2095
2096 JobExecutionContext jec = null;
2097 JobDetail jobDetail = null;
2098 Job job = null;
2099
2100 boolean interrupted = false;
2101
2102 while (it.hasNext()) {
2103 jec = (JobExecutionContext)it.next();
2104 jobDetail = jec.getJobDetail();
2105 if (jobName.equals(jobDetail.getName())
2106 && groupName.equals(jobDetail.getGroup())){
2107 job = jec.getJobInstance();
2108 if (job instanceof InterruptableJob) {
2109 ((InterruptableJob)job).interrupt();
2110 interrupted = true;
2111 } else {
2112 throw new UnableToInterruptJobException(
2113 "Job '"
2114 + jobName
2115 + "' of group '"
2116 + groupName
2117 + "' can not be interrupted, since it does not implement "
2118 + InterruptableJob.class.getName());
2119
2120 }
2121 }
2122 }
2123
2124 return interrupted;
2125 }
2126
2127 private void shutdownPlugins() {
2128 java.util.Iterator itr = resources.getSchedulerPlugins().iterator();
2129 while (itr.hasNext()) {
2130 SchedulerPlugin plugin = (SchedulerPlugin) itr.next();
2131 plugin.shutdown();
2132 }
2133 }
2134
2135 private void startPlugins() {
2136 java.util.Iterator itr = resources.getSchedulerPlugins().iterator();
2137 while (itr.hasNext()) {
2138 SchedulerPlugin plugin = (SchedulerPlugin) itr.next();
2139 plugin.start();
2140 }
2141 }
2142
2143 }
2144
2145 /////////////////////////////////////////////////////////////////////////////
2146 //
2147 // ErrorLogger - Scheduler Listener Class
2148 //
2149 /////////////////////////////////////////////////////////////////////////////
2150
2151 class ErrorLogger extends SchedulerListenerSupport {
2152 ErrorLogger() {
2153 }
2154
2155 public void schedulerError(String msg, SchedulerException cause) {
2156 getLog().error(msg, cause);
2157 }
2158 }
2159
2160 /////////////////////////////////////////////////////////////////////////////
2161 //
2162 // ExecutingJobsManager - Job Listener Class
2163 //
2164 /////////////////////////////////////////////////////////////////////////////
2165
2166 class ExecutingJobsManager implements JobListener {
2167 HashMap executingJobs = new HashMap();
2168
2169 int numJobsFired = 0;
2170
2171 ExecutingJobsManager() {
2172 }
2173
2174 public String getName() {
2175 return getClass().getName();
2176 }
2177
2178 public int getNumJobsCurrentlyExecuting() {
2179 synchronized (executingJobs) {
2180 return executingJobs.size();
2181 }
2182 }
2183
2184 public void jobToBeExecuted(JobExecutionContext context) {
2185 numJobsFired++;
2186
2187 synchronized (executingJobs) {
2188 executingJobs
2189 .put(context.getTrigger().getFireInstanceId(), context);
2190 }
2191 }
2192
2193 public void jobWasExecuted(JobExecutionContext context,
2194 JobExecutionException jobException) {
2195 synchronized (executingJobs) {
2196 executingJobs.remove(context.getTrigger().getFireInstanceId());
2197 }
2198 }
2199
2200 public int getNumJobsFired() {
2201 return numJobsFired;
2202 }
2203
2204 public List getExecutingJobs() {
2205 synchronized (executingJobs) {
2206 return java.util.Collections.unmodifiableList(new ArrayList(
2207 executingJobs.values()));
2208 }
2209 }
2210
2211 public void jobExecutionVetoed(JobExecutionContext context) {
2212
2213 }
2214 }