0001: /********************************************************************************
0002: * CruiseControl, a Continuous Integration Toolkit
0003: * Copyright (c) 2001-2003, ThoughtWorks, Inc.
0004: * 200 E. Randolph, 25th Floor
0005: * Chicago, IL 60601 USA
0006: * All rights reserved.
0007: *
0008: * Redistribution and use in source and binary forms, with or without
0009: * modification, are permitted provided that the following conditions
0010: * are met:
0011: *
0012: * + Redistributions of source code must retain the above copyright
0013: * notice, this list of conditions and the following disclaimer.
0014: *
0015: * + Redistributions in binary form must reproduce the above
0016: * copyright notice, this list of conditions and the following
0017: * disclaimer in the documentation and/or other materials provided
0018: * with the distribution.
0019: *
0020: * + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the
0021: * names of its contributors may be used to endorse or promote
0022: * products derived from this software without specific prior
0023: * written permission.
0024: *
0025: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
0026: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
0027: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
0028: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
0029: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0030: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
0031: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
0032: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
0033: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
0034: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
0035: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0036: ********************************************************************************/package net.sourceforge.cruisecontrol;
0037:
0038: import java.io.FileOutputStream;
0039: import java.io.IOException;
0040: import java.io.ObjectInputStream;
0041: import java.io.ObjectOutputStream;
0042: import java.io.Serializable;
0043: import java.util.ArrayList;
0044: import java.util.Date;
0045: import java.util.HashMap;
0046: import java.util.Iterator;
0047: import java.util.List;
0048: import java.util.Map;
0049:
0050: import javax.management.JMException;
0051: import javax.management.MBeanServer;
0052:
0053: import net.sourceforge.cruisecontrol.events.BuildProgressEvent;
0054: import net.sourceforge.cruisecontrol.events.BuildProgressListener;
0055: import net.sourceforge.cruisecontrol.events.BuildResultEvent;
0056: import net.sourceforge.cruisecontrol.events.BuildResultListener;
0057: import net.sourceforge.cruisecontrol.jmx.ProjectController;
0058: import net.sourceforge.cruisecontrol.listeners.ProjectStateChangedEvent;
0059: import net.sourceforge.cruisecontrol.util.CVSDateUtil;
0060: import net.sourceforge.cruisecontrol.util.DateUtil;
0061: import net.sourceforge.cruisecontrol.util.IO;
0062:
0063: import org.apache.log4j.Logger;
0064: import org.jdom.Element;
0065:
0066: /**
0067: * Represents a single logical project consisting of source code that needs to
0068: * be built. Project is associated with bootstrappers that run before builds
0069: * and a Schedule that determines when builds occur.
0070: */
0071: public class Project implements Serializable, Runnable {
0072: static final long serialVersionUID = 2656877748476842326L;
0073: private static final Logger LOG = Logger.getLogger(Project.class);
0074:
0075: private transient ProjectState state;
0076:
0077: private transient ProjectConfig projectConfig;
0078: private transient LabelIncrementer labelIncrementer;
0079:
0080: /**
0081: * If this attribute is set, then it means that the user has overridden
0082: * the build interval specified in the Schedule element, probably
0083: * using the JMX interface.
0084: */
0085: private transient Long overrideBuildInterval;
0086:
0087: private transient Date buildStartTime;
0088: private transient Object pausedMutex;
0089: private transient Object scheduleMutex;
0090: private transient Object waitMutex;
0091: private transient BuildQueue queue;
0092: private transient List progressListeners;
0093: private transient List resultListeners;
0094: private transient Progress progress;
0095:
0096: private int buildCounter = 0;
0097: private Date lastBuild = DateUtil.getMidnight();
0098: private Date lastSuccessfulBuild = lastBuild;
0099: private boolean wasLastBuildSuccessful = true;
0100: private String label;
0101: private String name;
0102: private transient boolean buildForced = false;
0103: private String buildTarget = null;
0104: private boolean isPaused = false;
0105: private boolean buildAfterFailed = true;
0106: private boolean stopped = true;
0107: private boolean forceOnly = false;
0108: private boolean requiremodification = true;
0109:
0110: public Project() {
0111: initializeTransientFields();
0112: }
0113:
0114: private void initializeTransientFields() {
0115: state = ProjectState.STOPPED;
0116:
0117: pausedMutex = new Object();
0118: scheduleMutex = new Object();
0119: waitMutex = new Object();
0120: progressListeners = new ArrayList();
0121: resultListeners = new ArrayList();
0122:
0123: progress = new ProgressImpl(this );
0124: }
0125:
0126: private void readObject(ObjectInputStream stream)
0127: throws IOException, ClassNotFoundException {
0128: stream.defaultReadObject();
0129: initializeTransientFields();
0130: }
0131:
0132: public void execute() {
0133: if (stopped) {
0134: LOG.warn("not building project " + name
0135: + " because project has been stopped.");
0136: buildFinished();
0137: return;
0138: }
0139:
0140: synchronized (pausedMutex) {
0141: if (isPaused) {
0142: LOG.info("not building project " + name
0143: + " because project has been paused.");
0144: buildFinished();
0145: return;
0146: }
0147: }
0148:
0149: try {
0150: init();
0151: build();
0152: } catch (CruiseControlException e) {
0153: LOG.error("exception attempting build in project " + name,
0154: e);
0155: } finally {
0156: buildFinished();
0157: }
0158: }
0159:
0160: /**
0161: * Unless paused, runs any bootstrappers and then the entire build.
0162: * @throws CruiseControlException if an error occurs during the build
0163: */
0164: protected void build() throws CruiseControlException {
0165: if (projectConfig == null) {
0166: throw new IllegalStateException(
0167: "projectConfig must be set on project before calling build()");
0168: }
0169:
0170: if (stopped) {
0171: LOG.warn("not building project " + name
0172: + " because project has been stopped.");
0173: return;
0174: }
0175:
0176: // If the force only flag is set, only build if forced
0177: // or if the last build faild and we want to build on failures
0178: if (forceOnly && !buildForced
0179: && !(!wasLastBuildSuccessful && buildAfterFailed)) {
0180: info("not building because project is forceOnly and build not forced.");
0181: return;
0182: }
0183:
0184: boolean buildWasForced = buildForced;
0185:
0186: try {
0187: setBuildStartTime(new Date());
0188: Schedule schedule = projectConfig.getSchedule();
0189: if (schedule == null) {
0190: throw new IllegalStateException(
0191: "project must have a schedule");
0192: }
0193: if (schedule.isPaused(buildStartTime)) {
0194: // a regularly scheduled paused
0195: // is different than ProjectState.PAUSED
0196: return;
0197: }
0198:
0199: // @todo Add Progress param to Bootstrapper API?
0200: bootstrap();
0201:
0202: String target = useAndResetBuildTargetIfBuildWasForced(buildWasForced);
0203:
0204: // @todo Add Progress param to ModificationSet API?
0205: // getModifications will only return null if we don't need to build
0206: Element modifications = getModifications(buildWasForced);
0207:
0208: if (modifications == null) {
0209: return;
0210: }
0211:
0212: // Using local reference to avoid NPE if config.xml is updated during build
0213: Log buildLog = projectConfig.getLog();
0214:
0215: buildLog.addContent(modifications);
0216:
0217: Date now = new Date();
0218: if (projectConfig.getModificationSet() != null
0219: && projectConfig.getModificationSet()
0220: .getTimeOfCheck() != null) {
0221:
0222: now = projectConfig.getModificationSet()
0223: .getTimeOfCheck();
0224: }
0225:
0226: if (getLabelIncrementer().isPreBuildIncrementer()) {
0227: label = getLabelIncrementer().incrementLabel(label,
0228: buildLog.getContent());
0229: }
0230:
0231: // collect project information
0232: buildLog.addContent(getProjectPropertiesElement(now));
0233:
0234: setState(ProjectState.BUILDING);
0235: Element builderLog = schedule.build(buildCounter,
0236: lastBuild, now, getProjectPropertiesMap(now),
0237: target, progress);
0238:
0239: buildLog.addContent(builderLog.detach());
0240:
0241: boolean buildSuccessful = buildLog.wasBuildSuccessful();
0242: fireResultEvent(new BuildResultEvent(this , buildSuccessful));
0243:
0244: if (!getLabelIncrementer().isPreBuildIncrementer()
0245: && buildSuccessful) {
0246: label = getLabelIncrementer().incrementLabel(label,
0247: buildLog.getContent());
0248: }
0249:
0250: setState(ProjectState.MERGING_LOGS);
0251: buildLog.writeLogFile(now);
0252:
0253: // If we only want to build after a check in, even when broken, set the last build to now,
0254: // regardless of success or failure (buildAfterFailed = false in config.xml)
0255: if (!buildAfterFailed) {
0256: lastBuild = now;
0257: }
0258:
0259: // If this was a successful build, update both last build and last successful build
0260: if (buildSuccessful) {
0261: lastBuild = now;
0262: lastSuccessfulBuild = now;
0263: info("build successful");
0264: } else {
0265: info("build failed");
0266: }
0267:
0268: buildCounter++;
0269: setWasLastBuildSuccessful(buildSuccessful);
0270:
0271: // also need to reset forced flag before serializing, unless buildForced var is transient
0272: //resetBuildForcedOnlyIfBuildWasForced(buildWasForced);
0273: serializeProject();
0274:
0275: // @todo Add Progress param to Publisher API?
0276: publish(buildLog);
0277: buildLog.reset();
0278: } finally {
0279: resetBuildForcedOnlyIfBuildWasForced(buildWasForced);
0280: setState(ProjectState.IDLE);
0281: }
0282: }
0283:
0284: private String useAndResetBuildTargetIfBuildWasForced(
0285: boolean buildWasForced) {
0286: String target = null;
0287: if (buildWasForced) {
0288: target = buildTarget;
0289: buildTarget = null;
0290: }
0291: return target;
0292: }
0293:
0294: private void resetBuildForcedOnlyIfBuildWasForced(
0295: boolean buildWasForced) {
0296: if (buildWasForced) {
0297: buildForced = false;
0298: }
0299: }
0300:
0301: void setBuildStartTime(Date date) {
0302: buildStartTime = date;
0303: }
0304:
0305: public void run() {
0306: LOG.info("Project " + name + " started");
0307: try {
0308: while (!stopped) {
0309: try {
0310: waitIfPaused();
0311: if (!stopped) {
0312: waitForNextBuild();
0313: }
0314: if (!stopped) {
0315: setState(ProjectState.QUEUED);
0316: synchronized (scheduleMutex) {
0317: queue.requestBuild(projectConfig);
0318: waitForBuildToFinish();
0319: }
0320: }
0321: } catch (InterruptedException e) {
0322: String message = "Project " + name
0323: + ".run() interrupted";
0324: LOG.error(message, e);
0325: throw new RuntimeException(message);
0326: }
0327: }
0328: } finally {
0329: stopped = true;
0330: LOG.info("Project " + name + " stopped");
0331: }
0332: }
0333:
0334: void waitIfPaused() throws InterruptedException {
0335: synchronized (pausedMutex) {
0336: while (isPaused) {
0337: setState(ProjectState.PAUSED);
0338: pausedMutex.wait(10 * DateUtil.ONE_MINUTE);
0339: }
0340: }
0341: }
0342:
0343: void waitForNextBuild() throws InterruptedException {
0344: long waitTime = getTimeToNextBuild(new Date());
0345: if (needToWaitForNextBuild(waitTime) && !buildForced) {
0346: final String msg = "next build in "
0347: + DateUtil.formatTime(waitTime);
0348: info(msg);
0349: synchronized (waitMutex) {
0350: setState(ProjectState.WAITING);
0351: progress.setValue(msg);
0352: waitMutex.wait(waitTime);
0353: }
0354: }
0355: }
0356:
0357: long getTimeToNextBuild(Date now) {
0358: long waitTime = projectConfig.getSchedule().getTimeToNextBuild(
0359: now, getBuildInterval());
0360: if (waitTime == 0) {
0361: // check for the exceptional case that we're dealing with a
0362: // project that has just built within a minute time
0363: if (buildStartTime != null) {
0364: long millisSinceLastBuild = now.getTime()
0365: - buildStartTime.getTime();
0366: if (millisSinceLastBuild < Schedule.ONE_MINUTE) {
0367: debug("build finished within a minute, getting new time to next build");
0368: Date oneMinuteInFuture = new Date(now.getTime()
0369: + Schedule.ONE_MINUTE);
0370: waitTime = projectConfig.getSchedule()
0371: .getTimeToNextBuild(oneMinuteInFuture,
0372: getBuildInterval());
0373: waitTime += Schedule.ONE_MINUTE;
0374: }
0375: }
0376: }
0377: return waitTime;
0378: }
0379:
0380: static boolean needToWaitForNextBuild(long waitTime) {
0381: return waitTime > 0;
0382: }
0383:
0384: /** @return true if build was forced, intended for unit testing only. */
0385: boolean isBuildForced() {
0386: return buildForced;
0387: }
0388:
0389: void forceBuild() {
0390: synchronized (waitMutex) {
0391: waitMutex.notify();
0392: }
0393: }
0394:
0395: public void forceBuildWithTarget(String buildTarget) {
0396: this .buildTarget = buildTarget;
0397: setBuildForced(true);
0398: }
0399:
0400: void waitForBuildToFinish() throws InterruptedException {
0401: synchronized (scheduleMutex) {
0402: debug("waiting for build to finish");
0403: scheduleMutex.wait();
0404: }
0405: }
0406:
0407: void buildFinished() {
0408: synchronized (scheduleMutex) {
0409: debug("build finished");
0410: scheduleMutex.notify();
0411: }
0412: }
0413:
0414: /**
0415: * Return modifications since the last build. timeOfCheck will be updated according to the last modification to
0416: * account for time synchronisation issues.
0417: *
0418: * @param buildWasForced true if the build was forced
0419: * @return Element jdom element containing modification information
0420: */
0421: Element getModifications(final boolean buildWasForced) {
0422: setState(ProjectState.MODIFICATIONSET);
0423: Element modifications;
0424:
0425: ModificationSet modificationSet = projectConfig
0426: .getModificationSet();
0427: if (modificationSet == null) {
0428: debug("no modification set, nothing to detect.");
0429: if (buildWasForced) {
0430: info("no modification set but build was forced");
0431: return new Element("modifications");
0432: }
0433: if (!requiremodification) {
0434: info("no modification set but no modifications required");
0435: return new Element("modifications");
0436: }
0437: return null;
0438: }
0439:
0440: boolean checkNewChangesFirst = checkOnlySinceLastBuild();
0441: if (checkNewChangesFirst) {
0442: debug("getting changes since last build");
0443: modifications = modificationSet
0444: .retrieveModificationsAsElement(lastBuild, progress);
0445: } else {
0446: debug("getting changes since last successful build");
0447: modifications = modificationSet
0448: .retrieveModificationsAsElement(
0449: lastSuccessfulBuild, progress);
0450: }
0451:
0452: if (!modificationSet.isModified()) {
0453: info("No modifications found, build not necessary.");
0454:
0455: // Sometimes we want to build even though we don't have any
0456: // modifications:
0457: // * last build failed & buildaferfailed="true"
0458: // * requiremodifications="false"
0459: // * build forced
0460: if (buildAfterFailed && !wasLastBuildSuccessful) {
0461: info("Building anyway, since buildAfterFailed is true and last build failed.");
0462: } else if (!requiremodification) {
0463: info("Building anyway, since modifications not required");
0464: } else {
0465: if (buildWasForced) {
0466: info("Building anyway, since build was explicitly forced.");
0467: } else {
0468: return null;
0469: }
0470: }
0471: }
0472:
0473: if (checkNewChangesFirst) {
0474: debug("new changes found; now getting complete set");
0475: modifications = modificationSet
0476: .retrieveModificationsAsElement(
0477: lastSuccessfulBuild, progress);
0478: }
0479:
0480: return modifications;
0481: }
0482:
0483: /**
0484: * @return boolean
0485: */
0486: boolean checkOnlySinceLastBuild() {
0487: if (lastBuild == null || lastSuccessfulBuild == null) {
0488: return false;
0489: }
0490:
0491: long lastBuildLong = lastBuild.getTime();
0492: long timeDifference = lastBuildLong
0493: - lastSuccessfulBuild.getTime();
0494: boolean moreThanASecond = timeDifference > DateUtil.ONE_SECOND;
0495:
0496: return !buildAfterFailed && moreThanASecond;
0497: }
0498:
0499: /**
0500: * Serialize the project to allow resumption after a process bounce
0501: */
0502: public void serializeProject() {
0503: ObjectOutputStream s = null;
0504: try {
0505: s = new ObjectOutputStream(new FileOutputStream(name
0506: + ".ser"));
0507: s.writeObject(this );
0508: s.flush();
0509: debug("Serializing project to [" + name + ".ser]");
0510: } catch (Exception e) {
0511: LOG.warn("Error serializing project to [" + name
0512: + ".ser]: " + e.getMessage(), e);
0513: } finally {
0514: IO.close(s);
0515: }
0516: }
0517:
0518: public void setLabelIncrementer(LabelIncrementer incrementer)
0519: throws CruiseControlException {
0520: if (incrementer == null) {
0521: throw new IllegalArgumentException(
0522: "label incrementer can't be null");
0523: }
0524: labelIncrementer = incrementer;
0525: if (label == null) {
0526: label = labelIncrementer.getDefaultLabel();
0527: }
0528: validateLabel(label, labelIncrementer);
0529: }
0530:
0531: public LabelIncrementer getLabelIncrementer() {
0532: return labelIncrementer;
0533: }
0534:
0535: public void setName(String projectName) {
0536: name = projectName;
0537: }
0538:
0539: public String getName() {
0540: return name;
0541: }
0542:
0543: public void setLabel(String newLabel) {
0544: label = newLabel;
0545: }
0546:
0547: public String getLabel() {
0548: return label;
0549: }
0550:
0551: /**
0552: * @param newLastBuild string containing the build date in the format
0553: * yyyyMMddHHmmss
0554: * @throws CruiseControlException if the date cannot be extracted from the
0555: * input string
0556: */
0557: public void setLastBuild(String newLastBuild)
0558: throws CruiseControlException {
0559: lastBuild = DateUtil.parseFormattedTime(newLastBuild,
0560: "lastBuild");
0561: }
0562:
0563: /**
0564: * @param newLastSuccessfulBuild string containing the build date in the format
0565: * yyyyMMddHHmmss
0566: * @throws CruiseControlException if the date cannot be extracted from the
0567: * input string
0568: */
0569: public void setLastSuccessfulBuild(String newLastSuccessfulBuild)
0570: throws CruiseControlException {
0571: lastSuccessfulBuild = DateUtil.parseFormattedTime(
0572: newLastSuccessfulBuild, "lastSuccessfulBuild");
0573: }
0574:
0575: public String getLastBuild() {
0576: if (lastBuild == null) {
0577: return null;
0578: }
0579: return DateUtil.getFormattedTime(lastBuild);
0580: }
0581:
0582: public boolean getBuildForced() {
0583: return buildForced;
0584: }
0585:
0586: public void setBuildForced(boolean forceNewBuildNow) {
0587: buildForced = forceNewBuildNow;
0588: if (forceNewBuildNow) {
0589: forceBuild();
0590: }
0591: }
0592:
0593: public String getLastSuccessfulBuild() {
0594: if (lastSuccessfulBuild == null) {
0595: return null;
0596: }
0597: return DateUtil.getFormattedTime(lastSuccessfulBuild);
0598: }
0599:
0600: public String getLogDir() {
0601: return projectConfig.getLog().getLogDir();
0602: }
0603:
0604: /**
0605: * Returns the build interval. This value is initially specified on the
0606: * schedule, but the user may override that value using the JMX interface.
0607: * If the user hasn't override the Schedule, then this method will
0608: * return the Schedule's interval, otherwise the overridden value will
0609: * be returned.
0610: * @return the build interval
0611: */
0612: public long getBuildInterval() {
0613: if (overrideBuildInterval == null) {
0614: return projectConfig.getSchedule().getInterval();
0615: } else {
0616: return overrideBuildInterval.longValue();
0617: }
0618: }
0619:
0620: /**
0621: * Sets the build interval that this Project should use. This method
0622: * overrides the value initially specified in the Schedule attribute.
0623: * @param sleepMillis the number of milliseconds to sleep between build attempts
0624: */
0625: public void overrideBuildInterval(long sleepMillis) {
0626: overrideBuildInterval = new Long(sleepMillis);
0627: }
0628:
0629: public boolean isPaused() {
0630: return isPaused;
0631: }
0632:
0633: public void setPaused(boolean paused) {
0634: synchronized (pausedMutex) {
0635: if (isPaused && !paused) {
0636: pausedMutex.notifyAll();
0637: }
0638: isPaused = paused;
0639: }
0640: }
0641:
0642: public void setBuildAfterFailed(
0643: boolean rebuildEvenWithNoNewModifications) {
0644: buildAfterFailed = rebuildEvenWithNoNewModifications;
0645: }
0646:
0647: public String getStatus() {
0648: return getState().getDescription();
0649: }
0650:
0651: public String getStatusWithQueuePosition() {
0652: if (ProjectState.QUEUED.equals(getState())) {
0653: return getState().getDescription() + " - "
0654: + queue.findPosition(projectConfig);
0655: } else {
0656: return getState().getDescription();
0657: }
0658: }
0659:
0660: public ProjectState getState() {
0661: return state;
0662: }
0663:
0664: private void setState(ProjectState newState) {
0665: state = newState;
0666: info(getStatus());
0667: notifyListeners(new ProjectStateChangedEvent(name, getState()));
0668: fireProgressEvent(new BuildProgressEvent(this , getState()));
0669: }
0670:
0671: public void setBuildQueue(BuildQueue buildQueue) {
0672: queue = buildQueue;
0673: }
0674:
0675: public String getBuildStartTime() {
0676: return DateUtil.getFormattedTime(buildStartTime);
0677: }
0678:
0679: public Log getLog() {
0680: return this .projectConfig.getLog();
0681: }
0682:
0683: /**
0684: * Initialize the project. Uses ProjectXMLHelper to parse a project file.
0685: */
0686: protected void init() {
0687: if (projectConfig == null) {
0688: throw new IllegalStateException(
0689: "projectConfig must be set on project before calling init()");
0690: }
0691:
0692: buildAfterFailed = projectConfig.shouldBuildAfterFailed();
0693: forceOnly = projectConfig.isForceOnly();
0694: requiremodification = projectConfig.isRequiremodification();
0695:
0696: if (lastBuild == null) {
0697: lastBuild = DateUtil.getMidnight();
0698: }
0699:
0700: if (lastSuccessfulBuild == null) {
0701: lastSuccessfulBuild = lastBuild;
0702: }
0703:
0704: // should we move this to build? This singleton thingy is scaring me..
0705: setDateFormat(projectConfig.getDateFormat());
0706:
0707: if (LOG.isDebugEnabled()) {
0708: debug("buildInterval = [" + getBuildInterval()
0709: + "]");
0710: debug("buildForced = [" + buildForced + "]");
0711: debug("buildAfterFailed = [" + buildAfterFailed + "]");
0712: debug("requireModifcation = [" + requiremodification
0713: + "]");
0714: debug("forceOnly = [" + forceOnly + "]");
0715: debug("buildCounter = [" + buildCounter + "]");
0716: debug("isPaused = [" + isPaused + "]");
0717: debug("label = [" + label + "]");
0718: debug("lastBuild = ["
0719: + DateUtil.getFormattedTime(lastBuild) + "]");
0720: debug("lastSuccessfulBuild = ["
0721: + DateUtil.getFormattedTime(lastSuccessfulBuild)
0722: + "]");
0723: debug("logDir = ["
0724: + projectConfig.getLog().getLogDir() + "]");
0725: debug("logXmlEncoding = ["
0726: + projectConfig.getLog().getLogXmlEncoding() + "]");
0727: debug("wasLastBuildSuccessful = [" + wasLastBuildSuccessful
0728: + "]");
0729: }
0730: }
0731:
0732: private void setDateFormat(CCDateFormat dateFormat) {
0733: if (dateFormat != null && dateFormat.getFormat() != null) {
0734: DateFormatFactory.setFormat(dateFormat.getFormat());
0735: }
0736: }
0737:
0738: protected Element getProjectPropertiesElement(Date now) {
0739: Element infoElement = new Element("info");
0740: addProperty(infoElement, "projectname", name);
0741: String lastBuildString = DateUtil
0742: .getFormattedTime(lastBuild == null ? now : lastBuild);
0743: addProperty(infoElement, "lastbuild", lastBuildString);
0744: String lastSuccessfulBuildString = DateUtil
0745: .getFormattedTime(lastSuccessfulBuild == null ? now
0746: : lastSuccessfulBuild);
0747: addProperty(infoElement, "lastsuccessfulbuild",
0748: lastSuccessfulBuildString);
0749: addProperty(infoElement, "builddate", DateFormatFactory
0750: .getDateFormat().format(now));
0751: addProperty(infoElement, "cctimestamp", DateUtil
0752: .getFormattedTime(now));
0753: addProperty(infoElement, "label", label);
0754: addProperty(infoElement, "interval", Long
0755: .toString(getBuildInterval() / 1000L));
0756: addProperty(infoElement, "lastbuildsuccessful", String
0757: .valueOf(wasLastBuildSuccessful));
0758:
0759: return infoElement;
0760: }
0761:
0762: private void addProperty(Element parent, String key, String value) {
0763: Element propertyElement = new Element("property");
0764: propertyElement.setAttribute("name", key);
0765: propertyElement.setAttribute("value", value);
0766: parent.addContent(propertyElement);
0767: }
0768:
0769: protected Map getProjectPropertiesMap(Date now) {
0770: Map buildProperties = new HashMap();
0771:
0772: buildProperties.put("projectname", name);
0773:
0774: buildProperties.put("label", label);
0775:
0776: // TODO: Shouldn't have CVS specific properties here
0777: buildProperties.put("cvstimestamp", CVSDateUtil
0778: .formatCVSDate(now));
0779:
0780: buildProperties.put("cctimestamp", DateUtil
0781: .getFormattedTime(now));
0782: buildProperties.put("cclastgoodbuildtimestamp",
0783: getLastSuccessfulBuild());
0784: buildProperties.put("cclastbuildtimestamp", getLastBuild());
0785: buildProperties.put("lastbuildsuccessful", String
0786: .valueOf(isLastBuildSuccessful()));
0787: buildProperties.put("buildforced", String
0788: .valueOf(getBuildForced()));
0789: if (projectConfig.getModificationSet() != null) {
0790: buildProperties.putAll(projectConfig.getModificationSet()
0791: .getProperties());
0792: }
0793: return buildProperties;
0794: }
0795:
0796: /**
0797: * Iterate over all of the registered <code>Publisher</code>s and call
0798: * their respective <code>publish</code> methods.
0799: * @param buildLog the content to publish
0800: * @throws CruiseControlException if an error occurs during publishing
0801: */
0802: protected void publish(Log buildLog) throws CruiseControlException {
0803: setState(ProjectState.PUBLISHING);
0804: for (Iterator i = projectConfig.getPublishers().iterator(); i
0805: .hasNext();) {
0806: Publisher publisher = (Publisher) i.next();
0807: // catch all errors, Publishers shouldn't cause failures in the build method
0808: try {
0809: publisher.publish(buildLog.getContent());
0810: } catch (Throwable t) {
0811: StringBuffer message = new StringBuffer(
0812: "exception publishing results");
0813: message.append(" with ").append(
0814: publisher.getClass().getName());
0815: message.append(" for project ").append(name);
0816: LOG.error(message.toString(), t);
0817: }
0818: }
0819: }
0820:
0821: /**
0822: * Iterate over all of the registered <code>Bootstrapper</code>s and call
0823: * their respective <code>bootstrap</code> methods.
0824: * @throws CruiseControlException if an error occurs during bootstrapping
0825: */
0826: protected void bootstrap() throws CruiseControlException {
0827: setState(ProjectState.BOOTSTRAPPING);
0828: for (Iterator i = projectConfig.getBootstrappers().iterator(); i
0829: .hasNext();) {
0830: ((Bootstrapper) i.next()).bootstrap();
0831: }
0832: }
0833:
0834: /**
0835: * Ensure that label is valid for the specified LabelIncrementer
0836: *
0837: * @param oldLabel target label
0838: * @param incrementer target LabelIncrementer
0839: * @throws CruiseControlException if label is not valid
0840: */
0841: protected void validateLabel(String oldLabel,
0842: LabelIncrementer incrementer) throws CruiseControlException {
0843: if (!incrementer.isValidLabel(oldLabel)) {
0844: final String message = oldLabel
0845: + " is not a valid label for labelIncrementer "
0846: + incrementer.getClass().getName();
0847: debug(message);
0848: throw new CruiseControlException(message);
0849: }
0850: }
0851:
0852: public boolean isLastBuildSuccessful() {
0853: return wasLastBuildSuccessful;
0854: }
0855:
0856: void setWasLastBuildSuccessful(boolean buildSuccessful) {
0857: wasLastBuildSuccessful = buildSuccessful;
0858: }
0859:
0860: /**
0861: * Logs a message to the application log, not to be confused with the
0862: * CruiseControl build log.
0863: * @param message the application message to log
0864: */
0865: private void info(String message) {
0866: LOG.info("Project " + name + ": " + message);
0867: }
0868:
0869: private void debug(String message) {
0870: LOG.debug("Project " + name + ": " + message);
0871: }
0872:
0873: public void start() {
0874: if (stopped || getState() == ProjectState.STOPPED) {
0875: stopped = false;
0876: LOG.info("Project " + name + " starting");
0877: setState(ProjectState.IDLE);
0878: createNewSchedulingThread();
0879: }
0880: }
0881:
0882: protected void createNewSchedulingThread() {
0883: Thread projectSchedulingThread = new Thread(this , "Project "
0884: + getName() + " thread");
0885: projectSchedulingThread.start();
0886:
0887: // brief nap to allow thread to start
0888: try {
0889: Thread.sleep(100);
0890: } catch (InterruptedException ie) {
0891: LOG
0892: .warn(
0893: "interrupted while waiting for scheduling thread to start",
0894: ie);
0895: }
0896: }
0897:
0898: public void stop() {
0899: LOG.info("Project " + name + " stopping");
0900: stopped = true;
0901: setState(ProjectState.STOPPED);
0902: synchronized (pausedMutex) {
0903: pausedMutex.notifyAll();
0904: }
0905: synchronized (waitMutex) {
0906: waitMutex.notifyAll();
0907: }
0908: synchronized (scheduleMutex) {
0909: scheduleMutex.notifyAll();
0910: }
0911: }
0912:
0913: public String toString() {
0914: StringBuffer sb = new StringBuffer("Project ");
0915: sb.append(getName());
0916: sb.append(": ");
0917: sb.append(getStatus());
0918: if (isPaused) {
0919: sb.append(" (paused)");
0920: }
0921: return sb.toString();
0922: }
0923:
0924: public void addBuildProgressListener(BuildProgressListener listener) {
0925: synchronized (progressListeners) {
0926: progressListeners.add(listener);
0927: }
0928: }
0929:
0930: protected void fireProgressEvent(BuildProgressEvent event) {
0931: synchronized (progressListeners) {
0932: for (Iterator i = progressListeners.iterator(); i.hasNext();) {
0933: BuildProgressListener listener = (BuildProgressListener) i
0934: .next();
0935: listener.handleBuildProgress(event);
0936: }
0937: }
0938: }
0939:
0940: public void addBuildResultListener(BuildResultListener listener) {
0941: synchronized (resultListeners) {
0942: resultListeners.add(listener);
0943: }
0944: }
0945:
0946: protected void fireResultEvent(BuildResultEvent event) {
0947: synchronized (resultListeners) {
0948: for (Iterator i = resultListeners.iterator(); i.hasNext();) {
0949: BuildResultListener listener = (BuildResultListener) i
0950: .next();
0951: listener.handleBuildResult(event);
0952: }
0953: }
0954: }
0955:
0956: List getListeners() {
0957: return projectConfig.getListeners();
0958: }
0959:
0960: public void setProjectConfig(ProjectConfig projectConfig)
0961: throws CruiseControlException {
0962: if (projectConfig == null) {
0963: throw new IllegalArgumentException(
0964: "project config can't be null");
0965: }
0966: this .projectConfig = projectConfig;
0967: setLabelIncrementer(projectConfig.getLabelIncrementer());
0968: }
0969:
0970: void notifyListeners(ProjectEvent event) {
0971: if (projectConfig == null) {
0972: throw new IllegalStateException("projectConfig is null");
0973: }
0974:
0975: for (Iterator i = projectConfig.getListeners().iterator(); i
0976: .hasNext();) {
0977: Listener listener = (Listener) i.next();
0978: try {
0979: listener.handleEvent(event);
0980: } catch (CruiseControlException e) {
0981: StringBuffer message = new StringBuffer(
0982: "exception notifying listener ");
0983: message.append(listener.getClass().getName());
0984: message.append(" for project ").append(name);
0985: LOG.error(message.toString(), e);
0986: }
0987: }
0988: }
0989:
0990: public boolean equals(Object arg0) {
0991: if (arg0 == null) {
0992: return false;
0993: }
0994:
0995: if (arg0.getClass().getName().equals(getClass().getName())) {
0996: Project thatProject = (Project) arg0;
0997: return thatProject.name.equals(name);
0998: }
0999:
1000: return false;
1001: }
1002:
1003: public int hashCode() {
1004: return name.hashCode();
1005: }
1006:
1007: public void register(MBeanServer server) throws JMException {
1008: LOG.debug("Registering project mbean");
1009: ProjectController projectController = new ProjectController(
1010: this );
1011: projectController.register(server);
1012: }
1013:
1014: public ProjectConfig getProjectConfig() {
1015: return projectConfig;
1016: }
1017:
1018: public Date getLastBuildDate() {
1019: return lastBuild;
1020: }
1021:
1022: public Progress getProgress() {
1023: return progress;
1024: }
1025: }
|