KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > sourceforge > cruisecontrol > Schedule


1 /********************************************************************************
2  * CruiseControl, a Continuous Integration Toolkit
3  * Copyright (c) 2001-2003, ThoughtWorks, Inc.
4  * 651 W Washington Ave. Suite 600
5  * Chicago, IL 60661 USA
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
10  * are met:
11  *
12  * + Redistributions of source code must retain the above copyright
13  * notice, this list of conditions and the following disclaimer.
14  *
15  * + Redistributions in binary form must reproduce the above
16  * copyright notice, this list of conditions and the following
17  * disclaimer in the documentation and/or other materials provided
18  * with the distribution.
19  *
20  * + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the
21  * names of its contributors may be used to endorse or promote
22  * products derived from this software without specific prior
23  * written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
29  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
30  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
31  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
32  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
33  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
34  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
35  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36  ********************************************************************************/

37 package net.sourceforge.cruisecontrol;
38
39 import java.text.DateFormat JavaDoc;
40 import java.text.SimpleDateFormat JavaDoc;
41 import java.util.ArrayList JavaDoc;
42 import java.util.Calendar JavaDoc;
43 import java.util.Date JavaDoc;
44 import java.util.Iterator JavaDoc;
45 import java.util.List JavaDoc;
46 import java.util.Map JavaDoc;
47
48 import net.sourceforge.cruisecontrol.util.DateUtil;
49 import net.sourceforge.cruisecontrol.util.ValidationHelper;
50
51 import org.apache.log4j.Logger;
52 import org.jdom.Element;
53
54 /**
55  * Handles scheduling different builds.
56  *
57  * @author alden almagro, ThoughtWorks, Inc. 2001-2
58  */

59 public class Schedule {
60
61     private static final Logger LOG = Logger.getLogger(Schedule.class);
62
63     static final long ONE_SECOND = 1000;
64     static final long ONE_MINUTE = 60 * ONE_SECOND;
65     static final long ONE_DAY = 24 * 60 * ONE_MINUTE;
66     static final long ONE_YEAR = ONE_DAY * 365;
67
68     static final long MAX_INTERVAL_SECONDS = 60 * 60 * 24 * 365;
69     static final long MAX_INTERVAL_MILLISECONDS = MAX_INTERVAL_SECONDS * 1000;
70
71     private List JavaDoc builders = new ArrayList JavaDoc();
72     private List JavaDoc pauseBuilders = new ArrayList JavaDoc();
73     private long interval = 300 * ONE_SECOND;
74
75     /** date formatting for time statements */
76     private final DateFormat JavaDoc timeFormatter = new SimpleDateFormat JavaDoc("HH:mm");
77
78     public void add(Builder builder) {
79         checkParamNotNull("builder", builder);
80         builders.add(builder);
81     }
82     
83     public void add(PauseBuilder pause) {
84         checkParamNotNull("pauseBuilder", pause);
85         pauseBuilders.add(pause);
86     }
87
88     /**
89      * Determine if CruiseControl should run a build, given the current time.
90      *
91      * @param now The current date
92      * @return true if CruiseControl is currently paused (no build should run).
93      */

94     public boolean isPaused(Date JavaDoc now) {
95         checkParamNotNull("date", now);
96         PauseBuilder pause = findPause(now);
97         if (pause != null) {
98             LOG.info("CruiseControl is paused until: " + getEndTimeString(pause));
99             return true;
100         }
101         return false;
102     }
103
104     /**
105      * Returns a String representing the time following the end time of
106      * the given {@link PauseBuilder}.
107      *
108      * @param builder the <code>PauseBuilder</code> to be considered.
109      * @return a String representing the time following the end time of
110      * the <code>PauseBuilder</code>.
111      */

112     private String JavaDoc getEndTimeString(PauseBuilder builder) {
113         Calendar JavaDoc cal = Calendar.getInstance();
114         cal.set(Calendar.HOUR_OF_DAY, builder.getEndTime() / 100);
115         cal.set(Calendar.MINUTE, builder.getEndTime() % 100);
116         cal.add(Calendar.MINUTE, 1);
117         return timeFormatter.format(cal.getTime());
118     }
119
120     PauseBuilder findPause(Date JavaDoc date) {
121         checkParamNotNull("date", date);
122         Iterator JavaDoc pauseBuilderIterator = pauseBuilders.iterator();
123         while (pauseBuilderIterator.hasNext()) {
124             PauseBuilder pause = (PauseBuilder) pauseBuilderIterator.next();
125             if (pause.isPaused(date)) {
126                 return pause;
127             }
128         }
129         return null;
130     }
131
132     /**
133      * Select the correct <code>Builder</code> and start a build.
134      *
135      * @param buildNumber The sequential build number.
136      * @param lastBuild The date of the last build.
137      * @param now The current time.
138      * @param properties Properties that would need to be passed in to the actual build tool.
139      * @param buildTarget the build target to use instead of the configured one (pass in null if no override is needed)
140      *
141      * @return JDOM Element representation of build log.
142      */

143     public Element build(int buildNumber, Date JavaDoc lastBuild, Date JavaDoc now, Map JavaDoc properties, String JavaDoc buildTarget)
144       throws CruiseControlException {
145         Builder builder = selectBuilder(buildNumber, lastBuild, now);
146         if (buildTarget != null) {
147             LOG.info("Overriding build target with \"" + buildTarget + "\"");
148             return builder.buildWithTarget(properties, buildTarget);
149         }
150         return builder.build(properties);
151     }
152
153     /**
154      * Select the correct build based on the current buildNumber and time.
155      *
156      * @param buildNumber The sequential build number
157      * @param lastBuild The date of the last build.
158      * @param now The current time.
159      *
160      * @return The <code>Builder</code> that should be run.
161      */

162     protected Builder selectBuilder(int buildNumber, Date JavaDoc lastBuild, Date JavaDoc now)
163         throws CruiseControlException {
164         Builder builder = findBuilder(buildNumber, lastBuild, now);
165         
166         if (builder == null) {
167             long timeToNextBuild = getTimeToNextBuild(now, ONE_MINUTE);
168             Date JavaDoc futureDate = getFutureDate(now, timeToNextBuild);
169             builder = findBuilder(buildNumber, now, futureDate);
170         }
171         
172         if (builder == null) {
173             validate();
174             throw new CruiseControlException("configuration error not caught by validate? no builder selected");
175         }
176
177         return builder;
178     }
179
180     private Builder findBuilder(int buildNumber, Date JavaDoc lastBuild, Date JavaDoc now) throws CruiseControlException {
181         Iterator JavaDoc builderIterator = builders.iterator();
182         while (builderIterator.hasNext()) {
183             Builder builder = (Builder) builderIterator.next();
184             int buildTime = builder.getTime();
185             boolean isTimeBuilder = buildTime >= 0;
186             if (isTimeBuilder) {
187                 boolean didntBuildToday = builderDidntBuildToday(lastBuild, now, buildTime);
188                 int nowTime = DateUtil.getTimeFromDate(now);
189                 boolean isAfterBuildTime = buildTime <= nowTime;
190                 boolean isValidDay = builder.isValidDay(now);
191                 if (didntBuildToday && isAfterBuildTime && isValidDay) {
192                     return builder;
193                 }
194             } else if (builder.getMultiple() > 0) {
195                 if (builder.isValidDay(now)) {
196                     if ((buildNumber % builder.getMultiple()) == 0) {
197                         return builder;
198                     }
199                 }
200             } else {
201                 throw new CruiseControlException("The selected Builder is not properly configured");
202             }
203         }
204                
205         return null;
206     }
207
208     boolean builderDidntBuildToday(Date JavaDoc lastBuild, Date JavaDoc now, int buildTime) {
209         int time = DateUtil.getTimeFromDate(now);
210         long timeMillis = DateUtil.convertToMillis(time);
211         long startOfToday = now.getTime() - timeMillis;
212         boolean lastBuildYesterday = lastBuild.getTime() < startOfToday;
213         boolean lastBuildTimeBeforeBuildTime = DateUtil.getTimeFromDate(lastBuild) < buildTime;
214         return lastBuildYesterday || lastBuildTimeBeforeBuildTime;
215     }
216
217     long getTimeToNextBuild(Date JavaDoc now, long sleepInterval) {
218         return getTimeToNextBuild(now, sleepInterval, 0);
219     }
220
221     private long getTimeToNextBuild(Date JavaDoc now, long sleepInterval, long priorPauseAdjustment) {
222         long timeToNextBuild = sleepInterval;
223         LOG.debug("getTimeToNextBuild: initial timeToNextBuild = " + timeToNextBuild);
224         timeToNextBuild = checkMultipleBuilders(now, timeToNextBuild);
225         LOG.debug("getTimeToNextBuild: after checkMultipleBuilders = " + timeToNextBuild);
226         timeToNextBuild = checkTimeBuilders(now, timeToNextBuild);
227         LOG.debug("getTimeToNextBuild: after checkTimeBuilders = " + timeToNextBuild);
228         long timeTillNotPaused = checkPauseBuilders(now, timeToNextBuild);
229         LOG.debug("getTimeToNextBuild: after checkPauseBuilders = " + timeToNextBuild);
230         
231         if (timeToNextBuild != timeTillNotPaused) {
232             boolean atMaxTime = timeTillNotPaused >= MAX_INTERVAL_MILLISECONDS
233                               || priorPauseAdjustment >= MAX_INTERVAL_MILLISECONDS;
234             if (hasOnlyTimeBuilders() && !atMaxTime) {
235                 Date JavaDoc dateAfterPause = getFutureDate(now, timeTillNotPaused);
236                 long adjustmentFromEndOfPause = getTimeToNextBuild(dateAfterPause,
237                                                                                 0,
238                                          priorPauseAdjustment + timeTillNotPaused);
239                 timeToNextBuild = timeTillNotPaused + adjustmentFromEndOfPause;
240                 timeToNextBuild = checkMaximumInterval(timeToNextBuild);
241             } else {
242                 timeToNextBuild = timeTillNotPaused;
243             }
244         }
245         
246         return timeToNextBuild;
247     }
248
249     private long checkMultipleBuilders(Date JavaDoc now, long interval) {
250         if (hasOnlyTimeBuilders()) {
251             LOG.debug("has only time builders, so no correction for multiple builders.");
252             return interval;
253         }
254         
255         Date JavaDoc then = getFutureDate(now, interval);
256         
257         List JavaDoc buildersForOtherDays = new ArrayList JavaDoc();
258         Iterator JavaDoc iterator = builders.iterator();
259         while (iterator.hasNext()) {
260             Builder builder = (Builder) iterator.next();
261             boolean isTimeBuilder = builder.getTime() != Builder.NOT_SET;
262             if (!isTimeBuilder) {
263                 if (builder.getMultiple() == 1) {
264                     if (builder.isValidDay(then)) {
265                         LOG.debug("multiple=1 builder found that could run on " + then);
266                         return interval;
267                     } else {
268                         buildersForOtherDays.add(builder);
269                     }
270                 }
271             }
272         }
273         
274         if (buildersForOtherDays.size() == 0) {
275             LOG.error("configuration error: has some multiple builders but no multiple=1 builders found!");
276             return interval;
277         } else {
278             LOG.debug("no multiple=1 builders found for " + then + ". checking other days");
279         }
280         
281         for (int i = 1; i < 7; i++) {
282             long daysPastInitialInterval = i * ONE_DAY;
283             then = getFutureDate(now, interval + daysPastInitialInterval);
284             iterator = builders.iterator();
285             while (iterator.hasNext()) {
286                 Builder builder = (Builder) iterator.next();
287                 if (builder.isValidDay(then)) {
288                     LOG.debug("multiple=1 builder found that could run on " + then);
289                     long correctionToMidnight = getTimePastMidnight(then);
290                     return interval + daysPastInitialInterval - correctionToMidnight;
291                 }
292             }
293         }
294         
295         LOG.error("configuration error? could not find appropriate multiple=1 builder.");
296         return interval;
297     }
298
299     private long getTimePastMidnight(Date JavaDoc date) {
300         Calendar JavaDoc cal = Calendar.getInstance();
301         cal.setTime(date);
302         long time = 60 * ONE_MINUTE * cal.get(Calendar.HOUR_OF_DAY);
303         time += ONE_MINUTE * cal.get(Calendar.MINUTE);
304         return time;
305     }
306
307     private boolean hasOnlyTimeBuilders() {
308         boolean onlyTimeBuilders = true;
309         Iterator JavaDoc iterator = builders.iterator();
310         while (iterator.hasNext()) {
311             Builder builder = (Builder) iterator.next();
312             boolean isTimeBuilder = builder.getTime() != Builder.NOT_SET;
313             if (!isTimeBuilder) {
314                 onlyTimeBuilders = false;
315                 break;
316             }
317         }
318         return onlyTimeBuilders;
319     }
320
321     long checkTimeBuilders(Date JavaDoc now, long proposedTime) {
322         long timeToNextBuild = proposedTime;
323         if (hasOnlyTimeBuilders()) {
324             timeToNextBuild = Long.MAX_VALUE;
325         }
326         int nowTime = DateUtil.getTimeFromDate(now);
327         Iterator JavaDoc builderIterator = builders.iterator();
328         while (builderIterator.hasNext()) {
329             Builder builder = (Builder) builderIterator.next();
330             int thisBuildTime = builder.getTime();
331             boolean isTimeBuilder = thisBuildTime != Builder.NOT_SET;
332             if (isTimeBuilder) {
333                 long timeToThisBuild = Long.MAX_VALUE;
334                 Calendar JavaDoc cal = Calendar.getInstance();
335                 long oneYear = 365;
336                 for (int daysInTheFuture = 0; daysInTheFuture < oneYear; daysInTheFuture++) {
337                     cal.setTime(now);
338                     cal.add(Calendar.DATE, daysInTheFuture);
339                     Date JavaDoc future = cal.getTime();
340                     boolean dayIsValid = builder.isValidDay(future);
341                     if (dayIsValid) {
342                         boolean timePassedToday = (daysInTheFuture == 0) && (nowTime > thisBuildTime);
343                         if (!timePassedToday) {
344                             int buildHour = thisBuildTime / 100;
345                             int buildMinute = thisBuildTime % 100;
346                             cal.set(Calendar.HOUR_OF_DAY, buildHour);
347                             cal.set(Calendar.MINUTE, buildMinute);
348                             future = cal.getTime();
349                             timeToThisBuild = future.getTime() - now.getTime();
350                             break;
351                         }
352                     }
353                 }
354                 if (timeToThisBuild < timeToNextBuild) {
355                     timeToNextBuild = timeToThisBuild;
356                 }
357             }
358         }
359
360         if (timeToNextBuild > MAX_INTERVAL_MILLISECONDS) {
361             LOG.error(
362                 "checkTimeBuilders exceeding maximum interval. using proposed value ["
363                     + proposedTime
364                     + "] instead");
365             timeToNextBuild = proposedTime;
366         }
367         return timeToNextBuild;
368     }
369
370     long checkPauseBuilders(Date JavaDoc now, long proposedTime) {
371         long oldTime = proposedTime;
372         long newTime = checkForPauseAtProposedTime(now, oldTime);
373         while (oldTime != newTime) {
374             oldTime = newTime;
375             newTime = checkForPauseAtProposedTime(now, oldTime);
376         }
377         
378         return newTime;
379     }
380
381     private long checkForPauseAtProposedTime(Date JavaDoc now, long proposedTime) {
382         Date JavaDoc futureDate = getFutureDate(now, proposedTime);
383         PauseBuilder pause = findPause(futureDate);
384         if (pause == null) {
385             return proposedTime;
386         }
387
388         int endPause = pause.getEndTime();
389         int currentTime = DateUtil.getTimeFromDate(now);
390
391         long timeToEndOfPause = DateUtil.milliTimeDifference(currentTime, endPause);
392
393         while (timeToEndOfPause < proposedTime) {
394             timeToEndOfPause += ONE_DAY;
395         }
396
397         timeToEndOfPause = checkMaximumInterval(timeToEndOfPause);
398
399         return timeToEndOfPause == MAX_INTERVAL_MILLISECONDS ? timeToEndOfPause : timeToEndOfPause + ONE_MINUTE;
400     }
401
402     private long checkMaximumInterval(long timeToEndOfPause) {
403         if (timeToEndOfPause > MAX_INTERVAL_MILLISECONDS) {
404             LOG.error("maximum interval exceeded! project perpetually paused?");
405             return MAX_INTERVAL_MILLISECONDS;
406         }
407         return timeToEndOfPause;
408     }
409
410     private Date JavaDoc getFutureDate(Date JavaDoc now, long delay) {
411         long futureMillis = now.getTime() + delay;
412         return new Date JavaDoc(futureMillis);
413     }
414
415     public void setInterval(long intervalBetweenModificationChecks) {
416         if (intervalBetweenModificationChecks < 0) {
417             throw new IllegalArgumentException JavaDoc("interval can't be less than zero");
418         }
419         interval = intervalBetweenModificationChecks * ONE_SECOND;
420     }
421
422     public long getInterval() {
423         return interval;
424     }
425
426     public void validate() throws CruiseControlException {
427         ValidationHelper.assertTrue(builders.size() > 0,
428             "schedule element requires at least one nested builder element");
429
430         ValidationHelper.assertFalse(interval > ONE_YEAR,
431             "maximum interval value is " + MAX_INTERVAL_SECONDS + " (one year)");
432         
433         if (hasOnlyTimeBuilders()) {
434             LOG.warn("schedule has all time based builders: interval value will be ignored.");
435             ValidationHelper.assertFalse(checkWithinPause(new ArrayList JavaDoc(builders)), "all build times during pauses.");
436         }
437
438         //Validate the child builders, since no one else seems to be doing it.
439
for (Iterator JavaDoc iterator = builders.iterator(); iterator.hasNext();) {
440             Builder next = (Builder) iterator.next();
441             next.validate();
442         }
443     }
444
445     private boolean checkWithinPause(List JavaDoc timeBuilders) {
446         for (int i = 0; i < timeBuilders.size(); i++) {
447             Builder builder = (Builder) timeBuilders.get(i);
448             for (int j = 0; j < pauseBuilders.size(); j++) {
449                 PauseBuilder pauseBuilder = (PauseBuilder) pauseBuilders.get(j);
450                 if (buildDaySameAsPauseDay(builder, pauseBuilder) && buildTimeWithinPauseTime(builder, pauseBuilder)) {
451                     timeBuilders.remove(builder);
452                     StringBuffer JavaDoc message = new StringBuffer JavaDoc();
453                     message.append("time Builder for time ");
454                     message.append(Integer.toString(builder.getTime()));
455                     if (builder.getDay() != Builder.NOT_SET) {
456                         message.append(" and day of ");
457                         message.append(getDayString(builder.getDay()));
458                     }
459                     message.append(" is always within a pause and will never build");
460                     LOG.error(message.toString());
461                 }
462             }
463         }
464         return timeBuilders.isEmpty();
465     }
466
467     /**
468      * I can't believe this method doesn't already exist in the JDK...
469      * am I just missing it?!? Probably some locale thing I need to use.
470      * @param day int value
471      * @return english string value
472      */

473     private String JavaDoc getDayString(int day) {
474         switch(day) {
475         case Calendar.SUNDAY:
476             return "sunday";
477         case Calendar.MONDAY:
478             return "monday";
479         case Calendar.TUESDAY:
480             return "tuesday";
481         case Calendar.WEDNESDAY:
482             return "wednesday";
483         case Calendar.THURSDAY:
484             return "thursday";
485         case Calendar.FRIDAY:
486             return "friday";
487         case Calendar.SATURDAY:
488             return "saturday";
489         default:
490             return "invalid day " + day;
491         }
492     }
493
494     private boolean buildDaySameAsPauseDay(Builder builder, PauseBuilder pauseBuilder) {
495         return pauseBuilder.getDay() == PauseBuilder.NOT_SET
496                 || pauseBuilder.getDay() == builder.getDay();
497     }
498
499     private boolean buildTimeWithinPauseTime(Builder builder, PauseBuilder pauseBuilder) {
500         return pauseBuilder.getStartTime() < builder.getTime()
501                 && builder.getTime() < pauseBuilder.getEndTime();
502     }
503
504     /**
505      * utility method to check method parameters and ensure they're not null
506      * @param paramName name of the paramter to check
507      * @param param parameter to check
508      */

509     private void checkParamNotNull(String JavaDoc paramName, Object JavaDoc param) {
510         if (param == null) {
511             throw new IllegalArgumentException JavaDoc(paramName + " can't be null");
512         }
513     }
514
515     public List JavaDoc getBuilders() {
516         return builders;
517     }
518 }
519
Popular Tags