1 /*
2 * UICronTrigger.java
3 *
4 * Based on work in Quartz: Copyright James House (c) 2001-2004
5 *
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met: 1.
10 * Redistributions of source code must retain the above copyright notice, this
11 * list of conditions and the following disclaimer. 2. Redistributions in
12 * binary form must reproduce the above copyright notice, this list of
13 * conditions and the following disclaimer in the documentation and/or other
14 * materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 *
27 */
28 package org.quartz;
29
30 import org.quartz.Job;
31 import org.quartz.JobExecutionContext;
32 import org.quartz.JobExecutionException;
33 import org.quartz.Scheduler;
34 import org.quartz.SimpleTrigger;
35 import org.quartz.Trigger;
36 import org.quartz.CronTrigger;
37
38 import java.util.Calendar;
39 import java.util.Date;
40 import java.util.HashMap;
41 import java.util.Iterator;
42 import java.util.Map;
43 import java.util.Set;
44 import java.util.SortedSet;
45 import java.util.TimeZone;
46 import java.util.TreeSet;
47 import java.text.ParseException;
48
49 /**
50 * <p>
51 * A concrete <code>{@link Trigger}</code> that is used to fire a <code>{@link Job}</code>
52 * at given moments in time, defined with Unix 'cron-like' definitions.
53 * </p>
54 *
55 * <p>
56 * What you should know about this particular trigger is that it is based on
57 * org.quartz.CronTrigger, but the the functionality to build the sets from a
58 * string are unused. Whereas CronTrigger would essentially deserialize by
59 * rebuilding the TreeSets from the cronExpression, this class does not have a
60 * cronExpression, and de/serializes the TreeSets in their entirety. This is
61 * because the TreeSets map directly to the Struts user interface for set
62 * selection, and no effort is made to write an interpreter to map them back
63 * and forth between legacy UN*X cron expressions that CronTrigger uses.
64 * </p>
65 *
66 * <p>
67 * The method I use with this trigger is to instantiate it, then put it in the
68 * ActionForm of a Struts bean, then let Struts manipulate it directly through
69 * BeanUtils. You are by no means required to do that, but to fully understand
70 * the concepts here, at least until there is better documentation, you should
71 * understand how it works within that context first so you can write the
72 * appropriate code that Struts does for you for free. I'll try to explain that
73 * here.
74 * </p>
75 *
76 * <p>
77 * Struts JSP tags allow the user to use Apache BeanUtils to reference
78 * components of beans by path. This is to say that a bean <code>Foo</code>
79 * that has an accessor method <code>Bar getBar()</code> and given <code>
80 * Bar</code>
81 * has a primitive type <code>String</code> as a field named <code>splat</code>,
82 * one can set the field to <i>"new string value"</i> as follows:
83 * </p>
84 * <code>
85 * // create a new Foo with contained reference to a new Bar
86 * Foo fooBean = new Foo();
87 * fooBean.setBar(new Bar());
88 * // set the splat string in the Bar bean from Foo
89 * BeanUtils.setProperty(fooBean, "bar.splat", "new string value");
90 * </code>
91 * <p>
92 * In turn, Struts JSP tags use the bean addressing provided by BeanUtils to
93 * address accessor methods within the bean graph that is rooted with the
94 * ActionForm that is put into the Action context.
95 * </p>
96 * <p>
97 * Finally, having all this allows you to put direct selection lists on the
98 * screen of the UI, then map them directly into the UICronTrigger bean. Given
99 * a ActionForm bean that was set up to contain a <code>UICronTrigger</code>
100 * in a field called <code>trigger</code>, the following HTML code will
101 * completely create your UI in Struts:
102 * </p>
103 *
104 * <code>
105 * <tr class="listRowUnshaded">
106 * <td width="80">Date</td>
107 * <td align="right">
108 * <html:select property="trigger.daysOfWeek" size="5" multiple="true">
109 * <html:options property="trigger.daysOfWeekValues" labelProperty="trigger.daysOfWeekLabels"/>
110 * </html:select>
111 * <html:select property="trigger.daysOfMonth" size="5" multiple="true">
112 * <html:options property="trigger.daysOfMonthValues" labelProperty="trigger.daysOfMonthLabels"/>
113 * </html:select>
114 * <html:select property="trigger.months" size="5" multiple="true">
115 * <html:options property="trigger.monthsValues" labelProperty="trigger.monthsLabels"/>
116 * </html:select>
117 * <html:select property="trigger.years" size="5" multiple="true">
118 * <html:options property="trigger.yearsValues" labelProperty="trigger.yearsLabels"/>
119 * </html:select>
120 * </td>
121 * </tr>
122 * <tr class="listRowShaded">
123 * <td width="80">Time</td>
124 * <td colspan="2" align="right">
125 * <html:select property="trigger.hours" size="5" multiple="true">
126 * <html:options property="trigger.hoursValues" labelProperty="trigger.hoursLabels"/>
127 * </html:select>
128 * <html:select property="trigger.minutes" size="5" multiple="true">
129 * <html:options property="trigger.minutesValues" labelProperty="trigger.minutesLabels"/>
130 * </html:select>
131 * </td>
132 * </tr>
133 * </code>
134 * <p>
135 * So if you don't want to use Struts, what you have to do is take the
136 * information that was submitted on the form in the HTML select ranges,
137 * iterate each of them, and add the values to the appropriate sets in the
138 * fields of this class. Make sense?
139 * </p>
140 *
141 * Note that this is not as versatile as the standard CronTrigger. There are
142 * tricks with "last day of month" and repeating sets that need to be manually
143 * selected, and sets that can happen for date ranges much longer than we can
144 * reasonably map with direct selection in a UI.
145 *
146 * @see org.quartz.CronTrigger
147 * @see Trigger
148 * @see SimpleTrigger
149 *
150 * @author Brian Topping
151 * @author based on code by Sharada Jambula, James House, Mads Henderson
152 */
153 public class UICronTrigger extends Trigger {
154
155 /*
156 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
157 *
158 * Constants.
159 *
160 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
161 */
162
163 /**
164 * <p>
165 * Instructs the <code>{@link Scheduler}</code> that upon a mis-fire
166 * situation, the <code>{@link org.quartz.CronTrigger}</code> wants to be
167 * fired now by <code>Scheduler</code>.
168 * </p>
169 */
170 public static final int MISFIRE_INSTRUCTION_FIRE_ONCE_NOW = 1;
171
172 /**
173 * <p>
174 * Instructs the <code>{@link Scheduler}</code> that upon a mis-fire
175 * situation, the <code>{@link org.quartz.CronTrigger}</code> wants to
176 * have it's next-fire-time updated to the next time in the schedule after
177 * the current time, but it does not to be fired now.
178 * </p>
179 */
180 public static final int MISFIRE_INSTRUCTION_DO_NOTHING = 2;
181
182 /*
183 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
184 *
185 * Data members.
186 *
187 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
188 */
189 private static final int ALL_SPEC_INT = 99; // '*'
190
191 private static final int NO_SPEC_INT = 98; // '?'
192
193 private static final Integer ALL_SPEC = new Integer(ALL_SPEC_INT);
194
195 private static final Integer NO_SPEC = new Integer(NO_SPEC_INT);
196
197 private static Map monthMap = new HashMap(20);
198
199 private static Map dayMap = new HashMap(60);
200
201 static {
202 monthMap.put("JAN", new Integer(0));
203 monthMap.put("FEB", new Integer(1));
204 monthMap.put("MAR", new Integer(2));
205 monthMap.put("APR", new Integer(3));
206 monthMap.put("MAY", new Integer(4));
207 monthMap.put("JUN", new Integer(5));
208 monthMap.put("JUL", new Integer(6));
209 monthMap.put("AUG", new Integer(7));
210 monthMap.put("SEP", new Integer(8));
211 monthMap.put("OCT", new Integer(9));
212 monthMap.put("NOV", new Integer(10));
213 monthMap.put("DEC", new Integer(11));
214
215 dayMap.put("SUN", new Integer(1));
216 dayMap.put("MON", new Integer(2));
217 dayMap.put("TUE", new Integer(3));
218 dayMap.put("WED", new Integer(4));
219 dayMap.put("THU", new Integer(5));
220 dayMap.put("FRI", new Integer(6));
221 dayMap.put("SAT", new Integer(7));
222 }
223
224 private Date startTime = null;
225
226 private Date endTime = null;
227
228 private Date nextFireTime = null;
229
230 private TimeZone timeZone = null;
231
232 private Date previousFireTime = null;
233
234 private TreeSet seconds = null;
235
236 private TreeSet minutes = null;
237
238 private TreeSet hours = null;
239
240 private TreeSet daysOfMonth = null;
241
242 private TreeSet months = null;
243
244 private TreeSet daysOfWeek = null;
245
246 private TreeSet years = null;
247
248 private transient boolean lastdayOfWeek = false;
249
250 private transient int nthdayOfWeek = 0;
251
252 private transient boolean lastdayOfMonth = false;
253
254 private transient boolean calendardayOfWeek = false;
255
256 private transient boolean calendardayOfMonth = false;
257
258 /*
259 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
260 *
261 * Constructors.
262 *
263 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
264 */
265
266 public void reset() {
267 seconds = new TreeSet();
268 minutes = new TreeSet();
269 hours = new TreeSet();
270 daysOfMonth = new TreeSet();
271 months = new TreeSet();
272 daysOfWeek = new TreeSet();
273 years = new TreeSet();
274
275 // we always fire on the minute
276 seconds.add(new Integer(0));
277
278 minutes.add(ALL_SPEC);
279 for (int i = 0; i < 60; i++)
280 minutes.add(new Integer(i));
281
282 hours.add(ALL_SPEC);
283 for (int i = 0; i < 24; i++)
284 hours.add(new Integer(i));
285
286 daysOfMonth.add(ALL_SPEC);
287 for (int i = 1; i <= 31; i++)
288 daysOfMonth.add(new Integer(i));
289
290 months.add(ALL_SPEC);
291 for (int i = 1; i <= 12; i++)
292 months.add(new Integer(i));
293
294 daysOfWeek.add(NO_SPEC);
295
296 years.add(ALL_SPEC);
297 for (int i = 1970; i <= 2099; i++)
298 years.add(new Integer(i));
299
300 startTime = new Date();
301 setStartTime(startTime);
302 setTimeZone(TimeZone.getDefault());
303 }
304
305 /**
306 * <p>
307 * Create a <code>CronTrigger</code> with no settings.
308 * </p>
309 */
310 public UICronTrigger() {
311 super();
312 reset();
313 }
314
315 /**
316 * <p>
317 * Create a <code>CronTrigger</code> with the given name and group.
318 * </p>
319 */
320 public UICronTrigger(String name, String group) {
321 super(name, group);
322 reset();
323 }
324
325 /**
326 * <p>
327 * Create a <code>CronTrigger</code> with the given name and group, and
328 * associated with the identified <code>{@link Job}</code>.
329 * </p>
330 */
331 public UICronTrigger(String name, String group, String jobName,
332 String jobGroup) {
333 super(name, group, jobName, jobGroup);
334 reset();
335 }
336
337 /*
338 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
339 *
340 * Interface.
341 *
342 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
343 */
344
345 /**
346 * <p>
347 * Get the time at which the <code>CronTrigger</code> should occur.
348 * </p>
349 */
350 public Date getStartTime() {
351 return this.startTime;
352 }
353
354 public void setStartTime(Date startTime) {
355 if (startTime == null)
356 throw new IllegalArgumentException("Start time cannot be null");
357
358 Date eTime = getEndTime();
359 if (eTime != null && startTime != null && eTime.before(startTime))
360 throw new IllegalArgumentException(
361 "End time cannot be before start time");
362
363
364 // round off millisecond...
365 // Note timeZone is not needed here as parameter for
366 // Calendar.getInstance(), since time zone is implicit
367 // when using a Date in the setTime method.
368 Calendar cl = Calendar.getInstance();
369 cl.setTime(startTime);
370 cl.set(Calendar.MILLISECOND, 0);
371
372 this.startTime = cl.getTime();
373 }
374
375 /**
376 * <p>
377 * Get the time at which the <code>CronTrigger</code> should quit
378 * repeating - even if repeastCount isn't yet satisfied.
379 * </p>
380 *
381 * @see #getFinalFireTime()
382 */
383 public Date getEndTime() {
384 return this.endTime;
385 }
386
387 public void setEndTime(Date endTime) {
388 Date sTime = getStartTime();
389 if (sTime != null && endTime != null && sTime.after(endTime))
390 throw new IllegalArgumentException(
391 "End time cannot be before start time");
392
393 this.endTime = endTime;
394 }
395
396 /**
397 * <p>
398 * Returns the next time at which the <code>CronTrigger</code> will fire.
399 * If the trigger will not fire again, <code>null</code> will be
400 * returned. The value returned is not guaranteed to be valid until after
401 * the <code>Trigger</code> has been added to the scheduler.
402 * </p>
403 */
404 public Date getNextFireTime() {
405 return this.nextFireTime;
406 }
407
408 public void updateAfterMisfire(org.quartz.Calendar cal) {
409 int instr = getMisfireInstruction();
410
411 if (instr == MISFIRE_INSTRUCTION_SMART_POLICY)
412 instr = MISFIRE_INSTRUCTION_DO_NOTHING;
413
414 if (instr == MISFIRE_INSTRUCTION_DO_NOTHING) {
415 Date newFireTime = getFireTimeAfter(new Date());
416 while (newFireTime != null && cal != null
417 && !cal.isTimeIncluded(newFireTime.getTime())) {
418 newFireTime = getFireTimeAfter(newFireTime);
419 }
420
421 setNextFireTime(newFireTime);
422
423 } else if (instr == MISFIRE_INSTRUCTION_FIRE_ONCE_NOW) {
424 setNextFireTime(new Date());
425 }
426 }
427
428 public Date getPreviousFireTime() {
429 return this.previousFireTime;
430 }
431
432 /**
433 * <p>
434 * Set the previous time at which the <code>SimpleTrigger</code> fired.
435 * </p>
436 *
437 * <p>
438 * <b>This method should not be invoked by client code.</b>
439 * </p>
440 */
441 public void setPreviousFireTime(Date previousFireTime) {
442 this.previousFireTime = previousFireTime;
443 }
444
445 /**
446 * <p>
447 * Sets the next time at which the <code>CronTrigger</code> will fire. If
448 * the trigger will not fire again, <code>null</code> will be returned.
449 * </p>
450 */
451 public void setNextFireTime(Date nextFireTime) {
452 this.nextFireTime = nextFireTime;
453 }
454
455 /**
456 * <p>
457 * Returns the time zone for which the <code>cronExpression</code> of
458 * this <code>CronTrigger</code> will be resolved.
459 * </p>
460 */
461 public TimeZone getTimeZone() {
462 return this.timeZone;
463 }
464
465 /**
466 * <p>
467 * Sets the time zone for which the <code>cronExpression</code> of this
468 * <code>CronTrigger</code> will be resolved.
469 * </p>
470 */
471 public void setTimeZone(TimeZone timeZone) {
472 this.timeZone = timeZone;
473 }
474
475 /**
476 * <p>
477 * Returns the next time at which the <code>CronTrigger</code> will fire,
478 * after the given time. If the trigger will not fire after the given time,
479 * <code>null</code> will be returned.
480 * </p>
481 *
482 * <p>
483 * Note that the date returned is NOT validated against the related
484 * org.quartz.Calendar (if any)
485 * </p>
486 */
487 public Date getFireTimeAfter(Date afterTime) {
488 if (afterTime == null) afterTime = new Date();
489
490 if (startTime.after(afterTime))
491 afterTime = new Date(startTime.getTime() - 1000l);
492
493 Date pot = getTimeAfter(afterTime);
494 if (endTime != null && pot != null && pot.after(endTime)) return null;
495
496 return pot;
497 }
498
499 /**
500 * <p>
501 * Returns the final time at which the <code>CronTrigger</code> will
502 * fire.
503 * </p>
504 *
505 * <p>
506 * Note that the return time *may* be in the past. and the date returned is
507 * not validated against org.quartz.calendar
508 * </p>
509 */
510 public Date getFinalFireTime() {
511 if (this.endTime != null) return getTimeBefore(this.endTime);
512 else
513 return null;
514 }
515
516 /**
517 * <p>
518 * Determines whether or not the <code>CronTrigger</code> will occur
519 * again.
520 * </p>
521 */
522 public boolean mayFireAgain() {
523 return (getNextFireTime() != null);
524 }
525
526 protected boolean validateMisfireInstruction(int misfireInstruction) {
527 if (misfireInstruction < MISFIRE_INSTRUCTION_SMART_POLICY)
528 return false;
529
530 if (misfireInstruction > MISFIRE_INSTRUCTION_DO_NOTHING) return false;
531
532 return true;
533 }
534
535 /**
536 * <p>
537 * Updates the <code>CronTrigger</code>'s state based on the
538 * MISFIRE_INSTRUCTION_XXX that was selected when the <code>SimpleTrigger</code>
539 * was created.
540 * </p>
541 *
542 * <p>
543 * If the misfire instruction is set to MISFIRE_INSTRUCTION_SMART_POLICY,
544 * then the following scheme will be used: <br>
545 * <ul>
546 * <li>The instruction will be interpreted as <code>MISFIRE_INSTRUCTION_DO_NOTHING</code>
547 * </ul>
548 * </p>
549 */
550 public void updateAfterMisfire() {
551 int instr = getMisfireInstruction();
552
553 if (instr == MISFIRE_INSTRUCTION_SMART_POLICY)
554 instr = MISFIRE_INSTRUCTION_DO_NOTHING;
555
556 if (instr == MISFIRE_INSTRUCTION_DO_NOTHING) {
557 setNextFireTime(getFireTimeAfter(new Date()));
558 } else if (instr == MISFIRE_INSTRUCTION_FIRE_ONCE_NOW) {
559 setNextFireTime(new Date());
560 }
561 }
562
563 /**
564 * <p>
565 * Determines whether the date & time of the given java.util.Calendar
566 * instance falls on a scheduled fire-time of this trigger.
567 * </p>
568 *
569 * <p>
570 * Note that the date returned is NOT validated against the related
571 * org.quartz.Calendar (if any)
572 * </p>
573 */
574 public boolean willFireOn(Calendar test) {
575 Integer second = new Integer(test.get(Calendar.SECOND));
576 Integer minute = new Integer(test.get(Calendar.MINUTE));
577 Integer hour = new Integer(test.get(Calendar.HOUR_OF_DAY));
578 Integer day = new Integer(test.get(Calendar.DAY_OF_MONTH));
579 Integer month = new Integer(test.get(Calendar.MONTH));
580
581 if ((seconds.contains(second) || seconds.contains(ALL_SPEC))
582 && (minutes.contains(minute) || minutes.contains(ALL_SPEC))
583 && (hours.contains(hour) || hours.contains(ALL_SPEC))
584 && (daysOfMonth.contains(day) || daysOfMonth.contains(ALL_SPEC))
585 && (months.contains(month) || months.contains(ALL_SPEC))) {
586
587 return true; }
588
589 return false;
590 }
591
592 /**
593 * <p>
594 * Called after the <code>{@link Scheduler}</code> has executed the
595 * <code>{@link Job}</code> associated with the <code>Trigger</code> in
596 * order to get the final instruction code from the trigger.
597 * </p>
598 *
599 * @param context
600 * is the <code>JobExecutionContext</code> that was used by the
601 * <code>Job</code>'s<code>execute(xx)</code> method.
602 * @param result
603 * is the <code>JobExecutionException</code> thrown by the
604 * <code>Job</code>, if any (may be null).
605 * @return one of the Trigger.INSTRUCTION_XXX constants.
606 *
607 * @see #INSTRUCTION_NOOP
608 * @see #INSTRUCTION_RE_EXECUTE_JOB
609 * @see #INSTRUCTION_DELETE_TRIGGER
610 * @see #INSTRUCTION_SET_TRIGGER_COMPLETE
611 * @see #triggered(Calendar)
612 */
613 public int executionComplete(JobExecutionContext context,
614 JobExecutionException result) {
615 if (result != null && result.refireImmediately())
616 return Trigger.INSTRUCTION_RE_EXECUTE_JOB;
617
618 if (result != null && result.refireImmediately())
619 return INSTRUCTION_RE_EXECUTE_JOB;
620
621 if (result != null && result.unscheduleFiringTrigger())
622 return INSTRUCTION_SET_TRIGGER_COMPLETE;
623
624 if (result != null && result.unscheduleAllTriggers())
625 return INSTRUCTION_SET_ALL_JOB_TRIGGERS_COMPLETE;
626
627 if (!mayFireAgain()) return INSTRUCTION_DELETE_TRIGGER;
628
629 return INSTRUCTION_NOOP;
630 }
631
632 /**
633 * <p>
634 * Called when the <code>{@link Scheduler}</code> has decided to 'fire'
635 * the trigger (execute the associated <code>Job</code>), in order to
636 * give the <code>Trigger</code> a chance to update itself for its next
637 * triggering (if any).
638 * </p>
639 *
640 * @see #executionComplete(JobExecutionContext, JobExecutionException)
641 */
642 public void triggered(org.quartz.Calendar calendar) {
643 previousFireTime = nextFireTime;
644 nextFireTime = getFireTimeAfter(nextFireTime);
645
646 while (nextFireTime != null && calendar != null
647 && !calendar.isTimeIncluded(nextFireTime.getTime())) {
648 nextFireTime = getFireTimeAfter(nextFireTime);
649 }
650 }
651
652 /**
653 *
654 * @see org.quartz.Trigger#updateWithNewCalendar(org.quartz.Calendar, long)
655 */
656 public void updateWithNewCalendar(org.quartz.Calendar calendar, long misfireThreshold)
657 {
658 nextFireTime = getFireTimeAfter(previousFireTime);
659
660 Date now = new Date();
661 do {
662 while (nextFireTime != null && calendar != null
663 && !calendar.isTimeIncluded(nextFireTime.getTime())) {
664 nextFireTime = getFireTimeAfter(nextFireTime);
665 }
666
667 if(nextFireTime != null && nextFireTime.before(now)) {
668 long diff = now.getTime() - nextFireTime.getTime();
669 if(diff >= misfireThreshold) {
670 nextFireTime = getFireTimeAfter(nextFireTime);
671 continue;
672 }
673 }
674 }while(false);
675 }
676
677 /**
678 * <p>
679 * Called by the scheduler at the time a <code>Trigger</code> is first
680 * added to the scheduler, in order to have the <code>Trigger</code>
681 * compute its first fire time, based on any associated calendar.
682 * </p>
683 *
684 * <p>
685 * After this method has been called, <code>getNextFireTime()</code>
686 * should return a valid answer.
687 * </p>
688 *
689 * @return the first time at which the <code>Trigger</code> will be fired
690 * by the scheduler, which is also the same value <code>getNextFireTime()</code>
691 * will return (until after the first firing of the <code>Trigger</code>).
692 * </p>
693 */
694 public Date computeFirstFireTime(org.quartz.Calendar calendar) {
695 nextFireTime = getFireTimeAfter(new Date(startTime.getTime() - 1000l));
696
697 while (nextFireTime != null && calendar != null
698 && !calendar.isTimeIncluded(nextFireTime.getTime())) {
699 nextFireTime = getFireTimeAfter(nextFireTime);
700 }
701
702 return nextFireTime;
703 }
704
705 public String getExpressionSummary() {
706 StringBuffer buf = new StringBuffer();
707
708 buf.append("seconds: ");
709 buf.append(getExpressionSetSummary(seconds));
710 buf.append("\n");
711 buf.append("minutes: ");
712 buf.append(getExpressionSetSummary(minutes));
713 buf.append("\n");
714 buf.append("hours: ");
715 buf.append(getExpressionSetSummary(hours));
716 buf.append("\n");
717 buf.append("daysOfMonth: ");
718 buf.append(getExpressionSetSummary(daysOfMonth));
719 buf.append("\n");
720 buf.append("months: ");
721 buf.append(getExpressionSetSummary(months));
722 buf.append("\n");
723 buf.append("daysOfWeek: ");
724 buf.append(getExpressionSetSummary(daysOfWeek));
725 buf.append("\n");
726 buf.append("lastdayOfWeek: ");
727 buf.append(lastdayOfWeek);
728 buf.append("\n");
729 buf.append("lastdayOfMonth: ");
730 buf.append(lastdayOfMonth);
731 buf.append("\n");
732 buf.append("calendardayOfWeek: ");
733 buf.append(calendardayOfWeek);
734 buf.append("\n");
735 buf.append("calendardayOfMonth: ");
736 buf.append(calendardayOfMonth);
737 buf.append("\n");
738 buf.append("years: ");
739 buf.append(getExpressionSetSummary(years));
740 buf.append("\n");
741
742 return buf.toString();
743 }
744
745 private String getExpressionSetSummary(Set set) {
746
747 if (set.contains(NO_SPEC)) return "?";
748 if (set.contains(ALL_SPEC)) return "*";
749
750 StringBuffer buf = new StringBuffer();
751
752 Iterator itr = set.iterator();
753 boolean first = true;
754 while (itr.hasNext()) {
755 Integer iVal = (Integer) itr.next();
756 String val = iVal.toString();
757 if (!first) buf.append(",");
758 buf.append(val);
759 first = false;
760 }
761
762 return buf.toString();
763 }
764
765 ////////////////////////////////////////////////////////////////////////////
766 //
767 // Computation Functions
768 //
769 ////////////////////////////////////////////////////////////////////////////
770
771 private Date getTimeAfter(Date afterTime) {
772 Calendar cl = Calendar.getInstance(timeZone);
773
774 // move ahead one second, since we're computing the time *after* the
775 // given time
776 afterTime = new Date(afterTime.getTime() + 1000);
777 // CronTrigger does not deal with milliseconds
778 cl.setTime(afterTime);
779 cl.set(Calendar.MILLISECOND, 0);
780
781 boolean gotOne = false;
782
783 // loop until we've computed the next time, or we've past the endTime
784 while (!gotOne) {
785
786 if (endTime != null && cl.getTime().after(endTime)) return null;
787
788 SortedSet st = null;
789 int t = 0;
790
791 int sec = cl.get(Calendar.SECOND);
792 int min = cl.get(Calendar.MINUTE);
793
794 // get second.................................................
795 st = seconds.tailSet(new Integer(sec));
796 if (st != null && st.size() != 0) {
797 sec = ((Integer) st.first()).intValue();
798 } else {
799 sec = ((Integer) seconds.first()).intValue();
800 min++;
801 cl.set(Calendar.MINUTE, min);
802 }
803 cl.set(Calendar.SECOND, sec);
804
805 min = cl.get(Calendar.MINUTE);
806 int hr = cl.get(Calendar.HOUR_OF_DAY);
807 t = -1;
808
809 // get minute.................................................
810 st = minutes.tailSet(new Integer(min));
811 if (st != null && st.size() != 0) {
812 t = min;
813 min = ((Integer) st.first()).intValue();
814 } else {
815 min = ((Integer) minutes.first()).intValue();
816 hr++;
817 }
818 if (min != t) {
819 cl.set(Calendar.SECOND, 0);
820 cl.set(Calendar.MINUTE, min);
821 cl.set(Calendar.HOUR_OF_DAY, hr);
822 continue;
823 }
824 cl.set(Calendar.MINUTE, min);
825
826 hr = cl.get(Calendar.HOUR_OF_DAY);
827 int day = cl.get(Calendar.DAY_OF_MONTH);
828 t = -1;
829
830 // get hour...................................................
831 st = hours.tailSet(new Integer(hr));
832 if (st != null && st.size() != 0) {
833 t = hr;
834 hr = ((Integer) st.first()).intValue();
835 } else {
836 hr = ((Integer) hours.first()).intValue();
837 day++;
838 }
839 if (hr != t) {
840 cl.set(Calendar.SECOND, 0);
841 cl.set(Calendar.MINUTE, 0);
842 cl.set(Calendar.HOUR_OF_DAY, hr);
843 cl.set(Calendar.DAY_OF_MONTH, day);
844 continue;
845 }
846 cl.set(Calendar.HOUR_OF_DAY, hr);
847
848 day = cl.get(Calendar.DAY_OF_MONTH);
849 int mon = cl.get(Calendar.MONTH) + 1; // '+ 1' because calendar is
850 // 0-based for this field,
851 // and we are 1-based
852 t = -1;
853
854 // get day...................................................
855 boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
856 boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
857 if (dayOfMSpec && !dayOfWSpec) { // get day only by day of month
858 // rule
859 st = daysOfMonth.tailSet(new Integer(day));
860 if (lastdayOfMonth) {
861 t = day;
862 day = getLastDayOfMonth(mon);
863 } else if (st != null && st.size() != 0) {
864 t = day;
865 day = ((Integer) st.first()).intValue();
866 } else {
867 day = ((Integer) daysOfMonth.first()).intValue();
868 mon++;
869 }
870 if (day != t) {
871 cl.set(Calendar.SECOND, 0);
872 cl.set(Calendar.MINUTE, 0);
873 cl.set(Calendar.HOUR_OF_DAY, 0);
874 cl.set(Calendar.DAY_OF_MONTH, day);
875 cl.set(Calendar.MONTH, mon - 1); // '- 1' because calendar
876 // is 0-based for this
877 // field, and we are
878 // 1-based
879 continue;
880 }
881 } else if (dayOfWSpec && !dayOfMSpec) { // get day only by day of
882 // week rule
883 if (lastdayOfWeek) { // are we looking for the last XXX day of
884 // the month?
885 int dow = ((Integer) daysOfWeek.first()).intValue(); // desired
886 // d-o-w
887 int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
888 int daysToAdd = 0;
889 if (cDow < dow) daysToAdd = dow - cDow;
890 if (cDow > dow) daysToAdd = dow + (7 - cDow);
891
892 int lDay = getLastDayOfMonth(mon);
893
894 if (day + daysToAdd > lDay) { // did we already miss the
895 // last one?
896 cl.set(Calendar.SECOND, 0);
897 cl.set(Calendar.MINUTE, 0);
898 cl.set(Calendar.HOUR_OF_DAY, 0);
899 cl.set(Calendar.DAY_OF_MONTH, 1);
900 cl.set(Calendar.MONTH, mon); // no '- 1' here because
901 // we are promoting the
902 // month
903 continue;
904 }
905
906 // find date of last occurance of this day in this month...
907 while ((day + daysToAdd + 7) <= lDay)
908 daysToAdd += 7;
909
910 day += daysToAdd;
911 } else if (nthdayOfWeek != 0) { // are we looking for the Nth
912 // XXX day in the month?
913 int dow = ((Integer) daysOfWeek.first()).intValue(); // desired
914 // d-o-w
915 int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
916 int daysToAdd = 0;
917 if (cDow < dow) daysToAdd = dow - cDow;
918 else if (cDow > dow) daysToAdd = dow + (7 - cDow);
919
920 day += daysToAdd;
921 int weekOfMonth = day / 7;
922 if (day % 7 > 0) weekOfMonth++;
923
924 daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
925 day += daysToAdd;
926 if (daysToAdd < 0 || day > getLastDayOfMonth(mon)) {
927 cl.set(Calendar.SECOND, 0);
928 cl.set(Calendar.MINUTE, 0);
929 cl.set(Calendar.HOUR_OF_DAY, 0);
930 cl.set(Calendar.DAY_OF_MONTH, 1);
931 cl.set(Calendar.MONTH, mon); // no '- 1' here because
932 // we are promoting the
933 // month
934 continue;
935 }
936 } else {
937 int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
938 int dow = ((Integer) daysOfWeek.first()).intValue(); // desired
939 // d-o-w
940 st = daysOfWeek.tailSet(new Integer(cDow));
941 if (st != null && st.size() > 0) {
942 dow = ((Integer) st.first()).intValue();
943 }
944
945 int daysToAdd = 0;
946 if (cDow < dow) daysToAdd = dow - cDow;
947 if (cDow > dow) daysToAdd = dow + (7 - cDow);
948
949 int lDay = getLastDayOfMonth(mon);
950
951 if (day + daysToAdd > lDay) { // will we pass the end of
952 // the month?
953 cl.set(Calendar.SECOND, 0);
954 cl.set(Calendar.MINUTE, 0);
955 cl.set(Calendar.HOUR_OF_DAY, 0);
956 cl.set(Calendar.DAY_OF_MONTH, 1);
957 cl.set(Calendar.MONTH, mon); // no '- 1' here because
958 // we are promoting the
959 // month
960 continue;
961 } else if (daysToAdd > 0) { // are we swithing days?
962 cl.set(Calendar.SECOND, 0);
963 cl.set(Calendar.MINUTE, 0);
964 cl.set(Calendar.HOUR_OF_DAY, 0);
965 cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
966 cl.set(Calendar.MONTH, mon - 1); // '- 1' because
967 // calendar is 0-based
968 // for this field, and
969 // we are 1-based
970 continue;
971 }
972 }
973 } else { // dayOfWSpec && !dayOfMSpec
974 throw new UnsupportedOperationException(
975 "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented."); // TODO:
976 }
977 cl.set(Calendar.DAY_OF_MONTH, day);
978
979 mon = cl.get(Calendar.MONTH) + 1; // '+ 1' because calendar is
980 // 0-based for this field, and we
981 // are 1-based
982 int year = cl.get(Calendar.YEAR);
983 t = -1;
984
985 // get month...................................................
986 st = months.tailSet(new Integer(mon));
987 if (st != null && st.size() != 0) {
988 t = mon;
989 mon = ((Integer) st.first()).intValue();
990 } else {
991 mon = ((Integer) months.first()).intValue();
992 year++;
993 }
994 if (mon != t) {
995 cl.set(Calendar.SECOND, 0);
996 cl.set(Calendar.MINUTE, 0);
997 cl.set(Calendar.HOUR_OF_DAY, 0);
998 cl.set(Calendar.DAY_OF_MONTH, 1);
999 cl.set(Calendar.MONTH, mon - 1); // '- 1' because calendar is
1000 // 0-based for this field, and
1001 // we are 1-based
1002 cl.set(Calendar.YEAR, year);
1003 continue;
1004 }
1005 cl.set(Calendar.MONTH, mon - 1); // '- 1' because calendar is
1006 // 0-based for this field, and we
1007 // are 1-based
1008
1009 year = cl.get(Calendar.YEAR);
1010 t = -1;
1011
1012 // get year...................................................
1013 st = years.tailSet(new Integer(year));
1014 if (st != null && st.size() != 0) {
1015 t = year;
1016 year = ((Integer) st.first()).intValue();
1017 } else
1018 return null; // ran out of years...
1019
1020 if (year != t) {
1021 cl.set(Calendar.SECOND, 0);
1022 cl.set(Calendar.MINUTE, 0);
1023 cl.set(Calendar.HOUR_OF_DAY, 0);
1024 cl.set(Calendar.DAY_OF_MONTH, 1);
1025 cl.set(Calendar.MONTH, mon - 1); // '- 1' because calendar is
1026 // 0-based for this field, and
1027 // we are 1-based
1028 cl.set(Calendar.YEAR, year);
1029 continue;
1030 }
1031 cl.set(Calendar.YEAR, year);
1032
1033 gotOne = true;
1034 } // while( !done )
1035
1036 return cl.getTime();
1037 }
1038
1039 private Date getTimeBefore(Date endTime) // TODO: implement
1040 {
1041 return null;
1042 }
1043
1044 public boolean isLeapYear() {
1045 Calendar cl = Calendar.getInstance(timeZone);
1046 int year = cl.get(Calendar.YEAR);
1047
1048 if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) return true;
1049 else
1050 return false;
1051 }
1052
1053 public int getLastDayOfMonth(int monthNum) {
1054
1055 switch (monthNum) {
1056 case 1:
1057 return 31;
1058 case 2:
1059 return (isLeapYear()) ? 29 : 28;
1060 case 3:
1061 return 31;
1062 case 4:
1063 return 30;
1064 case 5:
1065 return 31;
1066 case 6:
1067 return 30;
1068 case 7:
1069 return 31;
1070 case 8:
1071 return 31;
1072 case 9:
1073 return 30;
1074 case 10:
1075 return 31;
1076 case 11:
1077 return 30;
1078 case 12:
1079 return 31;
1080 default:
1081 throw new IllegalArgumentException("Illegal month number: "
1082 + monthNum);
1083 }
1084 }
1085
1086 public Integer[] getSecondsValues() {
1087 Integer list[] = new Integer[60];
1088 for (int i = 0; i < 60; i++) {
1089 list[i] = new Integer(i);
1090 }
1091
1092 return list;
1093 }
1094
1095 public Integer[] getSecondsLabels() {
1096 return getSecondsValues();
1097 }
1098
1099 public Integer[] getSeconds() {
1100 Integer list[] = new Integer[seconds.size()];
1101 if (seconds != null) {
1102 int i = 0;
1103 for (Iterator it = seconds.iterator(); it.hasNext(); i++) {
1104 list[i] = (Integer) it.next();
1105 }
1106 }
1107 return list;
1108 }
1109
1110 public void setSeconds(Integer[] val) {
1111 if (seconds != null) seconds.clear();
1112 else
1113 seconds = new TreeSet();
1114
1115 for (int i = 0; i < val.length; i++) {
1116 seconds.add(val[i]);
1117 }
1118 }
1119
1120 public Integer[] getMinutesValues() {
1121 Integer list[] = new Integer[60];
1122 for (int i = 0; i < 60; i++) {
1123 list[i] = new Integer(i);
1124 }
1125
1126 return list;
1127 }
1128
1129 public Integer[] getMinutesLabels() {
1130 return getMinutesValues();
1131 }
1132
1133 public Integer[] getMinutes() {
1134 Integer list[] = new Integer[minutes.size()];
1135 if (minutes != null) {
1136 int i = 0;
1137 for (Iterator it = minutes.iterator(); it.hasNext(); i++) {
1138 list[i] = (Integer) it.next();
1139 }
1140 }
1141 return list;
1142 }
1143
1144 public void setMinutes(Integer[] val) {
1145 if (minutes != null) minutes.clear();
1146 else
1147 minutes = new TreeSet();
1148
1149 for (int i = 0; i < val.length; i++) {
1150 minutes.add(val[i]);
1151 }
1152 }
1153
1154 public Integer[] getHoursValues() {
1155 Integer list[] = new Integer[24];
1156 for (int i = 0; i < 24; i++) {
1157 list[i] = new Integer(i);
1158 }
1159
1160 return list;
1161 }
1162
1163 public String[] getHoursLabels() {
1164 String vals[] = {"12AM (Midnight)", "1AM", "2AM", "3AM", "4AM", "5AM",
1165 "6AM", "7AM", "8AM", "9AM", "10AM", "11AM", "12PM (Noon)",
1166 "1PM", "2PM", "3PM", "4PM", "5PM", "6PM", "7PM", "8PM", "9PM",
1167 "10PM", "11PM"};
1168 return vals;
1169 }
1170
1171 public Integer[] getHours() {
1172 Integer list[] = new Integer[hours.size()];
1173 if (hours != null) {
1174 int i = 0;
1175 for (Iterator it = hours.iterator(); it.hasNext(); i++) {
1176 list[i] = (Integer) it.next();
1177 }
1178 }
1179 return list;
1180 }
1181
1182 public void setHours(Integer[] val) {
1183 if (hours != null) hours.clear();
1184 else
1185 hours = new TreeSet();
1186
1187 for (int i = 0; i < val.length; i++) {
1188 hours.add(val[i]);
1189 }
1190 }
1191
1192 public Integer[] getDaysOfMonthValues() {
1193 Integer list[] = new Integer[31];
1194 for (int i = 0; i < 31; i++) {
1195 list[i] = new Integer(i + 1);
1196 }
1197
1198 return list;
1199 }
1200
1201 public Integer[] getDaysOfMonthLabels() {
1202 return getDaysOfMonthValues();
1203 }
1204
1205 public Integer[] getDaysOfMonth() {
1206 Integer list[] = new Integer[daysOfMonth.size()];
1207 if (daysOfMonth != null) {
1208 int i = 0;
1209 for (Iterator it = daysOfMonth.iterator(); it.hasNext(); i++) {
1210 list[i] = (Integer) it.next();
1211 }
1212 }
1213 return list;
1214 }
1215
1216 public void setDaysOfMonth(Integer[] val) {
1217 if (daysOfMonth != null) daysOfMonth.clear();
1218 else
1219 daysOfMonth = new TreeSet();
1220
1221 for (int i = 0; i < val.length; i++) {
1222 daysOfMonth.add(val[i]);
1223 }
1224 daysOfWeek.clear();
1225 daysOfWeek.add(NO_SPEC);
1226 }
1227
1228 public Integer[] getMonthsValues() {
1229 Integer list[] = new Integer[12];
1230 for (int i = 0; i < 12; i++) {
1231 list[i] = new Integer(i + 1);
1232 }
1233
1234 return list;
1235 }
1236
1237 public String[] getMonthsLabels() {
1238 String vals[] = {"January", "February", "March", "April", "May",
1239 "June", "July", "August", "September", "October", "November",
1240 "December"};
1241 return vals;
1242 }
1243
1244 public Integer[] getMonths() {
1245 Integer list[] = new Integer[months.size()];
1246 if (months != null) {
1247 int i = 0;
1248 for (Iterator it = months.iterator(); it.hasNext(); i++) {
1249 list[i] = (Integer) it.next();
1250 }
1251 }
1252 return list;
1253 }
1254
1255 public void setMonths(Integer[] val) {
1256 if (months != null) months.clear();
1257 else
1258 months = new TreeSet();
1259
1260 for (int i = 0; i < val.length; i++) {
1261 months.add(val[i]);
1262 }
1263 }
1264
1265 public String[] getDaysOfWeekLabels() {
1266 String list[] = {"Sunday", "Monday", "Tuesday", "Wednesday",
1267 "Thursday", "Friday", "Saturday"};
1268 return list;
1269 }
1270
1271 public Integer[] getDaysOfWeekValues() {
1272 Integer list[] = new Integer[7];
1273 for (int i = 0; i < 7; i++)
1274 list[i] = new Integer(i + 1);
1275 return list;
1276 }
1277
1278 public Integer[] getDaysOfWeek() {
1279 Integer list[] = new Integer[daysOfWeek.size()];
1280 if (daysOfWeek != null) {
1281 int i = 0;
1282 for (Iterator it = daysOfWeek.iterator(); it.hasNext(); i++) {
1283 list[i] = (Integer) it.next();
1284 }
1285 }
1286 return list;
1287 }
1288
1289 public void setDaysOfWeek(Integer[] val) {
1290 if (daysOfWeek != null) daysOfWeek.clear();
1291 else
1292 daysOfWeek = new TreeSet();
1293
1294 for (int i = 0; i < val.length; i++) {
1295 daysOfWeek.add(val[i]);
1296 }
1297
1298 daysOfMonth.clear();
1299 daysOfMonth.add(NO_SPEC);
1300 }
1301
1302 public Integer[] getYearsValues() {
1303 Integer list[] = new Integer[20];
1304 Calendar now = Calendar.getInstance();
1305 int year = now.get(Calendar.YEAR);
1306 for (int i = 0; i < 20; i++) {
1307 list[i] = new Integer(i + year);
1308 }
1309
1310 return list;
1311 }
1312
1313 public Integer[] getYearsLabels() {
1314 return getYearsValues();
1315 }
1316
1317 public Integer[] getYears() {
1318 Integer list[] = new Integer[years.size()];
1319 if (years != null) {
1320 int i = 0;
1321 for (Iterator it = years.iterator(); it.hasNext(); i++) {
1322 list[i] = (Integer) it.next();
1323 }
1324 }
1325 return list;
1326 }
1327
1328 public void setYears(Integer[] val) {
1329 if (years != null) years.clear();
1330 else
1331 years = new TreeSet();
1332
1333 for (int i = 0; i < val.length; i++) {
1334 years.add(val[i]);
1335 }
1336 }
1337
1338 public static void main(String[] argv) {
1339 CronTrigger ct = new CronTrigger("a", "a");
1340 try {
1341 ct.setCronExpression("0 * * * * ? *");
1342 } catch (ParseException e) {
1343 // log.error("caught an exception", e);
1344 }
1345 ct.setStartTime(new Date());
1346 ct.setTimeZone(TimeZone.getDefault());
1347 System.out.println(ct.getExpressionSummary());
1348 ct.computeFirstFireTime(null);
1349
1350 UICronTrigger uict = new UICronTrigger("a", "a");
1351 Integer set[] = new Integer[1];
1352 set[0] = new Integer(1);
1353 uict.setSeconds(set);
1354 System.out.println(ct.getExpressionSummary());
1355 uict.computeFirstFireTime(null);
1356
1357 }
1358 }