001: /********************************************************************************
002: * CruiseControl, a Continuous Integration Toolkit
003: * Copyright (c) 2007, 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 net.sourceforge.cruisecontrol.CruiseControlException;
039: import net.sourceforge.cruisecontrol.Modification;
040: import net.sourceforge.cruisecontrol.SourceControl;
041: import net.sourceforge.cruisecontrol.util.Commandline;
042: import net.sourceforge.cruisecontrol.util.IO;
043: import net.sourceforge.cruisecontrol.util.StreamLogger;
044: import net.sourceforge.cruisecontrol.util.ValidationHelper;
045: import org.apache.log4j.Logger;
046: import org.jdom.Document;
047: import org.jdom.Element;
048: import org.jdom.JDOMException;
049: import org.jdom.input.SAXBuilder;
050:
051: import java.io.BufferedInputStream;
052: import java.io.File;
053: import java.io.IOException;
054: import java.io.InputStream;
055: import java.io.InputStreamReader;
056: import java.io.Reader;
057: import java.text.DateFormat;
058: import java.text.ParseException;
059: import java.text.SimpleDateFormat;
060: import java.util.ArrayList;
061: import java.util.Collections;
062: import java.util.Date;
063: import java.util.Iterator;
064: import java.util.List;
065: import java.util.Map;
066:
067: /**
068: * <p/>
069: * Source Control implementation for Darcs. Provides a means of executing the darcs changes command and parsing the xml
070: * output to determine if there have been any changes. The modifications are parsed and used for cruisecontrol build
071: * reports which allow the patch names associated with a build to be displayed. Currently the darcs xml-output does not
072: * display which files have changed.
073: * </p>
074: * <p/>
075: * Large parts of this implementation were based on the {@link net.sourceforge.cruisecontrol.sourcecontrols.SVN} source
076: * control implementation.
077: * </p>
078: */
079: public class Darcs implements SourceControl {
080:
081: private static final long serialVersionUID = 7976081409836256093L;
082:
083: private static final Logger LOGGER = Logger.getLogger(Darcs.class);
084:
085: private static final DateFormat DARCS_DATE_FORMAT = new SimpleDateFormat(
086: "yyyyMMddHHmmss");
087:
088: private String mWorkingDir;
089:
090: private String mRepositoryLocation;
091:
092: private SourceControlProperties mProperties = new SourceControlProperties();
093:
094: public void setProperty(String property) {
095: mProperties.assignPropertyName(property);
096: }
097:
098: public void setPropertyOnDelete(String propertyOnDelete) {
099: mProperties.assignPropertyOnDeleteName(propertyOnDelete);
100: }
101:
102: public void setRepositoryLocation(String repositoryLocation) {
103: mRepositoryLocation = repositoryLocation;
104: }
105:
106: public void setWorkingDir(String workingDir) {
107: mWorkingDir = workingDir;
108: }
109:
110: public Map getProperties() {
111: return mProperties.getPropertiesAndReset();
112: }
113:
114: public void validate() throws CruiseControlException {
115: ValidationHelper
116: .assertTrue(
117: mRepositoryLocation != null
118: || mWorkingDir != null,
119: "At least 'repositoryLocation'or 'workingDir' is a required attribute on the Darcs task ");
120:
121: if (mWorkingDir != null) {
122: File workingDir = new File(mWorkingDir);
123: ValidationHelper.assertTrue(workingDir.exists()
124: && workingDir.isDirectory(),
125: "'workingDir' must be an existing directory. Was "
126: + workingDir.getAbsolutePath());
127: }
128: }
129:
130: public List getModifications(Date lastBuild, Date now) {
131: try {
132: Commandline command = buildChangesCommand(lastBuild, now);
133: List modifications = execChangesCommand(command);
134: fillPropertiesIfNeeded(modifications);
135: return modifications;
136: } catch (Exception e) {
137: LOGGER.error("Failed to execute darcs changes command", e);
138: }
139: return Collections.EMPTY_LIST;
140: }
141:
142: Commandline buildChangesCommand(Date lastBuild, Date checkTime)
143: throws CruiseControlException {
144:
145: Commandline command = new Commandline();
146: command.setExecutable("darcs");
147:
148: if (mWorkingDir != null) {
149: command.setWorkingDirectory(mWorkingDir);
150: }
151:
152: command.createArgument("changes");
153: command.createArgument("--xml-output");
154: command.createArgument("--match");
155: command.createArgument("'date \""
156: + DARCS_DATE_FORMAT.format(lastBuild) + "/"
157: + DARCS_DATE_FORMAT.format(checkTime) + "\"'");
158:
159: LOGGER.debug("Executing command: " + command);
160:
161: return command;
162: }
163:
164: private List execChangesCommand(Commandline command)
165: throws InterruptedException, IOException, ParseException,
166: JDOMException {
167:
168: Process p = command.execute();
169:
170: Thread stderr = logErrorStream(p);
171: InputStream darcsStream = p.getInputStream();
172: List modifications = parseStream(darcsStream);
173: p.waitFor();
174: stderr.join();
175: IO.close(p);
176:
177: return modifications;
178: }
179:
180: private List parseStream(InputStream darcsStream)
181: throws ParseException, JDOMException, IOException {
182: InputStreamReader reader = new InputStreamReader(
183: new BufferedInputStream(darcsStream), "UTF-8");
184: return DarcsXmlParser.parse(reader);
185: }
186:
187: private Thread logErrorStream(Process p) {
188: Thread stderr = new Thread(StreamLogger.getWarnPumper(LOGGER, p
189: .getErrorStream()));
190: stderr.start();
191: return stderr;
192: }
193:
194: void fillPropertiesIfNeeded(List modifications) {
195: if (!modifications.isEmpty()) {
196: mProperties.modificationFound();
197: }
198: }
199:
200: static final class DarcsXmlParser {
201: private DarcsXmlParser() { /* helper class, no instances */
202: }
203:
204: static List parse(Reader reader) throws ParseException,
205: JDOMException, IOException {
206:
207: SAXBuilder builder = new SAXBuilder(false);
208: Document document = builder.build(reader);
209: return parseDOMTree(document);
210: }
211:
212: private static List parseDOMTree(Document document)
213: throws ParseException {
214: List modifications = new ArrayList();
215: Element rootElement = document.getRootElement();
216: List patches = rootElement.getChildren("patch");
217: if (patches != null) {
218: for (Iterator iterator = patches.iterator(); iterator
219: .hasNext();) {
220: Element patch = (Element) iterator.next();
221: modifications.add(parsePatch(patch));
222: }
223: }
224: return modifications;
225: }
226:
227: private static Modification parsePatch(Element patch)
228: throws ParseException {
229: Modification modification = new Modification("darcs");
230: modification.modifiedTime = DARCS_DATE_FORMAT.parse(patch
231: .getAttributeValue("date"));
232: String email = patch.getAttributeValue("author");
233: modification.userName = parseUser(email);
234: modification.emailAddress = email;
235: modification.comment = patch.getChildText("name");
236: return modification;
237: }
238:
239: private static String parseUser(String email) {
240: return email.substring(0, email.indexOf('@'));
241: }
242: }
243:
244: }
|