1 37 package net.sourceforge.cruisecontrol; 38 39 import java.text.DateFormat ; 40 import java.text.SimpleDateFormat ; 41 import java.util.ArrayList ; 42 import java.util.Calendar ; 43 import java.util.Date ; 44 import java.util.Iterator ; 45 import java.util.List ; 46 import java.util.Map ; 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 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 builders = new ArrayList (); 72 private List pauseBuilders = new ArrayList (); 73 private long interval = 300 * ONE_SECOND; 74 75 76 private final DateFormat timeFormatter = new SimpleDateFormat ("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 94 public boolean isPaused(Date 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 112 private String getEndTimeString(PauseBuilder builder) { 113 Calendar 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 date) { 121 checkParamNotNull("date", date); 122 Iterator 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 143 public Element build(int buildNumber, Date lastBuild, Date now, Map properties, String 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 162 protected Builder selectBuilder(int buildNumber, Date lastBuild, Date 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 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 lastBuild, Date now) throws CruiseControlException { 181 Iterator 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 lastBuild, Date 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 now, long sleepInterval) { 218 return getTimeToNextBuild(now, sleepInterval, 0); 219 } 220 221 private long getTimeToNextBuild(Date 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 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 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 then = getFutureDate(now, interval); 256 257 List buildersForOtherDays = new ArrayList (); 258 Iterator 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 date) { 300 Calendar 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 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 now, long proposedTime) { 322 long timeToNextBuild = proposedTime; 323 if (hasOnlyTimeBuilders()) { 324 timeToNextBuild = Long.MAX_VALUE; 325 } 326 int nowTime = DateUtil.getTimeFromDate(now); 327 Iterator 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 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 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 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 now, long proposedTime) { 382 Date 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 getFutureDate(Date now, long delay) { 411 long futureMillis = now.getTime() + delay; 412 return new Date (futureMillis); 413 } 414 415 public void setInterval(long intervalBetweenModificationChecks) { 416 if (intervalBetweenModificationChecks < 0) { 417 throw new IllegalArgumentException ("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 (builders)), "all build times during pauses."); 436 } 437 438 for (Iterator iterator = builders.iterator(); iterator.hasNext();) { 440 Builder next = (Builder) iterator.next(); 441 next.validate(); 442 } 443 } 444 445 private boolean checkWithinPause(List 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 message = new StringBuffer (); 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 473 private String 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 509 private void checkParamNotNull(String paramName, Object param) { 510 if (param == null) { 511 throw new IllegalArgumentException (paramName + " can't be null"); 512 } 513 } 514 515 public List getBuilders() { 516 return builders; 517 } 518 } 519 | Popular Tags |