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.sourcecontrols;
037:
038: import java.io.File;
039: import java.text.DateFormat;
040: import java.text.ParseException;
041: import java.text.SimpleDateFormat;
042: import java.util.ArrayList;
043: import java.util.Date;
044: import java.util.List;
045: import java.util.Map;
046: import java.util.StringTokenizer;
047:
048: import net.sourceforge.cruisecontrol.CruiseControlException;
049: import net.sourceforge.cruisecontrol.Modification;
050: import net.sourceforge.cruisecontrol.SourceControl;
051: import net.sourceforge.cruisecontrol.Modification.ModifiedFile;
052: import net.sourceforge.cruisecontrol.util.CommandExecutor;
053: import net.sourceforge.cruisecontrol.util.Commandline;
054: import net.sourceforge.cruisecontrol.util.StreamConsumer;
055: import net.sourceforge.cruisecontrol.util.ValidationHelper;
056:
057: import org.apache.log4j.Logger;
058:
059: /**
060: * This class implements the SourceControlElement methods for a PVCS repository.
061: *
062: * @author <a href="mailto:Richard.Wagner@alltel.com">Richard Wagner </a>
063: * @version $Id: PVCS.java 3714 2007-11-04 07:00:10Z jchyip $
064: */
065: public class PVCS implements SourceControl {
066:
067: public static class PvcsStreamConsumer implements StreamConsumer {
068:
069: private static final Logger LOGGER = Logger
070: .getLogger(PVCS.class);
071: private final String archiveFileSuffix;
072: private boolean firstModifiedTime = true;
073: private boolean firstRev = true;
074: private boolean firstUserName = true;
075: private final Date lastBuild;
076: private final String ls = System.getProperty("line.separator");
077: private Modification modification;
078: private final List modificationList = new ArrayList();
079: private boolean nextLineIsComment = false;
080: private final DateFormat outDateFormat;
081: private final DateFormat outDateFormatSub = new SimpleDateFormat(
082: "dd MMM yyyy HH:mm:ss");
083: private final String proj;
084: private final StringBuffer string = new StringBuffer();
085: private boolean waitingForNextValidStart = false;
086:
087: public PvcsStreamConsumer(final Date lastBuild,
088: DateFormat format, final String proj, String suffix) {
089: super ();
090: this .proj = proj;
091: this .lastBuild = lastBuild;
092: this .archiveFileSuffix = suffix;
093: this .outDateFormat = format;
094: }
095:
096: /**
097: * @see net.sourceforge.cruisecontrol.util.StreamConsumer#consumeLine(java.lang.String)
098: */
099: public void consumeLine(String line) {
100: string.append(line + ls);
101:
102: if (line.startsWith("Archive:")) {
103: initializeModification();
104: String fileName;
105:
106: int startIndex = (line.indexOf(proj) + proj.length());
107: int endIndex = line.indexOf(archiveFileSuffix);
108: if (endIndex == -1) {
109: endIndex = line.length();
110: }
111: fileName = line.substring(startIndex, endIndex);
112: if (fileName.startsWith("/")
113: || fileName.startsWith("\\")) {
114: fileName = fileName.substring(1);
115: }
116: if (fileName.startsWith("archives")) {
117: fileName = fileName.substring("archives".length());
118: }
119: modification.createModifiedFile(fileName, null);
120:
121: } else if (waitingForNextValidStart) {
122: // we're in this state after we've got the last useful line
123: // from the previous item, but haven't yet started a new one
124: // -- we should just skip these lines till we start a new one
125: // return
126: // } else if (line.startsWith("Workfile:")) {
127: // modification.createModifiedFile(line.substring(18), null);
128: } else if (line.startsWith("Archive created:")) {
129: try {
130: String createdDate = line.substring(18);
131: Date createTime;
132: try {
133: createTime = outDateFormat.parse(createdDate);
134: } catch (ParseException e) {
135: createTime = outDateFormatSub
136: .parse(createdDate);
137: }
138: ModifiedFile file = (ModifiedFile) modification.files
139: .get(0);
140: if (createTime.after(lastBuild)) {
141: file.action = "added";
142: } else {
143: file.action = "modified";
144: }
145: } catch (ParseException e) {
146: LOGGER.error("Error parsing create date: "
147: + e.getMessage(), e);
148: }
149: } else if (line.startsWith("Rev")
150: && !line.startsWith("Rev count")) {
151: if (firstRev) {
152: firstRev = false;
153: String revision = line.substring(4);
154: modification.revision = revision;
155: ModifiedFile file = (ModifiedFile) modification.files
156: .get(0);
157: file.revision = revision;
158: }
159: } else if (line.startsWith("Last modified:")) {
160: // if this is the newest revision...
161: if (firstModifiedTime) {
162: firstModifiedTime = false;
163: String lastMod = null;
164: try {
165: lastMod = line.substring(16);
166: modification.modifiedTime = outDateFormat
167: .parse(lastMod);
168: } catch (ParseException e) {
169: try {
170: modification.modifiedTime = outDateFormatSub
171: .parse(lastMod);
172: } catch (ParseException pe) {
173: modification.modifiedTime = null;
174: LOGGER
175: .error(
176: "Error parsing modification time : ",
177: e);
178: }
179: }
180: }
181: } else if (nextLineIsComment) {
182: // used boolean because don't know what comment will
183: // startWith....
184: boolean isDashesLine = line.startsWith("----------");
185: boolean isEqualsLine = line.startsWith("==========");
186: boolean isEndOfCommentsLine = isDashesLine
187: || isEqualsLine;
188: if (modification.comment == null
189: || modification.comment.length() == 0) {
190: modification.comment = line;
191: } else if (!isEndOfCommentsLine) {
192: modification.comment = modification.comment
193: + System.getProperty("line.separator")
194: + line;
195: } else {
196: // then set indicator to ignore future lines till next new
197: // item
198: modificationList.add(modification);
199: waitingForNextValidStart = true;
200: }
201: } else if (line.startsWith("Author id:")) {
202: // if this is the newest revision...
203: if (firstUserName) {
204: String sub = line.substring(11);
205: StringTokenizer st = new StringTokenizer(sub, " ");
206: modification.userName = st.nextToken().trim();
207: firstUserName = false;
208: nextLineIsComment = true;
209: }
210: } // end of Author id
211:
212: }
213:
214: public List getModificationList() {
215: return this .modificationList;
216: }
217:
218: public String getOutput() {
219: return string.toString();
220: }
221:
222: private void initializeModification() {
223: modification = new Modification("pvcs");
224: firstModifiedTime = true;
225: firstUserName = true;
226: nextLineIsComment = false;
227: waitingForNextValidStart = false;
228: }
229:
230: }
231:
232: private static final Logger LOG = Logger.getLogger(PVCS.class);
233:
234: private static final String PCLI = "pcli";
235:
236: private String archiveFileSuffix = "-arc";
237:
238: /**
239: * Date format required by commands passed to PVCS
240: */
241: private SimpleDateFormat inDateFormat = new SimpleDateFormat(
242: "MM/dd/yyyy hh:mm:ss aa");
243: private String loginId;
244:
245: /**
246: * Date format returned in the output of PVCS commands.
247: */
248: private SimpleDateFormat outDateFormat = new SimpleDateFormat(
249: "MMM dd yyyy HH:mm:ss");
250:
251: private final SourceControlProperties properties = new SourceControlProperties();
252: private String pvcsbin;
253: private String pvcsProject;
254: // i.e. "esa";
255: // i.e. "esa/uihub2";
256: private String pvcsPromotionGroup;
257: private String pvcsSubProject;
258: private String pvcsVersionLabel;
259:
260: /**
261: * Returns the command to be ran to check for repository changes run -ns -q vlog -idSomeUser "-ds11/23/2004 08:00:00
262: * AM" "-de11/23/2004 01:00:00 PM" "-prC:/PVCS-Repos/TestProject" "-vTest Version Label" -z /TestProject
263: *
264: * @return the command to be executed to check for repository changes
265: */
266: Commandline buildExecCommand(String lastBuild, String now) {
267: Commandline command = new Commandline();
268: // command.useSafeQuoting(false);
269: command.setExecutable(getExecutable(PCLI));
270: command.createArgument("run");
271: command.createArgument("-ns");
272: command.createArgument("-q");
273: command.createArgument("vlog");
274:
275: if (loginId != null && !loginId.trim().equals("")) {
276: command.createArgument("-id" + loginId);
277: }
278:
279: command.createArgument("-ds" + lastBuild);
280: command.createArgument("-de" + now);
281: command.createArgument("-pr" + pvcsProject);
282:
283: if (pvcsVersionLabel != null && !pvcsVersionLabel.equals("")) {
284: command.createArgument("-v" + pvcsVersionLabel);
285: }
286:
287: if (pvcsPromotionGroup != null
288: && !pvcsPromotionGroup.equals("")) {
289: command.createArgument("-g" + pvcsPromotionGroup);
290: }
291:
292: command.createArgument("-z");
293: command.createArgument(pvcsSubProject);
294: return command;
295: }
296:
297: protected void executeCommandline(Commandline command,
298: PvcsStreamConsumer consumer) throws CruiseControlException {
299: LOG.info("Running command: " + command);
300: CommandExecutor executor = new CommandExecutor(command, LOG);
301: executor.setOutputConsumer(consumer);
302: executor.executeAndWait();
303: LOG.debug("Output: \n" + consumer.getOutput());
304: }
305:
306: protected String getExecutable(String exe) {
307: StringBuffer correctedExe = new StringBuffer();
308: if (getPvcsbin() != null) {
309: if (getPvcsbin().endsWith(File.separator)) {
310: correctedExe.append(getPvcsbin());
311: } else {
312: correctedExe.append(getPvcsbin())
313: .append(File.separator);
314: }
315: }
316: return correctedExe.append(exe).toString();
317: }
318:
319: /**
320: * @return loginId
321: */
322: public String getLoginid() {
323: return loginId;
324: }
325:
326: /**
327: * Returns an {@link java.util.List List}of {@link Modification}s detailing all the changes between now and the
328: * last build.
329: *
330: * @param lastBuild
331: * the last build time
332: * @param now
333: * time now, or time to check
334: * @return the list of modifications, an empty (not null) list if no modifications or if developer had checked in
335: * files since quietPeriod seconds ago.
336: *
337: * Note: Internally uses external filesystem for files CruiseControlPVCS.pcli, files.tmp, vlog.txt
338: */
339: public List getModifications(Date lastBuild, Date now) {
340:
341: PvcsStreamConsumer consumer = new PvcsStreamConsumer(lastBuild,
342: this .outDateFormat, pvcsProject, this .archiveFileSuffix);
343: List modificationList = getModifications(lastBuild, now,
344: consumer);
345:
346: return modificationList;
347: }
348:
349: protected List getModifications(Date lastBuild, Date now,
350: PvcsStreamConsumer consumer) {
351: try {
352: // build file of PVCS command line instructions
353: String lastBuildDate = inDateFormat.format(lastBuild);
354: String nowDate = inDateFormat.format(now);
355: Commandline command = buildExecCommand(lastBuildDate,
356: nowDate);
357: executeCommandline(command, consumer);
358: } catch (Exception e) {
359: LOG.error("Error in executing the PVCS command : ", e);
360: }
361:
362: List modificationList = consumer.getModificationList();
363:
364: if (!modificationList.isEmpty()) {
365: properties.modificationFound();
366: }
367: StringBuffer msg = new StringBuffer(""
368: + modificationList.size());
369: if (1 == modificationList.size()) {
370: msg.append(" modification has been detected");
371: } else {
372: msg.append(" modifications have been detected");
373: }
374: msg.append(" for ").append(pvcsSubProject).append(".");
375: LOG.info(msg.toString());
376: return modificationList;
377: }
378:
379: public Map getProperties() {
380: return properties.getPropertiesAndReset();
381: }
382:
383: /**
384: * Get name of the PVCS bin directory
385: *
386: * @return String
387: */
388: public String getPvcsbin() {
389: return pvcsbin;
390: }
391:
392: public void setArchiveFileSuffix(String archiveSuffix) {
393: this .archiveFileSuffix = archiveSuffix;
394: }
395:
396: public void setInDateFormat(String inDateFormat) {
397: this .inDateFormat = new SimpleDateFormat(inDateFormat);
398: }
399:
400: /**
401: * @param loginId
402: */
403: public void setLoginid(String loginId) {
404: this .loginId = loginId;
405: }
406:
407: public void setOutDateFormat(String outDateFormat) {
408: this .outDateFormat = new SimpleDateFormat(outDateFormat);
409: }
410:
411: public void setProperty(String propertyName) {
412: properties.assignPropertyName(propertyName);
413: }
414:
415: /**
416: * Specifies the location of the PVCS bin directory
417: *
418: * @param bin
419: * Specifies the location of the PVCS bin directory
420: */
421: public void setPvcsbin(String bin) {
422: this .pvcsbin = bin;
423: }
424:
425: public void setPvcsproject(String project) {
426: pvcsProject = project;
427: }
428:
429: public void setPvcspromotiongroup(String promotiongroup) {
430: pvcsPromotionGroup = promotiongroup;
431: }
432:
433: public void setPvcssubproject(String subproject) {
434: pvcsSubProject = subproject;
435: }
436:
437: public void setPvcsversionlabel(String versionlabel) {
438: pvcsVersionLabel = versionlabel;
439: }
440:
441: public void validate() throws CruiseControlException {
442: ValidationHelper.assertIsSet(pvcsProject, "pvcsproject", this
443: .getClass());
444: ValidationHelper.assertIsSet(pvcsSubProject, "pvcssubproject",
445: this .getClass());
446: }
447:
448: } // end class PVCSElement
|