KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > oddjob > scheduling > RepeatJob


1 /*
2  * Copyright (c) 2004, Rob Gordon.
3  */

4 package org.oddjob.scheduling;
5
6 import java.util.Date JavaDoc;
7
8 import org.oddjob.Resetable;
9 import org.oddjob.Stateful;
10 import org.oddjob.Stoppable;
11 import org.oddjob.framework.StructuralJob;
12 import org.oddjob.schedules.Interval;
13 import org.oddjob.schedules.Schedule;
14 import org.oddjob.schedules.ScheduleCalculator;
15 import org.oddjob.schedules.ScheduleListener;
16 import org.oddjob.schedules.schedules.NowSchedule;
17 import org.oddjob.state.AbstractJobStateListener;
18 import org.oddjob.util.Clock;
19 import org.oddjob.util.DefaultClock;
20 import org.oddjob.util.OddjobConfigException;
21
22 /**
23  * @oddjob.description This job will repeatadly run it's
24  * child job. The repeats are determined by schedules. An optional
25  * regular schedule and an optional retry schedule can be configured.
26  * The schedules are as specified by
27  * {@link org.oddjob.schedules.ScheduleElement}
28  * <p>
29  * It might not be immediately obvious why a repeat job should involve
30  * schedules but frequently a job will be repeated because it fails and that
31  * failure will be due to a network connection or database failure where
32  * immediate retries are unnecessary and can impact on overall system
33  * resources. It's often much better to wait a few seconds before retrying.
34  * This effect could be achieved using a scheduler but a scheduler is sometimes
35  * to heavy-weight especially if there are only a few jobs to run.
36  * <p>
37  * A repeat without a schedule will execute once.
38  *
39  * <p>
40  * An exception job can be configured which will be run if the
41  * child either doesn't complete or ends in an exception state. If
42  * a retry schedule is specified
43  * then the exception job won't be triggered until the retry schedule
44  * expires.
45  *
46  * @oddjob.example
47  *
48  * <code><pre>
49  * &lt;repeat name="Repeat 10 times"&gt;
50  * &lt;schedule&gt;
51  * &lt;count count="10"/&gt;
52  * &lt;/schedule&gt;
53  * &lt;child&gt;
54  * &lt;sequential&gt;
55  * &lt;sequence id="seq"/&gt;
56  * &lt;echo text="Hello ${seq.current}"/&gt;
57  * &lt;/sequential&gt;
58  * &lt;/child&gt;
59  * &lt;/repeat&gt;
60  * </pre></code>
61  *
62  * @oddjob.example
63  *
64  * <code><pre>
65  * &lt;repeat name="Repeat immediately until complete"&gt;
66  * &lt;retry&gt;
67  * &lt;now/&gt;
68  * &lt;/retry&gt;
69  * &lt;child&gt;
70  * &lt;random class="org.oddjob.samples.RandomTask"/&gt;
71  * &lt;/child&gt;
72  * &lt;/repeat&gt;
73  * </pre></code>
74  *
75  * @oddjob.example
76  *
77  * <code><pre>
78  * &lt;repeat name="Try 10 times, 1 seconds apart, before alerting"&gt;
79  * &lt;retry&gt;
80  * &lt;count count="10"&gt;
81  * &lt;interval interval="00:00:01"/&gt;
82  * &lt;/count&gt;
83  * &lt;/retry&gt;
84  * &lt;child&gt;
85  * &lt;oddjob file="doesnt.exist"/&gt;
86  * &lt;/child&gt;
87  * &lt;exception&gt;
88  * &lt;echo text="Help!"/&gt;
89  * &lt;/exception&gt;
90  * &lt;/repeat&gt;
91  * </pre></code>
92  *
93  * @author Rob Gordon.
94  */

95 public class RepeatJob extends StructuralJob
96 implements Stoppable, ScheduleListener {
97     private static final long serialVersionUID = 20051121;
98     
99     /**
100      * @oddjob.property
101      * @oddjob.description A schedule for normal completion. It defaults
102      * to immediately.
103      * @oddjob.required No.
104      */

105     private transient Schedule normalSchedule = new NowSchedule();
106     
107     /**
108      * @oddjob.element retry
109      * @oddjob.description The schedule used in the event
110      * that the child job doesn't complete or is in an
111      * exception state.
112      * @oddjob.required No.
113      */

114     private transient Schedule retrySchedule;
115
116     /**
117      * @oddjob.element child
118      * @oddjob.description The job to run.
119      * @oddjob.required Yes.
120      */

121     private transient Runnable JavaDoc child;
122
123     /**
124      * @oddjob.element exception
125      * @oddjob.description The job to run on failure.
126      * @oddjob.required No.
127      */

128     private Runnable JavaDoc exceptionJob;
129
130     /** The clock - now only used for testing.
131      */

132     private transient Clock clock;
133
134     /** Next due date */
135     private transient Date JavaDoc nextDue;
136
137     /** Retry, so only soft reset. */
138     private transient boolean retry;
139     
140     /** failed */
141     private transient boolean failed;
142     
143     /**
144      * @oddjob.property
145      * @oddjob.description This is the start of the last normal schedule.
146      * The schedule date is used when the retry schedule polls past midnight,
147      * yet your business data is from the day before. The schedule date provides
148      * a consistent business date which can useful as the date for
149      * file names etc.
150      * @oddjob.required No.
151      */

152     private transient Date JavaDoc scheduleDate;
153     
154     /** Schedule calculator */
155     private transient ScheduleCalculator scheduleCalculator;
156         
157     /**
158      * @oddjob.element
159      * @oddjob.descritpion The job who's execution
160      * to schedule.
161      * @oddjob.required Yes.
162      */

163     public void addComponentChild(Runnable JavaDoc child) {
164         if (!(child instanceof Stateful)) {
165             throw new IllegalArgumentException JavaDoc("Child must be Stateful.");
166         }
167         this.child = child;
168     }
169     
170     /**
171      * @oddjob.element exception
172      * @oddjob.description Add a job to execute in
173      * the event that the child job has not completed or
174      * is in an exception state and the retry schedule has
175      * expired or there was no retry schedule.
176      * @oddjob.required No.
177      *
178      * @param exceptionJob The exception job.
179      */

180     public void addComponentException(Runnable JavaDoc exceptionJob) {
181         this.exceptionJob = exceptionJob;
182     }
183     
184     /**
185      * Set the schedule.
186      *
187      * @param schedule The schedule.
188      */

189     public void setSchedule(Schedule schedule) {
190         this.normalSchedule = schedule;
191     }
192     
193     /**
194      * Getter for schedule.
195      *
196      * @return The schedule.
197      */

198     public Schedule getSchedule() {
199         return this.normalSchedule;
200     }
201         
202     /**
203      * Set the retry schedule.
204      *
205      * @param retry
206      */

207     public void setRetry(Schedule retry) {
208         this.retrySchedule = retry;
209     }
210     
211     /**
212      * Getter for retry.
213      *
214      * @return The retry schedule.
215      */

216     public Schedule getRetry() {
217         return this.retrySchedule;
218     }
219     
220     /**
221      * Get the clock to use in this schedule.
222      *
223      * @return The clock being used.
224      */

225     Clock getClock() {
226         if (clock == null) {
227             clock = new DefaultClock();
228             logger().debug("Using default clock.");
229         }
230         return clock;
231     }
232
233     /**
234      * Set the clock. Only useful during testing.
235      *
236      * @param clock The clock.
237      */

238     void setClock(Clock clock) {
239         this.clock = clock;
240         logger().debug("set clock [" + clock + "]");
241     }
242
243     /**
244      * Initialise this job. Must be called before run but after adding
245      * children.
246      *
247      */

248     public void init() {
249         if (child == null) {
250             throw new OddjobConfigException("Child job is missing.");
251         }
252         childHelper.addChild(child);
253         if (exceptionJob != null) {
254             childHelper.addChild(exceptionJob);
255         }
256         childHelper.initialise();
257     }
258     
259     /*
260      * (non-Javadoc)
261      * @see org.oddjob.jobs.AbstractJob#execute()
262      */

263     protected void execute() {
264         Object JavaDoc[] children = childHelper.getChildren();
265         if (children.length == 0) {
266             throw new IllegalStateException JavaDoc("Call init() first!");
267         }
268
269         // initilise a new schedule calculator
270
scheduleCalculator = new ScheduleCalculator(getClock(),
271                 normalSchedule, retrySchedule, null);
272         scheduleCalculator.addScheduleListener(this);
273         scheduleCalculator.initialise();
274         
275         Runnable JavaDoc job = (Runnable JavaDoc) children[0];
276         while (!stop) {
277             Date JavaDoc timeNow = getClock().getDate();
278             logger().debug("Time now is " + timeNow);
279             
280             if (failed) {
281                 if (children.length > 1) {
282                     Runnable JavaDoc ej = (Runnable JavaDoc) children[1];
283                     if (ej instanceof Resetable) {
284                         ((Resetable) ej).hardReset();
285                     }
286                     ej.run();
287                 }
288                 failed = false;
289             }
290             else {
291                 if (nextDue == null) {
292                     logger().debug("Schedule finished.");
293                     break;
294                 }
295                 
296                 long sleepTime = nextDue.getTime() - timeNow.getTime();
297
298                 if (sleepTime <= 0) {
299                 // time to run
300
logger().debug("Executing at [" + timeNow + "]");
301                     if (job instanceof Resetable) {
302                         if (retry) {
303                             ((Resetable) job).softReset();
304                         }
305                         else {
306                             ((Resetable) job).hardReset();
307                         }
308                     }
309                     ScheduleStateListener ssl = new ScheduleStateListener();
310                     ((Stateful) job).addJobStateListener(ssl);
311                     
312                     job.run();
313                     
314                     ((Stateful) job).removeJobStateListener(ssl);
315                 }
316                 else {
317                     logger().debug(getName() + " next due at "
318                             + getNextDue());
319                     sleep(sleepTime);
320                 }
321             } // if retry
322
} // end while
323
scheduleCalculator.removeScheduleListener(this);
324     }
325
326     /* (non-Javadoc)
327      * @see org.oddjob.treesched.ScheduleListener#initialised(java.util.Date)
328      */

329     public void initialised(Date JavaDoc scheduleDate) {
330         this.scheduleDate = scheduleDate;
331         this.nextDue = scheduleDate;
332         this.retry = false;
333         this.failed = false;
334     }
335     
336     /* (non-Javadoc)
337      * @see org.oddjob.treesched.ScheduleListener#complete(java.util.Date, java.util.Date)
338      */

339     public void complete(Date JavaDoc scheduleDate, Interval lastComplete) {
340         this.scheduleDate = scheduleDate;
341         this.nextDue = scheduleDate;
342         this.retry = false;
343         this.failed = false;
344     }
345     
346     /* (non-Javadoc)
347      * @see org.oddjob.treesched.ScheduleListener#retry(java.util.Date, java.util.Date)
348      */

349     public void retry(Date JavaDoc scheduleDate, Date JavaDoc retryDate) {
350         this.scheduleDate = scheduleDate;
351         this.nextDue = retryDate;
352         this.retry = true;
353         this.failed = false;
354     }
355     
356     /* (non-Javadoc)
357      * @see org.oddjob.treesched.ScheduleListener#failed(java.util.Date)
358      */

359     public void failed(Date JavaDoc scheduleDate) {
360         this.scheduleDate = scheduleDate;
361         this.nextDue = scheduleDate;
362         this.retry = false;
363         this.failed = true;
364     }
365     
366     class ScheduleStateListener extends AbstractJobStateListener {
367         
368         // When job state is complete save the last complete time.
369
protected void jobStateComplete(Stateful source, Date JavaDoc time) {
370             logger().debug("Job Complete");
371             scheduleCalculator.calculateComplete();
372         }
373
374         // When jobstate is not complete move next schedule time on using either the
375
// retry schedule or the normal schedule. The time used is the time now.
376
protected void jobStateNotComplete(Stateful source, Date JavaDoc time) {
377             logger().debug("Job Not Complete");
378             scheduleCalculator.calculateRetry();
379         }
380             
381         // When the job state is an exception move the job state on using the retry schedule.
382

383         protected void jobStateException(Stateful source, Date JavaDoc time, Throwable JavaDoc t) {
384             logger().debug("Job Exception");
385             scheduleCalculator.calculateRetry();
386         }
387     }
388     /**
389      * @return Returns the nextDue.
390      */

391     public Date JavaDoc getNextDue() {
392         return nextDue;
393     }
394     /**
395      * @return Returns the schduleDate.
396      */

397     public Date JavaDoc getScheduleDate() {
398         return scheduleDate;
399     }
400     /**
401      * @param schduleDate The schduleDate to set.
402      */

403     public void setScheduleDate(Date JavaDoc schduleDate) {
404         this.scheduleDate = schduleDate;
405     }
406 }
407
Popular Tags