001: /********************************************************************************
002: * CruiseControl, a Continuous Integration Toolkit
003: * Copyright (c) 2001-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 java.io.File;
039: import java.util.ArrayList;
040: import java.util.Date;
041: import java.util.List;
042: import java.util.Map;
043:
044: import net.sourceforge.cruisecontrol.CruiseControlException;
045: import net.sourceforge.cruisecontrol.Modification;
046: import net.sourceforge.cruisecontrol.SourceControl;
047: import net.sourceforge.cruisecontrol.util.Commandline;
048: import net.sourceforge.cruisecontrol.util.Processes;
049: import net.sourceforge.cruisecontrol.util.StreamConsumer;
050: import net.sourceforge.cruisecontrol.util.StreamLogger;
051: import net.sourceforge.cruisecontrol.util.ValidationHelper;
052:
053: import org.apache.log4j.Logger;
054:
055: /**
056: * This class implements the SourceControlElement methods for a MKS repository.
057: * The call to MKS is assumed to work with any setup:
058: * The call to MKS login should be done prior to calling this class.
059: * *
060: * attributes:
061: * localWorkingDir - local directory for the sandbox
062: * project - the name and path to the MKS project
063: * doNothing - if this attribute is set to true, no mks command is executed. This is for
064: * testing purposes, if a potentially slow mks server connection should avoid
065: *
066: * @author Suresh K Bathala Skila, Inc.
067: * @author Dominik Hirt, Wincor-Nixdorf International GmbH, Leipzig
068: */
069: public class MKS implements SourceControl {
070: private static final Logger LOG = Logger.getLogger(MKS.class);
071:
072: private SourceControlProperties properties = new SourceControlProperties();
073: private String project;
074: private File localWorkingDir;
075:
076: /**
077: * if this attribute is set to true, no mks command is executed. This is for
078: * testing purposes, if a potentially slow mks server connection should
079: * avoid
080: */
081: private boolean doNothing;
082:
083: /**
084: * This is the workaround for the missing feature of MKS to return differences
085: * for a given time period. If a modification is detected during the quietperiod,
086: * CruiseControl calls <code>getModifications</code> of this sourcecontrol object
087: * again, with the new values for <code>Date now</code>. In that case, and if all
088: * modification are already found in the first cycle, the list of modifications
089: * becomes empty. Therefor, we have to return the summarized list of modifications:
090: * the values from the last run, and -maybe- results return by MKS for this run.
091: */
092: private List listOfModifications = new ArrayList();
093:
094: /**
095: * The listOfModifications is cleared when new value for lastBuild is used.
096: */
097: private Date lastBuild = new Date();
098:
099: public void setProject(String project) {
100: this .project = project;
101: }
102:
103: /**
104: * Sets the local working copy to use when making calls to MKS.
105: *
106: * @param local
107: * String indicating the relative or absolute path to the local
108: * working copy of the module of which to find the log history.
109: */
110: public void setLocalWorkingDir(String local) {
111: localWorkingDir = new File(local);
112: }
113:
114: public void setProperty(String property) {
115: properties.assignPropertyName(property);
116: }
117:
118: public Map getProperties() {
119: return properties.getPropertiesAndReset();
120: }
121:
122: public void setDoNothing(String doNothing) {
123: this .doNothing = Boolean.valueOf(doNothing).booleanValue();
124: }
125:
126: public void validate() throws CruiseControlException {
127: ValidationHelper.assertIsSet(localWorkingDir,
128: "localWorkingDir", this .getClass());
129: ValidationHelper.assertIsSet(project, "project", this
130: .getClass());
131: }
132:
133: /**
134: * Returns an ArrayList of Modifications.
135: * MKS ignores dates for such a range so therefor ALL
136: * modifications since the last resynch step are returned.
137: *
138: * @param lastBuild
139: * Last build time.
140: * @param now
141: * Time now, or time to check.
142: * @return maybe empty, never null.
143: * @throws CruiseControlException
144: */
145: public List getModifications(Date lastBuild, Date now) {
146:
147: if (doNothing) {
148: properties.modificationFound();
149: return listOfModifications;
150: }
151:
152: if (this .lastBuild.compareTo(lastBuild) != 0) {
153: listOfModifications.clear();
154: this .lastBuild = lastBuild;
155: }
156:
157: String projectFilePath = getProjectFilePath();
158:
159: Commandline cmdLine = createResyncCommandLine(projectFilePath);
160:
161: executeResyncAndParseModifications(cmdLine, listOfModifications);
162:
163: return listOfModifications;
164: }
165:
166: void executeResyncAndParseModifications(Commandline cmdLine,
167: List modifications) {
168: try {
169: StreamConsumer stderr = new ModificationsConsumer(
170: modifications);
171: StreamConsumer stdout = StreamLogger.getWarnLogger(LOG);
172: Processes.waitFor(cmdLine.execute(), stdout, stderr);
173: } catch (Exception ex) {
174: LOG.warn(ex.getMessage(), ex);
175: }
176: LOG.info("resync finished");
177: }
178:
179: Commandline createResyncCommandLine(String projectFilePath) {
180: Commandline cmdLine = new Commandline();
181: cmdLine.setExecutable("si");
182: cmdLine.createArgument("resync");
183: cmdLine.createArgument("-f");
184: cmdLine.createArgument("-R");
185: cmdLine.createArgument("-S");
186: cmdLine.createArgument(projectFilePath);
187: try {
188: cmdLine.setWorkingDir(localWorkingDir);
189: } catch (CruiseControlException e) {
190: throw new RuntimeException(e);
191: }
192: return cmdLine;
193: }
194:
195: String getProjectFilePath() {
196: String projectFilePath = localWorkingDir.getAbsolutePath()
197: + File.separator + project;
198: if (!new File(projectFilePath).exists()) {
199: throw new RuntimeException("project file not found at "
200: + projectFilePath);
201: }
202: return projectFilePath;
203: }
204:
205: /**
206: * Sample output:
207: * dominik.hirt;add forceDeploy peter.neumcke;path to properties file fixed,
208: * copy generated properties Member added to project
209: * d:/MKS/PCE_Usedom/Products/Info/Info.pj
210: *
211: * @param fileName
212: */
213: private void setUserNameAndComment(Modification modification,
214: String folderName, String fileName) {
215:
216: try {
217: Commandline commandLine = new Commandline();
218: commandLine.setExecutable("si");
219:
220: if (localWorkingDir != null) {
221: commandLine.setWorkingDirectory(localWorkingDir
222: .getAbsolutePath());
223: }
224:
225: commandLine.createArgument("rlog");
226: commandLine
227: .createArgument("--format={author};{description}");
228: commandLine.createArgument("--noHeaderFormat");
229: commandLine.createArgument("--noTrailerFormat");
230: commandLine.createArguments("-r", modification.revision);
231: commandLine.createArgument(folderName + File.separator
232: + fileName);
233:
234: Process proc = commandLine.execute();
235: UserAndCommentConsumer userData = new UserAndCommentConsumer();
236: Processes.waitFor(proc, userData, StreamLogger
237: .getWarnLogger(LOG));
238: if (userData.wasFound()) {
239: modification.userName = userData.getUserName();
240: modification.comment = userData.getComment();
241: } else {
242: LOG.warn("could not find username or comment for "
243: + fileName + " r" + modification.revision);
244: modification.userName = "";
245: modification.comment = "";
246: }
247: } catch (Exception e) {
248: LOG.warn(e.getMessage(), e);
249: modification.userName = "";
250: modification.comment = "";
251: }
252: }
253:
254: /* Sample output on stderr:
255: * output: Connecting to baswmks1:7001 ... Connecting to baswmks1:7001
256: * as dominik.hirt ... Resynchronizing files...
257: * c:\temp\test\Admin\ComponentBuild\antfile.xml
258: * c:\temp\test\Admin\PCEAdminCommand\projectbuild.properties: checked
259: * out revision 1.1
260: */
261:
262: private final class ModificationsConsumer implements StreamConsumer {
263: public List modifications;
264:
265: private ModificationsConsumer(List modifications) {
266: this .modifications = modifications;
267: }
268:
269: public void consumeLine(String line) {
270: int idxCheckedOutRevision = line
271: .indexOf(": checked out revision");
272:
273: if (idxCheckedOutRevision == -1) {
274: return;
275: }
276: LOG.info(line);
277:
278: int idxSeparator = line.lastIndexOf(File.separator);
279: String folderName = line.substring(0, idxSeparator);
280: String fileName = line.substring(idxSeparator + 1,
281: idxCheckedOutRevision);
282: Modification modification = new Modification();
283: Modification.ModifiedFile modFile = modification
284: .createModifiedFile(fileName, folderName);
285: modification.modifiedTime = new Date(new File(folderName,
286: fileName).lastModified());
287: modFile.revision = line
288: .substring(idxCheckedOutRevision + 23);
289: modification.revision = modFile.revision;
290: setUserNameAndComment(modification, folderName, fileName);
291:
292: modifications.add(modification);
293:
294: properties.modificationFound();
295: }
296: }
297:
298: private static class UserAndCommentConsumer implements
299: StreamConsumer {
300: private boolean found;
301: private String userName;
302: private String comment;
303:
304: public boolean wasFound() {
305: return found;
306: }
307:
308: public String getUserName() {
309: return userName;
310: }
311:
312: public String getComment() {
313: return comment;
314: }
315:
316: public void consumeLine(String line) {
317: if (found) {
318: return;
319: }
320: int idx = line.indexOf(";");
321: if (idx == -1) {
322: LOG.debug(line);
323: return;
324: }
325:
326: found = true;
327: userName = line.substring(0, idx);
328: if (idx < line.length()) {
329: comment = line.substring(idx + 1);
330: } else {
331: comment = "";
332: }
333: }
334: }
335: }
|