1 37 package net.sourceforge.cruisecontrol; 38 39 import java.io.FileOutputStream ; 40 import java.io.IOException ; 41 import java.io.ObjectInputStream ; 42 import java.io.ObjectOutputStream ; 43 import java.io.Serializable ; 44 import java.util.ArrayList ; 45 import java.util.Date ; 46 import java.util.HashMap ; 47 import java.util.Iterator ; 48 import java.util.List ; 49 import java.util.Map ; 50 51 import net.sourceforge.cruisecontrol.events.BuildProgressEvent; 52 import net.sourceforge.cruisecontrol.events.BuildProgressListener; 53 import net.sourceforge.cruisecontrol.events.BuildResultEvent; 54 import net.sourceforge.cruisecontrol.events.BuildResultListener; 55 import net.sourceforge.cruisecontrol.listeners.ProjectStateChangedEvent; 56 import net.sourceforge.cruisecontrol.util.DateUtil; 57 58 import org.apache.log4j.Logger; 59 import org.jdom.Element; 60 61 66 public class Project implements Serializable , Runnable { 67 static final long serialVersionUID = 2656877748476842326L; 68 private static final Logger LOG = Logger.getLogger(Project.class); 69 70 private transient ProjectState state; 71 72 private transient ProjectConfig projectConfig; 73 private transient LabelIncrementer labelIncrementer; 74 75 80 private transient Long overrideBuildInterval; 81 82 private transient Date buildStartTime; 83 private transient Object pausedMutex; 84 private transient Object scheduleMutex; 85 private transient Object waitMutex; 86 private transient BuildQueue queue; 87 private transient List progressListeners; 88 private transient List resultListeners; 89 90 private int buildCounter = 0; 91 private Date lastBuild = DateUtil.getMidnight(); 92 private Date lastSuccessfulBuild = lastBuild; 93 private boolean wasLastBuildSuccessful = true; 94 private String label; 95 private String name; 96 private boolean buildForced = false; 97 private String buildTarget = null; 98 private boolean isPaused = false; 99 private boolean buildAfterFailed = true; 100 private boolean stopped = true; 101 102 public Project() { 103 initializeTransientFields(); 104 } 105 106 private void initializeTransientFields() { 107 state = ProjectState.STOPPED; 108 109 pausedMutex = new Object (); 110 scheduleMutex = new Object (); 111 waitMutex = new Object (); 112 progressListeners = new ArrayList (); 113 resultListeners = new ArrayList (); 114 } 115 116 private void readObject(ObjectInputStream stream) throws IOException , ClassNotFoundException { 117 stream.defaultReadObject(); 118 initializeTransientFields(); 119 } 120 121 public void execute() { 122 if (stopped) { 123 LOG.warn("not building project " + name + " because project has been stopped."); 124 buildFinished(); 125 return; 126 } 127 128 synchronized (pausedMutex) { 129 if (isPaused) { 130 LOG.info("not building project " + name + " because project has been paused."); 131 buildFinished(); 132 return; 133 } 134 } 135 136 try { 137 init(); 138 build(); 139 } catch (CruiseControlException e) { 140 LOG.error("exception attempting build in project " + name, e); 141 } finally { 142 buildFinished(); 143 } 144 } 145 146 149 protected void build() throws CruiseControlException { 150 if (projectConfig == null) { 151 throw new IllegalStateException ("projectConfig must be set on project before calling build()"); 152 } 153 154 if (stopped) { 155 LOG.warn("not building project " + name + " because project has been stopped."); 156 return; 157 } 158 159 try { 160 setBuildStartTime(new Date ()); 161 Schedule schedule = projectConfig.getSchedule(); 162 if (schedule == null) { 163 throw new IllegalStateException ("project must have a schedule"); 164 } 165 if (schedule.isPaused(buildStartTime)) { 166 return; 169 } 170 171 bootstrap(); 172 173 boolean buildWasForced = buildForced; 174 String target = useAndResetBuildTargetIfBuildWasForced(buildWasForced); 175 resetBuildForcedOnlyIfBuildWasForced(buildWasForced); 176 177 Element modifications = getModifications(buildWasForced); 179 180 if (modifications == null) { 181 return; 182 } 183 184 projectConfig.getLog().addContent(modifications); 185 186 Date now = new Date (); 187 if (projectConfig.getModificationSet() != null) { 188 now = projectConfig.getModificationSet().getTimeOfCheck(); 189 } 190 191 if (getLabelIncrementer().isPreBuildIncrementer()) { 192 label = getLabelIncrementer().incrementLabel(label, projectConfig.getLog().getContent()); 193 } 194 195 projectConfig.getLog().addContent(getProjectPropertiesElement(now)); 197 198 setState(ProjectState.BUILDING); 199 Element buildLog = schedule.build(buildCounter, lastBuild, now, getProjectPropertiesMap(now), target); 200 projectConfig.getLog().addContent(buildLog.detach()); 201 202 boolean buildSuccessful = projectConfig.getLog().wasBuildSuccessful(); 203 fireResultEvent(new BuildResultEvent(this, buildSuccessful)); 204 205 if (!getLabelIncrementer().isPreBuildIncrementer() && buildSuccessful) { 206 label = getLabelIncrementer().incrementLabel(label, projectConfig.getLog().getContent()); 207 } 208 209 setState(ProjectState.MERGING_LOGS); 210 projectConfig.getLog().writeLogFile(now); 211 212 if (!buildAfterFailed) { 215 lastBuild = now; 216 } 217 218 if (buildSuccessful) { 220 lastBuild = now; 221 lastSuccessfulBuild = now; 222 info("build successful"); 223 } else { 224 info("build failed"); 225 } 226 227 buildCounter++; 228 setWasLastBuildSuccessful(buildSuccessful); 229 230 serializeProject(); 231 232 publish(); 233 projectConfig.getLog().reset(); 234 } finally { 235 setState(ProjectState.IDLE); 236 } 237 } 238 239 private String useAndResetBuildTargetIfBuildWasForced(boolean buildWasForced) { 240 String target = null; 241 if (buildWasForced) { 242 target = buildTarget; 243 buildTarget = null; 244 } 245 return target; 246 } 247 248 private void resetBuildForcedOnlyIfBuildWasForced(boolean buildWasForced) { 249 if (buildWasForced) { 250 buildForced = false; 251 } 252 } 253 254 void setBuildStartTime(Date date) { 255 buildStartTime = date; 256 } 257 258 public void run() { 259 LOG.info("Project " + name + " started"); 260 try { 261 while (!stopped) { 262 try { 263 waitIfPaused(); 264 waitForNextBuild(); 265 if (!stopped) { 266 setState(ProjectState.QUEUED); 267 synchronized (scheduleMutex) { 268 queue.requestBuild(this); 269 waitForBuildToFinish(); 270 } 271 } 272 } catch (InterruptedException e) { 273 String message = "Project " + name + ".run() interrupted"; 274 LOG.error(message, e); 275 throw new RuntimeException (message); 276 } 277 } 278 } finally { 279 stopped = true; 280 LOG.info("Project " + name + " stopped"); 281 } 282 } 283 284 void waitIfPaused() throws InterruptedException { 285 synchronized (pausedMutex) { 286 while (isPaused) { 287 setState(ProjectState.PAUSED); 288 pausedMutex.wait(10 * DateUtil.ONE_MINUTE); 289 } 290 } 291 } 292 293 void waitForNextBuild() throws InterruptedException { 294 long waitTime = getTimeToNextBuild(new Date ()); 295 if (needToWaitForNextBuild(waitTime) && !buildForced) { 296 info("next build in " + DateUtil.formatTime(waitTime)); 297 synchronized (waitMutex) { 298 setState(ProjectState.WAITING); 299 waitMutex.wait(waitTime); 300 } 301 } 302 } 303 304 long getTimeToNextBuild(Date now) { 305 long waitTime = projectConfig.getSchedule().getTimeToNextBuild(now, getBuildInterval()); 306 if (waitTime == 0) { 307 if (buildStartTime != null) { 310 long millisSinceLastBuild = now.getTime() - buildStartTime.getTime(); 311 if (millisSinceLastBuild < Schedule.ONE_MINUTE) { 312 debug("build finished within a minute, getting new time to next build"); 313 Date oneMinuteInFuture = new Date (now.getTime() + Schedule.ONE_MINUTE); 314 waitTime = projectConfig.getSchedule().getTimeToNextBuild(oneMinuteInFuture, getBuildInterval()); 315 waitTime += Schedule.ONE_MINUTE; 316 } 317 } 318 } 319 return waitTime; 320 } 321 322 static boolean needToWaitForNextBuild(long waitTime) { 323 return waitTime > 0; 324 } 325 326 void forceBuild() { 327 synchronized (waitMutex) { 328 waitMutex.notify(); 329 } 330 } 331 332 public void forceBuildWithTarget(String buildTarget) { 333 this.buildTarget = buildTarget; 334 setBuildForced(true); 335 } 336 337 void waitForBuildToFinish() throws InterruptedException { 338 synchronized (scheduleMutex) { 339 debug("waiting for build to finish"); 340 scheduleMutex.wait(); 341 } 342 } 343 344 void buildFinished() { 345 synchronized (scheduleMutex) { 346 debug("build finished"); 347 scheduleMutex.notify(); 348 } 349 } 350 351 354 Element getModifications(boolean buildWasForced) { 355 setState(ProjectState.MODIFICATIONSET); 356 Element modifications; 357 358 ModificationSet modificationSet = projectConfig.getModificationSet(); 359 if (modificationSet == null) { 360 debug("no modification set, nothing to detect."); 361 if (buildWasForced) { 362 info("no modification set but build was forced"); 363 return new Element("modifications"); 364 } 365 return null; 366 } 367 368 boolean checkNewChangesFirst = checkOnlySinceLastBuild(); 369 if (checkNewChangesFirst) { 370 debug("getting changes since last build"); 371 modifications = modificationSet.getModifications(lastBuild); 372 } else { 373 debug("getting changes since last successful build"); 374 modifications = modificationSet.getModifications(lastSuccessfulBuild); 375 } 376 377 if (!modificationSet.isModified()) { 378 info("No modifications found, build not necessary."); 379 380 if (buildAfterFailed && !wasLastBuildSuccessful) { 384 info("Building anyway, since buildAfterFailed is true and last build failed."); 385 } else { 386 if (buildWasForced) { 387 info("Building anyway, since build was explicitly forced."); 388 } else { 389 return null; 390 } 391 } 392 } 393 394 if (checkNewChangesFirst) { 395 debug("new changes found; now getting complete set"); 396 modifications = modificationSet.getModifications(lastSuccessfulBuild); 397 } 398 399 return modifications; 400 } 401 402 405 boolean checkOnlySinceLastBuild() { 406 if (lastBuild == null || lastSuccessfulBuild == null) { 407 return false; 408 } 409 410 long lastBuildLong = lastBuild.getTime(); 411 long timeDifference = lastBuildLong - lastSuccessfulBuild.getTime(); 412 boolean moreThanASecond = timeDifference > DateUtil.ONE_SECOND; 413 414 return !buildAfterFailed && moreThanASecond; 415 } 416 417 420 public void serializeProject() { 421 ObjectOutputStream s = null; 422 try { 423 s = new ObjectOutputStream (new FileOutputStream (name + ".ser")); 424 s.writeObject(this); 425 s.flush(); 426 debug("Serializing project to [" + name + ".ser]"); 427 } catch (Exception e) { 428 LOG.warn("Error serializing project to [" + name + ".ser]: " 429 + e.getMessage(), e); 430 } finally { 431 if (s != null) { 432 try { 433 s.close(); 434 } catch (Exception ignore) { 435 } 436 } 437 } 438 } 439 440 public void setLabelIncrementer(LabelIncrementer incrementer) throws CruiseControlException { 441 if (incrementer == null) { 442 throw new IllegalArgumentException ("label incrementer can't be null"); 443 } 444 labelIncrementer = incrementer; 445 if (label == null) { 446 label = labelIncrementer.getDefaultLabel(); 447 } 448 validateLabel(label, labelIncrementer); 449 } 450 451 public LabelIncrementer getLabelIncrementer() { 452 return labelIncrementer; 453 } 454 455 456 public void setLogXmlEncoding(String encoding) { 457 projectConfig.getLog().setEncoding(encoding); 458 } 459 460 public void setName(String projectName) { 461 name = projectName; 462 } 463 464 public String getName() { 465 return name; 466 } 467 468 public void setLabel(String newLabel) { 469 label = newLabel; 470 } 471 472 public String getLabel() { 473 return label; 474 } 475 476 482 public void setLastBuild(String newLastBuild) throws CruiseControlException { 483 lastBuild = DateUtil.parseFormattedTime(newLastBuild, "lastBuild"); 484 } 485 486 492 public void setLastSuccessfulBuild(String newLastSuccessfulBuild) 493 throws CruiseControlException { 494 lastSuccessfulBuild = DateUtil.parseFormattedTime(newLastSuccessfulBuild, "lastSuccessfulBuild"); 495 } 496 497 public String getLastBuild() { 498 if (lastBuild == null) { 499 return null; 500 } 501 return DateUtil.getFormattedTime(lastBuild); 502 } 503 504 public boolean getBuildForced() { 505 return buildForced; 506 } 507 508 public void setBuildForced(boolean forceNewBuildNow) { 509 buildForced = forceNewBuildNow; 510 if (forceNewBuildNow) { 511 forceBuild(); 512 } 513 } 514 515 public String getLastSuccessfulBuild() { 516 if (lastSuccessfulBuild == null) { 517 return null; 518 } 519 return DateUtil.getFormattedTime(lastSuccessfulBuild); 520 } 521 522 public String getLogDir() { 523 return projectConfig.getLog().getLogDir(); 524 } 525 526 533 public long getBuildInterval() { 534 if (overrideBuildInterval == null) { 535 return projectConfig.getSchedule().getInterval(); 536 } else { 537 return overrideBuildInterval.longValue(); 538 } 539 } 540 541 545 public void overrideBuildInterval(long sleepMillis) { 546 overrideBuildInterval = new Long (sleepMillis); 547 } 548 549 public boolean isPaused() { 550 return isPaused; 551 } 552 553 public void setPaused(boolean paused) { 554 synchronized (pausedMutex) { 555 if (isPaused && !paused) { 556 pausedMutex.notifyAll(); 557 } 558 isPaused = paused; 559 } 560 } 561 562 public void setBuildAfterFailed(boolean rebuildEvenWithNoNewModifications) { 563 buildAfterFailed = rebuildEvenWithNoNewModifications; 564 } 565 566 public String getStatus() { 567 return getState().getDescription(); 568 } 569 570 public String getStatusWithQueuePosition() { 571 if (ProjectState.QUEUED.equals(getState())) { 572 return getState().getDescription() + " - " + queue.findPosition(this); 573 } else { 574 return getState().getDescription(); 575 } 576 } 577 578 public ProjectState getState() { 579 return state; 580 } 581 582 private void setState(ProjectState newState) { 583 state = newState; 584 info(getStatus()); 585 notifyListeners(new ProjectStateChangedEvent(name, getState())); 586 fireProgressEvent(new BuildProgressEvent(this, getState())); 587 } 588 589 public void setBuildQueue(BuildQueue buildQueue) { 590 queue = buildQueue; 591 } 592 593 public String getBuildStartTime() { 594 return DateUtil.getFormattedTime(buildStartTime); 595 } 596 597 public Log getLog() { 598 return this.projectConfig.getLog(); 599 } 600 601 604 protected void init() throws CruiseControlException { 605 if (projectConfig == null) { 606 throw new IllegalStateException ("projectConfig must be set on project before calling init()"); 607 } 608 609 buildAfterFailed = projectConfig.shouldBuildAfterFailed(); 610 611 if (lastBuild == null) { 612 lastBuild = DateUtil.getMidnight(); 613 } 614 615 if (lastSuccessfulBuild == null) { 616 lastSuccessfulBuild = lastBuild; 617 } 618 619 setDateFormat(projectConfig.getDateFormat()); 621 622 if (LOG.isDebugEnabled()) { 623 debug("buildInterval = [" + getBuildInterval() + "]"); 624 debug("buildForced = [" + buildForced + "]"); 625 debug("buildAfterFailed = [" + buildAfterFailed + "]"); 626 debug("buildCounter = [" + buildCounter + "]"); 627 debug("isPaused = [" + isPaused + "]"); 628 debug("label = [" + label + "]"); 629 debug("lastBuild = [" + DateUtil.getFormattedTime(lastBuild) + "]"); 630 debug("lastSuccessfulBuild = [" + DateUtil.getFormattedTime(lastSuccessfulBuild) + "]"); 631 debug("logDir = [" + projectConfig.getLog().getLogDir() + "]"); 632 debug("logXmlEncoding = [" + projectConfig.getLog().getLogXmlEncoding() + "]"); 633 debug("wasLastBuildSuccessful = [" + wasLastBuildSuccessful + "]"); 634 } 635 } 636 637 private void setDateFormat(CCDateFormat dateFormat) { 638 if (dateFormat != null && dateFormat.getFormat() != null) { 639 DateFormatFactory.setFormat(dateFormat.getFormat()); 640 } 641 } 642 643 protected Element getProjectPropertiesElement(Date now) { 644 Element infoElement = new Element("info"); 645 addProperty(infoElement, "projectname", name); 646 String lastBuildString = DateUtil.getFormattedTime(lastBuild == null ? now : lastBuild); 647 addProperty(infoElement, "lastbuild", lastBuildString); 648 String lastSuccessfulBuildString = 649 DateUtil.getFormattedTime(lastSuccessfulBuild == null ? now : lastSuccessfulBuild); 650 addProperty(infoElement, "lastsuccessfulbuild", lastSuccessfulBuildString); 651 addProperty(infoElement, "builddate", DateFormatFactory.getDateFormat().format(now)); 652 if (now != null) { 653 addProperty(infoElement, "cctimestamp", DateUtil.getFormattedTime(now)); 654 } 655 addProperty(infoElement, "label", label); 656 addProperty(infoElement, "interval", Long.toString(getBuildInterval() / 1000L)); 657 addProperty(infoElement, "lastbuildsuccessful", String.valueOf(wasLastBuildSuccessful)); 658 659 return infoElement; 660 } 661 662 private void addProperty(Element parent, String key, String value) { 663 Element propertyElement = new Element("property"); 664 propertyElement.setAttribute("name", key); 665 propertyElement.setAttribute("value", value); 666 parent.addContent(propertyElement); 667 } 668 669 protected Map getProjectPropertiesMap(Date now) { 670 Map buildProperties = new HashMap (); 671 buildProperties.put("label", label); 672 buildProperties.put("cvstimestamp", DateUtil.formatCVSDate(now)); 673 buildProperties.put("cctimestamp", DateUtil.getFormattedTime(now)); 674 buildProperties.put("cclastgoodbuildtimestamp", getLastSuccessfulBuild()); 675 buildProperties.put("cclastbuildtimestamp", getLastBuild()); 676 buildProperties.put("lastbuildsuccessful", String.valueOf(isLastBuildSuccessful())); 677 if (projectConfig.getModificationSet() != null) { 678 buildProperties.putAll(projectConfig.getModificationSet().getProperties()); 679 } 680 return buildProperties; 681 } 682 683 687 protected void publish() throws CruiseControlException { 688 setState(ProjectState.PUBLISHING); 689 for (Iterator i = projectConfig.getPublishers().iterator(); i.hasNext(); ) { 690 Publisher publisher = (Publisher) i.next(); 691 try { 693 publisher.publish(projectConfig.getLog().getContent()); 694 } catch (Throwable t) { 695 StringBuffer message = new StringBuffer ("exception publishing results"); 696 message.append(" with ").append(publisher.getClass().getName()); 697 message.append(" for project ").append(name); 698 LOG.error(message.toString(), t); 699 } 700 } 701 } 702 703 707 protected void bootstrap() throws CruiseControlException { 708 setState(ProjectState.BOOTSTRAPPING); 709 for (Iterator i = projectConfig.getBootstrappers().iterator(); i.hasNext(); ) { 710 ((Bootstrapper) i.next()).bootstrap(); 711 } 712 } 713 714 721 protected void validateLabel(String oldLabel, LabelIncrementer incrementer) 722 throws CruiseControlException { 723 if (!incrementer.isValidLabel(oldLabel)) { 724 final String message = oldLabel + " is not a valid label for labelIncrementer " 725 + incrementer.getClass().getName(); 726 debug(message); 727 throw new CruiseControlException(message); 728 } 729 } 730 731 public boolean isLastBuildSuccessful() { 732 return wasLastBuildSuccessful; 733 } 734 735 void setWasLastBuildSuccessful(boolean buildSuccessful) { 736 wasLastBuildSuccessful = buildSuccessful; 737 } 738 739 743 private void info(String message) { 744 LOG.info("Project " + name + ": " + message); 745 } 746 747 private void debug(String message) { 748 LOG.debug("Project " + name + ": " + message); 749 } 750 751 public void start() { 752 if (stopped || getState() == ProjectState.STOPPED) { 753 stopped = false; 754 LOG.info("Project " + name + " starting"); 755 setState(ProjectState.IDLE); 756 createNewSchedulingThread(); 757 } 758 } 759 760 protected void createNewSchedulingThread() { 761 Thread projectSchedulingThread = new Thread (this, "Project " + getName() + " thread"); 762 projectSchedulingThread.start(); 763 764 try { 766 Thread.sleep(100); 767 } catch (InterruptedException ie) { 768 LOG.warn("interrupted while waiting for scheduling thread to start", ie); 769 } 770 } 771 772 public void stop() { 773 LOG.info("Project " + name + " stopping"); 774 stopped = true; 775 setState(ProjectState.STOPPED); 776 } 777 778 public String toString() { 779 StringBuffer sb = new StringBuffer ("Project "); 780 sb.append(getName()); 781 sb.append(": "); 782 sb.append(getStatus()); 783 if (isPaused) { 784 sb.append(" (paused)"); 785 } 786 return sb.toString(); 787 } 788 789 public void addBuildProgressListener(BuildProgressListener listener) { 790 synchronized (progressListeners) { 791 progressListeners.add(listener); 792 } 793 } 794 795 protected void fireProgressEvent(BuildProgressEvent event) { 796 synchronized (progressListeners) { 797 for (Iterator i = progressListeners.iterator(); i.hasNext();) { 798 BuildProgressListener listener = (BuildProgressListener) i.next(); 799 listener.handleBuildProgress(event); 800 } 801 } 802 } 803 804 public void addBuildResultListener(BuildResultListener listener) { 805 synchronized (resultListeners) { 806 resultListeners.add(listener); 807 } 808 } 809 810 protected void fireResultEvent(BuildResultEvent event) { 811 synchronized (resultListeners) { 812 for (Iterator i = resultListeners.iterator(); i.hasNext();) { 813 BuildResultListener listener = (BuildResultListener) i.next(); 814 listener.handleBuildResult(event); 815 } 816 } 817 } 818 819 List getListeners() { 820 return projectConfig.getListeners(); 821 } 822 823 public void setProjectConfig(ProjectConfig projectConfig) throws CruiseControlException { 824 if (projectConfig == null) { 825 throw new IllegalArgumentException ("project config can't be null"); 826 } 827 this.projectConfig = projectConfig; 828 setLabelIncrementer(projectConfig.getLabelIncrementer()); 829 } 830 831 void notifyListeners(ProjectEvent event) { 832 if (projectConfig == null) { 833 throw new IllegalStateException ("projectConfig is null"); 834 } 835 836 for (Iterator i = projectConfig.getListeners().iterator(); i.hasNext(); ) { 837 Listener listener = (Listener) i.next(); 838 try { 839 listener.handleEvent(event); 840 } catch (CruiseControlException e) { 841 StringBuffer message = new StringBuffer ("exception notifying listener "); 842 message.append(listener.getClass().getName()); 843 message.append(" for project ").append(name); 844 LOG.error(message.toString(), e); 845 } 846 } 847 } 848 849 public boolean equals(Object arg0) { 850 if (arg0 == null) { 851 return false; 852 } 853 854 if (arg0.getClass().getName().equals(getClass().getName())) { 855 Project thatProject = (Project) arg0; 856 return thatProject.name.equals(name); 857 } 858 859 return false; 860 } 861 862 public int hashCode() { 863 return name.hashCode(); 864 } 865 866 } 867 | Popular Tags |