001: /********************************************************************************
002: * CruiseControl, a Continuous Integration Toolkit
003: * Copyright (c) 2001-2003, 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;
037:
038: import java.io.BufferedOutputStream;
039: import java.io.File;
040: import java.io.FileOutputStream;
041: import java.io.IOException;
042: import java.io.OutputStream;
043: import java.io.Serializable;
044: import java.text.ParseException;
045: import java.text.SimpleDateFormat;
046: import java.util.ArrayList;
047: import java.util.Date;
048: import java.util.Iterator;
049: import java.util.List;
050:
051: import net.sourceforge.cruisecontrol.util.DateUtil;
052: import net.sourceforge.cruisecontrol.util.IO;
053: import net.sourceforge.cruisecontrol.util.XMLLogHelper;
054: import net.sourceforge.cruisecontrol.util.Util;
055:
056: import org.apache.log4j.Logger;
057: import org.jdom.Content;
058: import org.jdom.Document;
059: import org.jdom.Element;
060: import org.jdom.output.Format;
061: import org.jdom.output.XMLOutputter;
062:
063: /**
064: * Handles the Log element, and subelements, of the CruiseControl configuration file. Also represents the Build Log used
065: * by the CruiseControl build process.
066: */
067: public class Log implements Serializable {
068: private static final long serialVersionUID = -5727569770074024691L;
069:
070: private static final Logger LOG = Logger.getLogger(Log.class);
071:
072: public static final int BEFORE_LENGTH = "logYYYYMMDDhhmmssL"
073: .length();
074: private static final int AFTER_LENGTH = ".xml".length();
075:
076: private transient String logDir;
077: private transient String logXmlEncoding;
078: private transient Element buildLog;
079: private transient List loggers = new ArrayList();
080: private transient List manipulators = new ArrayList();
081: private transient String projectName;
082:
083: /**
084: * Log instances created this way must have their projectName set.
085: */
086: public Log() {
087: reset();
088: }
089:
090: /**
091: * Although this property is required, it is implicitly defined by the project and doesn't map to a config file
092: * attribute.
093: *
094: * @throws IllegalArgumentException
095: * is projectName is null
096: */
097: void setProjectName(String projectName) {
098: if (projectName == null) {
099: throw new IllegalArgumentException(
100: "projectName can't be null");
101: }
102: this .projectName = projectName;
103: if (logDir == null) {
104: logDir = "logs" + File.separatorChar + projectName;
105: }
106: }
107:
108: /**
109: * Validate the log. Also creates the log directory if it doesn't exist.
110: *
111: * @throws IllegalStateException
112: * if projectName wasn't set
113: */
114: public void validate() throws CruiseControlException {
115: if (projectName == null) {
116: // Not using ValidationHelper because projectName should be set
117: // implictly by the project, not as an attribute.
118: throw new CruiseControlException("projectName must be set");
119: }
120: if (logDir != null) {
121: checkLogDirectory(logDir);
122: }
123:
124: for (Iterator i = loggers.iterator(); i.hasNext();) {
125: BuildLogger logger = (BuildLogger) i.next();
126: logger.validate();
127: }
128:
129: for (Iterator i = manipulators.iterator(); i.hasNext();) {
130: Manipulator manipulator = (Manipulator) i.next();
131: manipulator.validate();
132: }
133: }
134:
135: /**
136: * Adds a BuildLogger that will be called to manipulate the project log just prior to writing the log.
137: */
138: public void add(BuildLogger logger) {
139: loggers.add(logger);
140: }
141:
142: /**
143: * Adds a Manipulator that will handle old log-files
144: */
145: public void add(Manipulator manipulator) {
146: manipulators.add(manipulator);
147: }
148:
149: public BuildLogger[] getLoggers() {
150: return (BuildLogger[]) loggers.toArray(new BuildLogger[loggers
151: .size()]);
152: }
153:
154: public String getLogXmlEncoding() {
155: return logXmlEncoding;
156: }
157:
158: public String getProjectName() {
159: return projectName;
160: }
161:
162: /**
163: * @param logDir
164: * @throws CruiseControlException
165: * @deprecated use {@link #setDir(String)}
166: */
167: public void setLogDir(String logDir) throws CruiseControlException {
168: setDir(logDir);
169: }
170:
171: public void setDir(String logDir) throws CruiseControlException {
172: this .logDir = logDir;
173: }
174:
175: /**
176: * @param logXmlEncoding
177: * @deprecated use {@link #setEncoding(String)}
178: */
179: public void setLogXmlEncoding(String logXmlEncoding) {
180: setEncoding(logXmlEncoding);
181: }
182:
183: public void setEncoding(String logXmlEncoding) {
184: this .logXmlEncoding = logXmlEncoding;
185: }
186:
187: public String getLogDir() {
188: return logDir;
189: }
190:
191: /**
192: * creates log directory if it doesn't already exist
193: *
194: * @throws CruiseControlException
195: * if directory can't be created or there is a file of the same name
196: */
197: private void checkLogDirectory(String logDir)
198: throws CruiseControlException {
199: File logDirectory = new File(logDir);
200: if (!logDirectory.exists()) {
201: LOG
202: .info("log directory specified in config file does not exist; creating: "
203: + logDirectory.getAbsolutePath());
204: if (!Util.doMkDirs(logDirectory)) {
205: throw new CruiseControlException(
206: "Can't create log directory specified in config file: "
207: + logDirectory.getAbsolutePath());
208: }
209: } else if (!logDirectory.isDirectory()) {
210: throw new CruiseControlException(
211: "Log directory specified in config file is not a directory: "
212: + logDirectory.getAbsolutePath());
213: }
214: }
215:
216: /**
217: * Writes the current build log to the appropriate directory and filename.
218: */
219: public void writeLogFile(Date now) throws CruiseControlException {
220:
221: // Call the Loggers to let them do their thing
222: for (int i = 0; i < loggers.size(); i++) {
223: BuildLogger nextLogger = (BuildLogger) loggers.get(i);
224: // The buildloggers get the "real" build log, not a clone. Therefore,
225: // call getContent() wouldn't be appropriate here.
226: nextLogger.log(buildLog);
227: }
228:
229: String logFilename = decideLogfileName(now);
230:
231: // Add the logDir as an info element
232: Element logDirElement = new Element("property");
233: logDirElement.setAttribute("name", "logdir");
234: logDirElement.setAttribute("value", new File(logDir)
235: .getAbsolutePath());
236: buildLog.getChild("info").addContent(logDirElement);
237:
238: // Add the logFile as an info element
239: Element logFileElement = new Element("property");
240: logFileElement.setAttribute("name", "logfile");
241: logFileElement.setAttribute("value", logFilename);
242: buildLog.getChild("info").addContent(logFileElement);
243:
244: File logfile = new File(logDir, logFilename);
245: LOG.debug("Project " + projectName + ": Writing log file ["
246: + logfile.getAbsolutePath() + "]");
247: writeLogFile(logfile, buildLog);
248:
249: callManipulators();
250: }
251:
252: protected void writeLogFile(File file, Element element)
253: throws CruiseControlException {
254: // Write the log file out, let jdom care about the encoding by using
255: // an OutputStream instead of a Writer.
256: OutputStream logStream = null;
257: try {
258: Format format = Format.getPrettyFormat();
259: if (logXmlEncoding != null) {
260: format.setEncoding(logXmlEncoding);
261: }
262: XMLOutputter outputter = new XMLOutputter(format);
263: logStream = new BufferedOutputStream(new FileOutputStream(
264: file));
265: outputter.output(new Document(element), logStream);
266: } catch (IOException e) {
267: throw new CruiseControlException(e);
268: } finally {
269: IO.close(logStream);
270: }
271: }
272:
273: private String decideLogfileName(Date now)
274: throws CruiseControlException {
275: XMLLogHelper helper = new XMLLogHelper(buildLog);
276: if (helper.isBuildSuccessful()) {
277: return formatLogFileName(now, helper.getLabel());
278: }
279:
280: return formatLogFileName(now);
281: }
282:
283: /**
284: * Calls all Manipulators to already existing logfiles.
285: */
286: protected void callManipulators() {
287: for (Iterator i = manipulators.iterator(); i.hasNext();) {
288: Manipulator manipulator = (Manipulator) i.next();
289: manipulator.execute(getLogDir());
290: }
291: }
292:
293: public static String formatLogFileName(Date date) {
294: return formatLogFileName(date, null);
295: }
296:
297: public static String formatLogFileName(Date date, String label) {
298: StringBuffer logFileName = new StringBuffer();
299: logFileName.append("log");
300: logFileName.append(DateUtil.getFormattedTime(date));
301: if (label != null) {
302: logFileName.append("L");
303: logFileName.append(label);
304: }
305: logFileName.append(".xml");
306:
307: return logFileName.toString();
308: }
309:
310: public void addContent(Content newContent) {
311: buildLog.addContent(newContent);
312: }
313:
314: public Element getContent() {
315: return (Element) buildLog.clone();
316: }
317:
318: public boolean wasBuildSuccessful() {
319: return new XMLLogHelper(buildLog).isBuildSuccessful();
320: }
321:
322: /**
323: * Resets the build log. After calling this method a fresh build log will exist, ready for adding new content.
324: */
325: public void reset() {
326: this .buildLog = new Element("cruisecontrol");
327: }
328:
329: public static boolean wasSuccessfulBuild(String filename) {
330: if (filename == null) {
331: return false;
332: }
333: boolean startsWithLog = filename.startsWith("log");
334: boolean hasLabelSeparator = filename.indexOf('L') == BEFORE_LENGTH - 1;
335: boolean isXmlFile = filename.endsWith(".xml");
336: return startsWithLog && hasLabelSeparator && isXmlFile;
337: }
338:
339: public static Date parseDateFromLogFileName(String filename)
340: throws ParseException {
341: SimpleDateFormat formatter = new SimpleDateFormat(
342: "yyyyMMddHHmmss");
343: return formatter
344: .parse(filename.substring(3, BEFORE_LENGTH - 1));
345: }
346:
347: public static String parseLabelFromLogFileName(String filename) {
348: if (!Log.wasSuccessfulBuild(filename)) {
349: return "";
350: }
351: return filename.substring(BEFORE_LENGTH, filename.length()
352: - AFTER_LENGTH);
353: }
354:
355: }
|