/********************************************************************************
* CruiseControl, a Continuous Integration Toolkit
* Copyright (c) 2001-2007, 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.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import net.sourceforge.cruisecontrol.CruiseControlException;
import net.sourceforge.cruisecontrol.Modification;
import net.sourceforge.cruisecontrol.SourceControl;
import net.sourceforge.cruisecontrol.util.Commandline;
import net.sourceforge.cruisecontrol.util.Processes;
import net.sourceforge.cruisecontrol.util.StreamConsumer;
import net.sourceforge.cruisecontrol.util.StreamLogger;
import net.sourceforge.cruisecontrol.util.ValidationHelper;
import org.apache.log4j.Logger;
/**
* This class implements the SourceControlElement methods for a MKS repository.
* The call to MKS is assumed to work with any setup:
* The call to MKS login should be done prior to calling this class.
* *
* attributes:
* localWorkingDir - local directory for the sandbox
* project - the name and path to the MKS project
* doNothing - if this attribute is set to true, no mks command is executed. This is for
* testing purposes, if a potentially slow mks server connection should avoid
*
* @author Suresh K Bathala Skila, Inc.
* @author Dominik Hirt, Wincor-Nixdorf International GmbH, Leipzig
*/
public class MKS implements SourceControl {
private static final Logger LOG = Logger.getLogger(MKS.class);
private SourceControlProperties properties = new SourceControlProperties();
private String project;
private File localWorkingDir;
/**
* if this attribute is set to true, no mks command is executed. This is for
* testing purposes, if a potentially slow mks server connection should
* avoid
*/
private boolean doNothing;
/**
* This is the workaround for the missing feature of MKS to return differences
* for a given time period. If a modification is detected during the quietperiod,
* CruiseControl calls <code>getModifications</code> of this sourcecontrol object
* again, with the new values for <code>Date now</code>. In that case, and if all
* modification are already found in the first cycle, the list of modifications
* becomes empty. Therefor, we have to return the summarized list of modifications:
* the values from the last run, and -maybe- results return by MKS for this run.
*/
private final List<Modification> listOfModifications = new ArrayList<Modification>();
/**
* The listOfModifications is cleared when new value for lastBuild is used.
*/
private Date lastBuild = new Date();
public void setProject(String project) {
this.project = project;
}
/**
* Sets the local working copy to use when making calls to MKS.
*
* @param local
* String indicating the relative or absolute path to the local
* working copy of the module of which to find the log history.
*/
public void setLocalWorkingDir(String local) {
localWorkingDir = new File(local);
}
public void setProperty(String property) {
properties.assignPropertyName(property);
}
public Map<String, String> getProperties() {
return properties.getPropertiesAndReset();
}
public void setDoNothing(String doNothing) {
this.doNothing = Boolean.valueOf(doNothing);
}
public void validate() throws CruiseControlException {
ValidationHelper.assertIsSet(localWorkingDir, "localWorkingDir", this.getClass());
ValidationHelper.assertIsSet(project, "project", this.getClass());
}
/**
* Returns an ArrayList of Modifications.
* MKS ignores dates for such a range so therefor ALL
* modifications since the last resynch step are returned.
*
* @param lastBuild
* Last build time.
* @param now
* Time now, or time to check.
* @return maybe empty, never null.
*/
public List<Modification> getModifications(final Date lastBuild, final Date now) {
if (doNothing) {
properties.modificationFound();
return listOfModifications;
}
if (this.lastBuild.compareTo(lastBuild) != 0) {
listOfModifications.clear();
this.lastBuild = lastBuild;
}
final String projectFilePath = getProjectFilePath();
final Commandline cmdLine = createResyncCommandLine(projectFilePath);
executeResyncAndParseModifications(cmdLine, listOfModifications);
return listOfModifications;
}
void executeResyncAndParseModifications(final Commandline cmdLine, final List<Modification> modifications) {
try {
final StreamConsumer stderr = new ModificationsConsumer(modifications);
final StreamConsumer stdout = StreamLogger.getWarnLogger(LOG);
Processes.waitFor(cmdLine.execute(), stdout, stderr);
} catch (Exception ex) {
LOG.warn(ex.getMessage(), ex);
}
LOG.info("resync finished");
}
Commandline createResyncCommandLine(final String projectFilePath) {
final Commandline cmdLine = new Commandline();
cmdLine.setExecutable("si");
cmdLine.createArgument("resync");
cmdLine.createArgument("-f");
cmdLine.createArgument("-R");
cmdLine.createArgument("-S");
cmdLine.createArgument(projectFilePath);
try {
cmdLine.setWorkingDir(localWorkingDir);
} catch (CruiseControlException e) {
throw new RuntimeException(e);
}
return cmdLine;
}
String getProjectFilePath() {
final String projectFilePath = localWorkingDir.getAbsolutePath() + File.separator + project;
if (!new File(projectFilePath).exists()) {
throw new RuntimeException("project file not found at " + projectFilePath);
}
return projectFilePath;
}
/**
* Sample output:
* dominik.hirt;add forceDeploy peter.neumcke;path to properties file fixed,
* copy generated properties Member added to project
* d:/MKS/PCE_Usedom/Products/Info/Info.pj
*
* @param modification not sure
* @param folderName not sure
* @param fileName not sure
*/
private void setUserNameAndComment(final Modification modification,
final String folderName, final String fileName) {
try {
final Commandline commandLine = new Commandline();
commandLine.setExecutable("si");
if (localWorkingDir != null) {
commandLine.setWorkingDirectory(localWorkingDir.getAbsolutePath());
}
commandLine.createArgument("rlog");
commandLine.createArgument("--format={author};{description}");
commandLine.createArgument("--noHeaderFormat");
commandLine.createArgument("--noTrailerFormat");
commandLine.createArguments("-r", modification.revision);
commandLine.createArgument(folderName + File.separator + fileName);
final Process proc = commandLine.execute();
final UserAndCommentConsumer userData = new UserAndCommentConsumer();
Processes.waitFor(proc, userData, StreamLogger.getWarnLogger(LOG));
if (userData.wasFound()) {
modification.userName = userData.getUserName();
modification.comment = userData.getComment();
} else {
LOG.warn("could not find username or comment for " + fileName + " r" + modification.revision);
modification.userName = "";
modification.comment = "";
}
} catch (Exception e) {
LOG.warn(e.getMessage(), e);
modification.userName = "";
modification.comment = "";
}
}
/* Sample output on stderr:
* output: Connecting to baswmks1:7001 ... Connecting to baswmks1:7001
* as dominik.hirt ... Resynchronizing files...
* c:\temp\test\Admin\ComponentBuild\antfile.xml
* c:\temp\test\Admin\PCEAdminCommand\projectbuild.properties: checked
* out revision 1.1
*/
private final class ModificationsConsumer implements StreamConsumer {
public final List<Modification> modifications;
private ModificationsConsumer(final List<Modification> modifications) {
this.modifications = modifications;
}
public void consumeLine(final String line) {
final int idxCheckedOutRevision = line
.indexOf(": checked out revision");
if (idxCheckedOutRevision == -1) {
return;
}
LOG.info(line);
final int idxSeparator = line.lastIndexOf(File.separator);
final String folderName = line.substring(0, idxSeparator);
final String fileName = line.substring(idxSeparator + 1,
idxCheckedOutRevision);
final Modification modification = new Modification();
final Modification.ModifiedFile modFile = modification
.createModifiedFile(fileName, folderName);
modification.modifiedTime = new Date(new File(folderName,
fileName).lastModified());
modFile.revision = line.substring(idxCheckedOutRevision + 23);
modification.revision = modFile.revision;
setUserNameAndComment(modification, folderName, fileName);
modifications.add(modification);
properties.modificationFound();
}
}
private static class UserAndCommentConsumer implements StreamConsumer {
private boolean found;
private String userName;
private String comment;
public boolean wasFound() {
return found;
}
public String getUserName() {
return userName;
}
public String getComment() {
return comment;
}
public void consumeLine(final String line) {
if (found) {
return;
}
final int idx = line.indexOf(";");
if (idx == -1) {
LOG.debug(line);
return;
}
found = true;
userName = line.substring(0, idx);
if (idx < line.length()) {
comment = line.substring(idx + 1);
} else {
comment = "";
}
}
}
}