001: /********************************************************************************
002: * CruiseControl, a Continuous Integration Toolkit
003: * Copyright (c) 2001, ThoughtWorks, Inc.
004: * 200 E. Randolph, 25th Floor
005: * Chicago, IL 60601 USA
006: * All rights reserved.
007: *
008: * Redistribution and use in source and binary forms, with or without
009: * modification, are permitted provided that the following conditions
010: * are met:
011: *
012: * + Redistributions of source code must retain the above copyright
013: * notice, this list of conditions and the following disclaimer.
014: *
015: * + Redistributions in binary form must reproduce the above
016: * copyright notice, this list of conditions and the following
017: * disclaimer in the documentation and/or other materials provided
018: * with the distribution.
019: *
020: * + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the
021: * names of its contributors may be used to endorse or promote
022: * products derived from this software without specific prior
023: * written permission.
024: *
025: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
026: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
027: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
028: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
029: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
030: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
031: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
032: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
033: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
034: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
035: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
036: ********************************************************************************/package net.sourceforge.cruisecontrol.sourcecontrols;
037:
038: import java.io.File;
039: import java.io.FilenameFilter;
040: import java.io.IOException;
041: import java.util.ArrayList;
042: import java.util.Date;
043: import java.util.List;
044: import java.util.Map;
045:
046: import net.sourceforge.cruisecontrol.CruiseControlException;
047: import net.sourceforge.cruisecontrol.Log;
048: import net.sourceforge.cruisecontrol.Modification;
049: import net.sourceforge.cruisecontrol.SourceControl;
050: import net.sourceforge.cruisecontrol.util.DateUtil;
051: import net.sourceforge.cruisecontrol.util.ValidationHelper;
052: import net.sourceforge.cruisecontrol.util.XMLLogHelper;
053:
054: import org.apache.log4j.Logger;
055: import org.jdom.Document;
056: import org.jdom.Element;
057: import org.jdom.JDOMException;
058: import org.jdom.input.SAXBuilder;
059:
060: /**
061: * This class allows for starting builds based on the results of another
062: * build. It does this by examining the build's log files. Only
063: * successful builds count as modifications.
064: *
065: * @author Garrick Olson
066: */
067: public class BuildStatus implements SourceControl {
068:
069: private static final Logger LOG = Logger
070: .getLogger(BuildStatus.class);
071:
072: /** The location being checked for new log files. */
073: public static final String MOST_RECENT_LOGDIR_KEY = "most.recent.logdir";
074:
075: /** The name of the newest logfile included in the modification set
076: * (e.g. "log20040120120000L0.1.xml"). */
077: public static final String MOST_RECENT_LOGFILE_KEY = "most.recent.logfile";
078:
079: /** The timestamp of the newest build included in the modification set
080: * (e.g. "20040120120000"). */
081: public static final String MOST_RECENT_LOGTIME_KEY = "most.recent.logtime";
082:
083: /**
084: * The label of the newest build included in the modification set
085: * (e.g. "0.1").
086: */
087: public static final String MOST_RECENT_LOGLABEL_KEY = "most.recent.loglabel";
088:
089: private SourceControlProperties properties = new SourceControlProperties();
090: private String logDir;
091:
092: /**
093: * This method is used to make certain attributes of the most
094: * recent modification available to Ant tasks.
095: * @return A Hashtable object containing no properties if there
096: * were no modifications, four properties if there were one
097: * or more modifications (keys are provided as constants on this
098: * class), or five is the property attribute was set.
099: * Never returns null.
100: */
101: public Map getProperties() {
102: return properties.getPropertiesAndReset();
103: }
104:
105: public void setProperty(String propertyName) {
106: properties.assignPropertyName(propertyName);
107: }
108:
109: /**
110: * Indicate where the build to be monitored places its output (log files).
111: *
112: * @param logDir Absolute path to the log directory.
113: */
114: public void setLogDir(String logDir) {
115: this .logDir = logDir;
116: }
117:
118: /**
119: * Make sure any attributes provided by the user are correctly specified.
120: */
121: public void validate() throws CruiseControlException {
122: ValidationHelper.assertIsSet(logDir, "logdir", this .getClass());
123:
124: File logDirectory = new File(logDir);
125: ValidationHelper.assertTrue(logDirectory.exists(),
126: "Log directory does not exist: "
127: + logDirectory.getAbsolutePath());
128: ValidationHelper.assertTrue(logDirectory.isDirectory(),
129: "Log directory is not a directory: "
130: + logDirectory.getAbsolutePath());
131: }
132:
133: /**
134: * The modifications reported by this method will be the list of new
135: * log files created as the result of successful builds (the log files
136: * will include the build label).
137: *
138: * @param lastBuild Look for successful builds newer than this date
139: * (may not be null).
140: * @param unused The timestamp of the current build is passed here
141: * (as per SourceControl interface) but we don't use it.
142: */
143: public List getModifications(Date lastBuild, Date unused) {
144: properties.put(MOST_RECENT_LOGDIR_KEY, logDir);
145: List modifications = new ArrayList();
146: File logDirectory = new File(logDir);
147: final String filename = Log.formatLogFileName(lastBuild);
148:
149: if (!logDirectory.exists()) {
150: LOG.error("log directory doesn't exist: " + logDir);
151: return modifications;
152: }
153:
154: if (!logDirectory.isDirectory()) {
155: LOG
156: .error("path for log directory exists but isn't a directory: "
157: + logDir);
158: return modifications;
159: }
160:
161: try {
162: File[] newLogs = logDirectory
163: .listFiles(new FilenameFilter() {
164: public boolean accept(File dir, String name) {
165: return name.compareTo(filename) > 0
166: && Log.wasSuccessfulBuild(name);
167: }
168: });
169:
170: Modification mostRecent = null;
171:
172: for (int i = 0; i < newLogs.length; i++) {
173: Modification modification = new Modification(
174: "buildstatus");
175: String name = newLogs[i].getName();
176:
177: modification.modifiedTime = Log
178: .parseDateFromLogFileName(name);
179: modification.userName = "cc-"
180: + getProjectFromLog(newLogs[i]);
181: modification.comment = logDir.substring(logDir
182: .lastIndexOf('/') + 1);
183: modification.revision = Log
184: .parseLabelFromLogFileName(name);
185:
186: Modification.ModifiedFile modfile = modification
187: .createModifiedFile(name, null);
188: modfile.revision = modification.revision;
189: modfile.action = "add";
190:
191: if (mostRecent == null
192: || modification.modifiedTime
193: .after(mostRecent.modifiedTime)) {
194: mostRecent = modification;
195: }
196:
197: modifications.add(modification);
198: }
199:
200: // This makes information about the most recent modification
201: // available to Ant tasks
202: if (mostRecent != null) {
203: properties.put(MOST_RECENT_LOGFILE_KEY,
204: ((Modification.ModifiedFile) mostRecent.files
205: .get(0)).fileName);
206: properties.put(MOST_RECENT_LOGTIME_KEY, DateUtil
207: .getFormattedTime(mostRecent.modifiedTime));
208: properties.put(MOST_RECENT_LOGLABEL_KEY,
209: mostRecent.revision);
210: }
211:
212: } catch (Exception e) {
213: LOG.error("Error checking for modifications", e);
214: }
215:
216: if (!modifications.isEmpty()) {
217: properties.modificationFound();
218: }
219:
220: return modifications;
221: }
222:
223: private String getProjectFromLog(File f) {
224: LOG.info("Getting project from file: " + f.getName());
225: try {
226: Document doc = readDocFromFile(f);
227: LOG.info("Loaded xml document for BuildStatus");
228: Element root = doc.getRootElement();
229: XMLLogHelper log = new XMLLogHelper(root);
230: return log.getProjectName();
231: } catch (JDOMException ex) {
232: LOG.info("Failed to load BuildStatus xml document" + ex);
233: } catch (IOException ex) {
234: LOG.info("Failed to load BuildStatus xml document" + ex);
235: } catch (CruiseControlException ex) {
236: LOG
237: .info("Could load BuildStatus xml log document, but generated exception anyway"
238: + ex);
239: }
240: return "Unknown";
241: }
242:
243: private Document readDocFromFile(File f) throws JDOMException,
244: IOException {
245: SAXBuilder sxb = new SAXBuilder();
246: return sxb.build(f);
247: }
248: }
|