KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > oddjob > quartz > OddjobSchedule


1 /*
2  * (c) Rob Gordon 2005
3  */

4 package org.oddjob.quartz;
5
6 import java.io.Serializable JavaDoc;
7 import java.util.Date JavaDoc;
8 import java.util.HashMap JavaDoc;
9 import java.util.Map JavaDoc;
10 import java.util.TimeZone JavaDoc;
11
12 import org.oddjob.OddjobException;
13 import org.oddjob.Resetable;
14 import org.oddjob.Stateful;
15 import org.oddjob.arooa.registry.ComponentRegistry;
16 import org.oddjob.framework.BasePrimary;
17 import org.oddjob.images.IconHelper;
18 import org.oddjob.schedules.Interval;
19 import org.oddjob.schedules.Schedule;
20 import org.oddjob.schedules.ScheduleCalculator;
21 import org.oddjob.schedules.ScheduleListener;
22 import org.oddjob.state.AbstractJobStateListener;
23 import org.oddjob.state.JobState;
24 import org.oddjob.state.JobStateEvent;
25 import org.oddjob.util.Clock;
26 import org.oddjob.util.DefaultClock;
27 import org.oddjob.util.OddjobConfigException;
28 import org.oddjob.util.OddjobConstantException;
29 import org.quartz.JobDataMap;
30 import org.quartz.JobDetail;
31 import org.quartz.Scheduler;
32 import org.quartz.SchedulerException;
33 import org.quartz.SimpleTrigger;
34
35 /**
36  * @oddjob.description The schedule component the
37  * {@link org.oddjob.quartz.QuartzSchedulerJob} creates to handle a
38  * {@link org.oddjob.scheduling.OJScheduleInstruction} schedule instruction.
39  * <p>
40  * This component supports setting certain properties at runtime which will
41  * override what have been set by the execution of the schedule instruction.
42  * <p>
43  * This schedules state will be
44  * <dl>
45  * <dt>READY<dt>
46  * <dd>When first created or when ready to run the scheduled job.</dd>
47  * <dt>EXECUTING<dt>
48  * <dd>When the scheduler is executing the job.</dd>
49  * <dt>COMPLETE<dt>
50  * <dd>If the scheduled job completes.</dd>
51  * <dt>NOT COMPLETE<dt>
52  * <dd>If the scheduled job fails to complete and a retry schedule is being
53  * used.</dd>
54  * <dt>EXCEPTION<dt>
55  * <dd>If the scheduled job fails to complete and the retry schedule has
56  * expired or the schedule job fails to complete and no retry schedule was
57  * specified.</dd>
58  * </dl>
59  *
60  * @author Rob Gordon.
61  */

62 public class OddjobSchedule extends BasePrimary
63 implements Serializable JavaDoc, Stateful, QuartzSchedule, ScheduleListener {
64     private static final long serialVersionUID = 20051121;
65
66     /** The quartz schedule group. */
67     private static final String JavaDoc GROUP_NAME = Scheduler.DEFAULT_GROUP;
68     
69     /**
70      * The Job Token.
71      */

72     private JobToken jobToken;
73     
74     private transient ComponentRegistry componentRegistry;
75     
76     /**
77      * The regular schedule.
78      */

79     private Schedule normalSchedule;
80
81     /**
82      * The schedule used in the event
83      * that the child job doesn't complete or is in an
84      * exception state.
85      */

86     private Schedule retrySchedule;
87     
88     /**
89      * @oddjob.property
90      * @oddjob.description The time zone. Changes at runtime will be ignored.
91      * @oddjob.required Set automatically.
92      */

93     private TimeZone JavaDoc timeZone;
94
95     /**
96      * @oddjob.property
97      * @oddjob.description The time the job is next due. Changing this
98      * property changes when the job is nexted schdeduled.
99      * @oddjob.required Set automatically.
100      */

101     private transient Date JavaDoc nextDue;
102     
103     /**
104      * @oddjob.property
105      * @oddjob.description This is the start of the last normal schedule.
106      * The schedule date is used when the retry schedule polls past midnight,
107      * yet your business data is from the day before. The schedule date provides
108      * a consistent business date which can be useful as the date for
109      * file names etc. This property can be changed temporarily but will be
110      * reset next time the schedule runs.
111      * @oddjob.required Set automatically.
112      */

113     private Date JavaDoc scheduleDate;
114     
115     /**
116      * @oddjob.property
117      * @oddjob.description The interval in the normal schedule in which the
118      * scheduled job last completed. This is the interval that will be used
119      * to determine when the next normal schedule is due. It can only be
120      * changed using the reSchedule property.
121      * @oddjob.required Read only.
122      */

123     private Interval lastComplete;
124
125     /** Retry, so only soft reset. */
126     private transient boolean retry;
127     
128     /** The wrapper for executing the job */
129     private transient Execute execute;
130         
131     /** The schedule id. */
132     private String JavaDoc id;
133
134     private Map JavaDoc contextData = new HashMap JavaDoc();
135     
136     /**
137      * @oddjob.property
138      * @oddjob.description The clock to use. Tells the current time.
139      * @oddjob.required Set automatically.
140      */

141     private transient Clock clock;
142     
143     /**
144      * Set the id for this schedule. This will be set when the schedule is
145      * created and can't be changed.
146      *
147      * @param id The id.
148      */

149     public void setId(String JavaDoc id) {
150         if (this.id != null) {
151             throw new OddjobConfigException("Id can't be changed.");
152         }
153         this.id = id;
154     }
155     
156     /**
157      * Helper funtion to build a trigger name.
158      *
159      * @return A quartz trigger name.
160      */

161     private String JavaDoc triggerName() {
162         return id + "-Trigger";
163     }
164
165     public void setComponentRegistry(ComponentRegistry componentRegistry) {
166         this.componentRegistry = componentRegistry;
167     }
168     
169     /**
170      * Add a child. Only one child is accepted.
171      *
172      * @param child The child.
173      */

174     public void setJob(Runnable JavaDoc job) {
175         if (job == null) {
176             throw new OddjobConfigException("No job to schedule specified!");
177         }
178         if (jobToken != null) {
179             throw new OddjobConstantException("[" + this
180                     + "] job can't be changed.");
181         }
182         jobToken = JobToken.create(componentRegistry, job);
183     }
184     
185     /**
186      * Set the schedule.
187      *
188      * @param schedule The schedule.
189      */

190     public void setSchedule(Schedule schedule) {
191         this.normalSchedule = schedule;
192     }
193             
194     /**
195      * Set the retry schedule.
196      *
197      * @param retry
198      */

199     public void setRetry(Schedule retry) {
200         this.retrySchedule = retry;
201     }
202         
203     /** The Quartz scheduler this schedule is scheduled with. */
204     private transient Scheduler scheduler;
205
206     /** A schedule calculator is used to cacluate the next due
207      * time. */

208     private transient ScheduleCalculator scheduleCalculator;
209     
210     /* (non-Javadoc)
211      * @see org.oddjob.quartz.QuartzSchedule#scheduleWith(org.quartz.Scheduler)
212      */

213     public void scheduleWith(Scheduler scheduler) throws SchedulerException {
214         if (jobToken == null) {
215             throw new OddjobConfigException("A job is required.");
216         }
217         
218         this.execute = new Execute();
219         this.scheduler = scheduler;
220                 
221         // set up the schedule calculator.
222
scheduleCalculator = new ScheduleCalculator(getClock(),
223                 normalSchedule, retrySchedule, timeZone);
224         scheduleCalculator.addScheduleListener(this);
225
226         scheduleCalculator.initialise(lastComplete, contextData);
227     }
228     
229     /* (non-Javadoc)
230      * @see org.oddjob.treesched.ScheduleListener#initialised(java.util.Date)
231      */

232     public void initialised(Date JavaDoc scheduleDate) {
233         this.scheduleDate = scheduleDate;
234         this.retry = false;
235         setNextDue(scheduleDate);
236         setJobStateReady();
237     }
238     
239     /* (non-Javadoc)
240      * @see org.oddjob.treesched.ScheduleListener#scheduleAt(org.oddjob.treesched.ScheduleCalculator)
241      */

242     public void complete(Date JavaDoc scheduleDate, Interval lastComplete) {
243         this.scheduleDate = scheduleDate;
244         this.lastComplete = lastComplete;
245         this.retry = false;
246         setNextDue(scheduleDate);
247         JobStateEvent jse = new JobStateEvent(this, JobState.COMPLETE,
248                 lastComplete.getFromDate(), null);
249         stateHandler.fireEvent(jse);
250         iconHelper.changeIcon(IconHelper.COMPLETE);
251     }
252
253     /* (non-Javadoc)
254      * @see org.oddjob.treesched.ScheduleListener#retry(java.util.Date, java.util.Date)
255      */

256     public void retry(Date JavaDoc scheduleDate, Date JavaDoc retryDate) {
257         this.scheduleDate = scheduleDate;
258         this.retry = true;
259         setNextDue(retryDate);
260         setJobStateNotComplete();
261     }
262
263     /* (non-Javadoc)
264      * @see org.oddjob.treesched.ScheduleListener#failed(java.util.Date)
265      */

266     public void failed(Date JavaDoc scheduleDate) {
267         this.scheduleDate = scheduleDate;
268         this.retry = false;
269         setNextDue(scheduleDate);
270         setJobStateException(new OddjobException("Schedule failed."));
271     }
272     
273     /**
274      * Get the current clock.
275      *
276      * @return The clock
277      */

278     public Clock getClock() {
279         if (clock == null) {
280             clock = new DefaultClock();
281         }
282         return clock;
283     }
284
285     /**
286      * Set the clock. Only useful for testing.
287      *
288      * @param clock The clock.
289      */

290     public void setClock(Clock clock) {
291         this.clock = clock;
292     }
293         
294     /**
295      * Get the next due date.
296      *
297      * @return The next due date
298      */

299     public Date JavaDoc getNextDue() {
300         return nextDue;
301     }
302
303     /**
304      * Set the next due date.
305      *
306      * @param nextDue The date schedule is next due.
307      */

308     public void setNextDue(Date JavaDoc nextDue) {
309         
310         logger().debug("Setting next due to : " + nextDue);
311         Date JavaDoc oldNextDue = this.nextDue;
312         this.nextDue = nextDue;
313         changes.firePropertyChange("nextDue", oldNextDue, nextDue);
314         
315         if (nextDue == null) {
316             logger().debug("Schedule finished.");
317             return;
318         }
319         
320         SimpleTrigger trigger = new SimpleTrigger(triggerName(),
321                       GROUP_NAME,
322                       nextDue,
323                       null,
324                       0,
325                       0L);
326                 
327         try {
328             JobDetail jobDetail = scheduler.getJobDetail(toString(), Scheduler.DEFAULT_GROUP);
329             // job detail will be null when job hasn't been scheduled.
330
if (jobDetail == null) {
331                 
332                 OddjobData ojd = new OddjobData();
333                 ojd.setJob(execute);
334                 
335                 JobDataMap jobDataMap = new JobDataMap();
336                 
337                 jobDataMap.put(OddjobData.ODDJOB_DATA, ojd);
338                 
339                 jobDetail = new JobDetail(toString(),
340                         GROUP_NAME,
341                         RunnableQuartzJob.class);
342                 jobDetail.setJobDataMap(jobDataMap);
343                 scheduler.scheduleJob(jobDetail, trigger);
344             }
345             else {
346                 trigger.setJobName(jobDetail.getName());
347                 trigger.setJobGroup(jobDetail.getGroup());
348                 scheduler.rescheduleJob(trigger.getName(), jobDetail.getGroup(), trigger);
349             }
350         } catch (SchedulerException e) {
351             logger().debug("Failed to schedule (" + e.getMessage() + ")");
352         }
353     }
354
355     /*
356      * (non-Javadoc)
357      * @see org.oddjob.quartz.QuartzSchedule#unschedule(org.quartz.Scheduler)
358      */

359     public void unscheduleFrom(Scheduler scheduler) throws SchedulerException {
360         scheduler.unscheduleJob(triggerName(), GROUP_NAME);
361     }
362     
363     /**
364      * Get the last complete date.
365      *
366      * @return The last complete date.
367      */

368     public Interval getLastComplete() {
369         return lastComplete;
370     }
371
372     /**
373     /**
374      * @oddjob.property reSchedule
375      * @oddjob.description Allows the schedule to be set back. Unlike setting nextDue
376      * which with every successful completion will cause the schedule to move forward
377      * an interval, setting reSchedule will cause the schedule to be revaluated. If
378      * you have a schedule which runs at 7am every day, setting the reschedule property
379      * to 2am today will cause the schdule to rerun again as if it were 7am.
380      * @oddjob.required No.
381      */

382     public void setReSchedule(Date JavaDoc reSchedule) {
383         logger().debug("Rescheduling [" + reSchedule + "]");
384         Interval old = this.lastComplete;
385         this.lastComplete = new Interval(reSchedule, reSchedule);
386
387         // remove this listener from exsiting schedule calculator.
388
scheduleCalculator.removeScheduleListener(this);
389         
390         // recreate the schedule calculator
391
scheduleCalculator = new ScheduleCalculator(getClock(),
392                 normalSchedule, retrySchedule, timeZone);
393         scheduleCalculator.addScheduleListener(this);
394
395         scheduleCalculator.initialise(this.lastComplete, contextData);
396         
397         changes.firePropertyChange("lastComplete", old, reSchedule);
398     }
399
400     /**
401      * Get the schedule date.
402      *
403      * @return The start Date of the job.
404      */

405     public Date JavaDoc getScheduleDate() {
406         return scheduleDate;
407     }
408         
409     /**
410      * Set the schedule date.
411      *
412      * @param scheduleDate The schedule date.
413      */

414     public void setScheduleDate(Date JavaDoc scheduleDate) {
415         this.scheduleDate = scheduleDate;
416     }
417     
418     /**
419      * Get the time zone id to use in this schedule.
420      *
421      * @return The time zone idbeing used.
422      */

423     public String JavaDoc getTimeZone() {
424         if (timeZone == null) {
425             return null;
426         }
427         return timeZone.getID();
428     }
429
430     /**
431      * Set the time zone.
432      *
433      * @param timeZoneId the timeZoneId.
434      */

435     public void setTimeZone(String JavaDoc timeZoneId) {
436         if (timeZoneId == null) {
437             this.timeZone = null;
438         } else {
439             this.timeZone = TimeZone.getTimeZone(timeZoneId);
440         }
441     }
442         
443     /**
444      */

445     class Execute implements Runnable JavaDoc {
446         synchronized public void run() {
447             logger().debug("Executing at [" + new Date JavaDoc()+ "]");
448             Runnable JavaDoc job = (Runnable JavaDoc) JobToken.retrieve(componentRegistry, jobToken);
449
450             if (job == null) {
451                 setJobStateException(
452                         new NullPointerException JavaDoc("Failed to find job for [" + jobToken + "]"));
453             }
454             
455             if (job instanceof Resetable) {
456                 if (retry) {
457                     ((Resetable) job).softReset();
458                 }
459                 else {
460                     // normal schedule means job completed
461
((Resetable) job).hardReset();
462                 }
463             }
464             ScheduleStateListener ssl = new ScheduleStateListener();
465             ((Stateful) job).addJobStateListener(ssl);
466             
467             try {
468                 job.run();
469             }
470             catch (Throwable JavaDoc t) {
471                 ssl.jobStateException((Stateful) job, new Date JavaDoc(), t);
472             }
473         }
474     }
475
476     class ScheduleStateListener extends AbstractJobStateListener {
477         
478         // When job state is complete save the last complete time.
479
protected void jobStateComplete(Stateful source, Date JavaDoc time) {
480             source.removeJobStateListener(this);
481             logger().debug("Job [" + source + "] Complete");
482             scheduleCalculator.calculateComplete();
483         }
484
485         // When jobstate is not complete move next schedule time on using either the
486
// retry schedule or the normal schedule. The time used is the time now.
487
protected void jobStateNotComplete(Stateful source, Date JavaDoc time) {
488             source.removeJobStateListener(this);
489             logger().debug("Job [" + source + "] Not Complete");
490             scheduleCalculator.calculateRetry();
491         }
492             
493         // When the job state is an exception move the job state on using the retry schedule.
494

495         protected void jobStateException(Stateful source, Date JavaDoc time, Throwable JavaDoc t) {
496             source.removeJobStateListener(this);
497             logger().debug("Job [" + source + "] Exception");
498             scheduleCalculator.calculateRetry();
499         }
500         
501         // will be called when first added to job.
502
protected void jobStateReady(Stateful source, Date JavaDoc time) {
503             setJobStateReady();
504         }
505         
506         // show the job is executing
507
protected void jobStateExecuting(Stateful source, Date JavaDoc time) {
508             iconHelper.changeIcon(IconHelper.EXECUTING);
509             stateHandler.requestJobStateExecuting();
510         }
511         
512     }
513
514     public String JavaDoc toString() {
515         if (getName() == null) {
516             return "OddjobSchedule [" + jobToken + "]";
517         }
518         return getName();
519     }
520
521     public void onDestroy() {
522         scheduleCalculator.removeScheduleListener(this);
523         scheduleCalculator = null;
524     }
525 }
526
Popular Tags