/********************************************************************************
* CruiseControl, a Continuous Integration Toolkit
* Copyright (c) 2001-2003, ThoughtWorks, Inc.
* 200 E. Randolph, 25th Floor
* Chicago, IL 60601 USA
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* + Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* + Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
********************************************************************************/
package net.sourceforge.cruisecontrol.sourcecontrols;
import java.io.File;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import net.sourceforge.cruisecontrol.CruiseControlException;
import net.sourceforge.cruisecontrol.Modification;
import net.sourceforge.cruisecontrol.SourceControl;
import net.sourceforge.cruisecontrol.Modification.ModifiedFile;
import net.sourceforge.cruisecontrol.util.CommandExecutor;
import net.sourceforge.cruisecontrol.util.Commandline;
import net.sourceforge.cruisecontrol.util.StreamConsumer;
import net.sourceforge.cruisecontrol.util.ValidationHelper;
import org.apache.log4j.Logger;
/**
* This class implements the SourceControlElement methods for a PVCS repository.
*
* @author <a href="mailto:Richard.Wagner@alltel.com">Richard Wagner </a>
* @version $Id$
*/
public class PVCS implements SourceControl {
public static class PvcsStreamConsumer implements StreamConsumer {
private static final Logger LOGGER = Logger.getLogger(PVCS.class);
private final String archiveFileSuffix;
private boolean firstModifiedTime = true;
private boolean firstRev = true;
private boolean firstUserName = true;
private final Date lastBuild;
private final String ls = System.getProperty("line.separator");
private Modification modification;
private final List<Modification> modificationList = new ArrayList<Modification>();
private boolean nextLineIsComment = false;
private final DateFormat outDateFormat;
private final DateFormat outDateFormatSub = new SimpleDateFormat("dd MMM yyyy HH:mm:ss");
private final String proj;
private final StringBuilder string = new StringBuilder();
private boolean waitingForNextValidStart = false;
public PvcsStreamConsumer(final Date lastBuild, DateFormat format, final String proj, String suffix) {
super();
this.proj = proj;
this.lastBuild = lastBuild;
this.archiveFileSuffix = suffix;
this.outDateFormat = format;
}
/**
* @see net.sourceforge.cruisecontrol.util.StreamConsumer#consumeLine(java.lang.String)
*/
public void consumeLine(final String line) {
string.append(line).append(ls);
if (line.startsWith("Archive:")) {
initializeModification();
String fileName;
int startIndex = (line.indexOf(proj) + proj.length());
int endIndex = line.indexOf(archiveFileSuffix);
if (endIndex == -1) {
endIndex = line.length();
}
fileName = line.substring(startIndex, endIndex);
if (fileName.startsWith("/") || fileName.startsWith("\\")) {
fileName = fileName.substring(1);
}
if (fileName.startsWith("archives")) {
fileName = fileName.substring("archives".length());
}
modification.createModifiedFile(fileName, null);
} else if (waitingForNextValidStart) {
// we're in this state after we've got the last useful line
// from the previous item, but haven't yet started a new one
// -- we should just skip these lines till we start a new one
// return
// } else if (line.startsWith("Workfile:")) {
// modification.createModifiedFile(line.substring(18), null);
} else if (line.startsWith("Archive created:")) {
try {
final String createdDate = line.substring(18);
Date createTime;
try {
createTime = outDateFormat.parse(createdDate);
} catch (ParseException e) {
createTime = outDateFormatSub.parse(createdDate);
}
final ModifiedFile file = modification.files.get(0);
if (createTime.after(lastBuild)) {
file.action = "added";
} else {
file.action = "modified";
}
} catch (ParseException e) {
LOGGER.error("Error parsing create date: " + e.getMessage(), e);
}
} else if (line.startsWith("Rev") && !line.startsWith("Rev count")) {
if (firstRev) {
firstRev = false;
final String revision = line.substring(4);
modification.revision = revision;
final ModifiedFile file = modification.files.get(0);
file.revision = revision;
}
} else if (line.startsWith("Last modified:")) {
// if this is the newest revision...
if (firstModifiedTime) {
firstModifiedTime = false;
String lastMod = null;
try {
lastMod = line.substring(16);
modification.modifiedTime = outDateFormat.parse(lastMod);
} catch (ParseException e) {
try {
modification.modifiedTime = outDateFormatSub.parse(lastMod);
} catch (ParseException pe) {
modification.modifiedTime = null;
LOGGER.error("Error parsing modification time : ", e);
}
}
}
} else if (nextLineIsComment) {
// used boolean because don't know what comment will
// startWith....
boolean isDashesLine = line.startsWith("----------");
boolean isEqualsLine = line.startsWith("==========");
boolean isEndOfCommentsLine = isDashesLine || isEqualsLine;
if (modification.comment == null || modification.comment.length() == 0) {
modification.comment = line;
} else if (!isEndOfCommentsLine) {
modification.comment = modification.comment + System.getProperty("line.separator") + line;
} else {
// then set indicator to ignore future lines till next new
// item
modificationList.add(modification);
waitingForNextValidStart = true;
}
} else if (line.startsWith("Author id:")) {
// if this is the newest revision...
if (firstUserName) {
final String sub = line.substring(11);
final StringTokenizer st = new StringTokenizer(sub, " ");
modification.userName = st.nextToken().trim();
firstUserName = false;
nextLineIsComment = true;
}
} // end of Author id
}
public List<Modification> getModificationList() {
return this.modificationList;
}
public String getOutput() {
return string.toString();
}
private void initializeModification() {
modification = new Modification("pvcs");
firstModifiedTime = true;
firstUserName = true;
nextLineIsComment = false;
waitingForNextValidStart = false;
}
}
private static final Logger LOG = Logger.getLogger(PVCS.class);
private static final String PCLI = "pcli";
private String archiveFileSuffix = "-arc";
/**
* Date format required by commands passed to PVCS
*/
private SimpleDateFormat inDateFormat = new SimpleDateFormat("MM/dd/yyyy hh:mm:ss aa");
private String loginId;
/**
* Date format returned in the output of PVCS commands.
*/
private SimpleDateFormat outDateFormat = new SimpleDateFormat("MMM dd yyyy HH:mm:ss");
private final SourceControlProperties properties = new SourceControlProperties();
private String pvcsbin;
private String pvcsProject;
// i.e. "esa";
// i.e. "esa/uihub2";
private String pvcsPromotionGroup;
private String pvcsSubProject;
private String pvcsVersionLabel;
/**
* Returns the command to be ran to check for repository changes run -ns -q vlog -idSomeUser "-ds11/23/2004 08:00:00
* AM" "-de11/23/2004 01:00:00 PM" "-prC:/PVCS-Repos/TestProject" "-vTest Version Label" -z /TestProject
*
* @param lastBuild last build date
* @param now current build date
* @return the command to be executed to check for repository changes
*/
Commandline buildExecCommand(final String lastBuild, final String now) {
final Commandline command = new Commandline();
// command.useSafeQuoting(false);
command.setExecutable(getExecutable(PCLI));
command.createArgument("run");
command.createArgument("-ns");
command.createArgument("-q");
command.createArgument("vlog");
if (loginId != null && !loginId.trim().equals("")) {
command.createArgument("-id" + loginId);
}
command.createArgument("-ds" + lastBuild);
command.createArgument("-de" + now);
command.createArgument("-pr" + pvcsProject);
if (pvcsVersionLabel != null && !pvcsVersionLabel.equals("")) {
command.createArgument("-v" + pvcsVersionLabel);
}
if (pvcsPromotionGroup != null && !pvcsPromotionGroup.equals("")) {
command.createArgument("-g" + pvcsPromotionGroup);
}
command.createArgument("-z");
command.createArgument(pvcsSubProject);
return command;
}
protected void executeCommandline(Commandline command, PvcsStreamConsumer consumer) throws CruiseControlException {
LOG.info("Running command: " + command);
CommandExecutor executor = new CommandExecutor(command, LOG);
executor.setOutputConsumer(consumer);
executor.executeAndWait();
LOG.debug("Output: \n" + consumer.getOutput());
}
protected String getExecutable(final String exe) {
final StringBuilder correctedExe = new StringBuilder();
if (getPvcsbin() != null) {
if (getPvcsbin().endsWith(File.separator)) {
correctedExe.append(getPvcsbin());
} else {
correctedExe.append(getPvcsbin()).append(File.separator);
}
}
return correctedExe.append(exe).toString();
}
/**
* @return loginId
*/
public String getLoginid() {
return loginId;
}
/**
* Returns an {@link java.util.List List}of {@link Modification}s detailing all the changes between now and the
* last build.
*
* @param lastBuild
* the last build time
* @param now
* time now, or time to check
* @return the list of modifications, an empty (not null) list if no modifications or if developer had checked in
* files since quietPeriod seconds ago.
*
* Note: Internally uses external filesystem for files CruiseControlPVCS.pcli, files.tmp, vlog.txt
*/
public List<Modification> getModifications(final Date lastBuild, final Date now) {
final PvcsStreamConsumer consumer = new PvcsStreamConsumer(lastBuild, this.outDateFormat, pvcsProject,
this.archiveFileSuffix);
return getModifications(lastBuild, now, consumer);
}
protected List<Modification> getModifications(final Date lastBuild, final Date now,
final PvcsStreamConsumer consumer) {
try {
// build file of PVCS command line instructions
final String lastBuildDate = inDateFormat.format(lastBuild);
final String nowDate = inDateFormat.format(now);
final Commandline command = buildExecCommand(lastBuildDate, nowDate);
executeCommandline(command, consumer);
} catch (Exception e) {
LOG.error("Error in executing the PVCS command : ", e);
}
final List<Modification> modificationList = consumer.getModificationList();
if (!modificationList.isEmpty()) {
properties.modificationFound();
}
final StringBuilder msg = new StringBuilder("" + modificationList.size());
if (1 == modificationList.size()) {
msg.append(" modification has been detected");
} else {
msg.append(" modifications have been detected");
}
msg.append(" for ").append(pvcsSubProject).append(".");
LOG.info(msg.toString());
return modificationList;
}
public Map<String, String> getProperties() {
return properties.getPropertiesAndReset();
}
/**
* Get name of the PVCS bin directory
*
* @return String
*/
public String getPvcsbin() {
return pvcsbin;
}
public void setArchiveFileSuffix(String archiveSuffix) {
this.archiveFileSuffix = archiveSuffix;
}
public void setInDateFormat(String inDateFormat) {
this.inDateFormat = new SimpleDateFormat(inDateFormat);
}
/**
* @param loginId login id
*/
public void setLoginid(String loginId) {
this.loginId = loginId;
}
public void setOutDateFormat(String outDateFormat) {
this.outDateFormat = new SimpleDateFormat(outDateFormat);
}
public void setProperty(String propertyName) {
properties.assignPropertyName(propertyName);
}
/**
* Specifies the location of the PVCS bin directory
*
* @param bin
* Specifies the location of the PVCS bin directory
*/
public void setPvcsbin(String bin) {
this.pvcsbin = bin;
}
public void setPvcsproject(String project) {
pvcsProject = project;
}
public void setPvcspromotiongroup(String promotiongroup) {
pvcsPromotionGroup = promotiongroup;
}
public void setPvcssubproject(String subproject) {
pvcsSubProject = subproject;
}
public void setPvcsversionlabel(String versionlabel) {
pvcsVersionLabel = versionlabel;
}
public void validate() throws CruiseControlException {
ValidationHelper.assertIsSet(pvcsProject, "pvcsproject", this.getClass());
ValidationHelper.assertIsSet(pvcsSubProject, "pvcssubproject", this.getClass());
}
} // end class PVCSElement