/********************************************************************************
* 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.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
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.util.StreamPumper;
import net.sourceforge.cruisecontrol.util.StreamLogger;
import net.sourceforge.cruisecontrol.util.Commandline;
import net.sourceforge.cruisecontrol.util.IO;
import org.apache.log4j.Logger;
/**
* This defines a child element for the ModificationSet element.
*
* @author Matt Harp
*/
public class SSCM implements net.sourceforge.cruisecontrol.SourceControl {
private static final Logger LOG = Logger.getLogger(SSCM.class);
private final SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss");
private final SSCMCLIStringParam strparamBranch = new SSCMCLIStringParam("branch", "-b", false);
private final SSCMCLIStringParam strparamRepository = new SSCMCLIStringParam("repository", "-p", false);
private final SSCMCLIStringParam strparamFile = new SSCMCLIStringParam("file", "", false);
private final SSCMCLIStringParam strparamServerConnect = new SSCMCLIStringParam("serverconnect", "-z", false);
private final SSCMCLIStringParam strparamServerLogin = new SSCMCLIStringParam("serverlogin", "-y", false);
private final SSCMCLIBoolParam fparamSearchRegExp = new SSCMCLIBoolParam("searchregexp", "-x", false);
private final SSCMCLIBoolParam fparamRecursive = new SSCMCLIBoolParam("recursive", "-r", false);
private final SourceControlProperties properties = new SourceControlProperties();
public void validate() throws CruiseControlException { /* nothing is required */ }
public void setBranch(String str) {
strparamBranch.setData(str);
}
public void setRepository(String str) {
strparamRepository.setData(str);
}
public void setFile(String str) {
strparamFile.setData(str);
}
public void setServerConnect(String str) {
strparamServerConnect.setData(str);
}
public void setServerLogin(String str) {
strparamServerLogin.setData(str);
}
public void setSearchRegExp(String str) {
if (str.equals("1")) {
fparamSearchRegExp.setData(null);
}
}
public void setRecursive(String str) {
if (str.equals("1")) {
fparamRecursive.setData(null);
}
}
public List<Modification> getModifications(final Date lastBuild, final Date now) {
final List<SSCMCLIParam> paramList = new ArrayList<SSCMCLIParam>();
if (!strparamFile.isSet()) {
strparamFile.setData("/");
}
paramList.add(strparamFile);
paramList.add(strparamBranch);
paramList.add(strparamRepository);
paramList.add(fparamRecursive);
paramList.add(fparamSearchRegExp);
paramList.add(strparamServerLogin);
paramList.add(strparamServerConnect);
List<Modification> listMods = executeCLICommand(paramList, buildDateTimeRangeCLIParam(lastBuild, now));
if (listMods == null) {
listMods = Collections.emptyList();
}
if (!listMods.isEmpty()) {
properties.modificationFound();
}
return listMods;
}
public Map<String, String> getProperties() {
return properties.getPropertiesAndReset();
}
public void setProperty(String property) {
properties.assignPropertyName(property);
}
protected List<Modification> executeCLICommand(final List<SSCMCLIParam> paramList, final String strDTRangeParam) {
List<Modification> listMods = null;
final Commandline command = new Commandline();
command.setExecutable("sscm");
command.createArgument().setValue("cc");
// Next, we just iterate through the list, adding entries.
boolean fAllRequirementsMet = true;
for (int i = 0; i < paramList.size() && fAllRequirementsMet; ++i) {
final SSCMCLIParam param = paramList.get(i);
if (param != null) {
if (param.checkRequired()) {
final String str = param.getFormatted();
if (str != null) {
command.createArgument().setValue(str);
LOG.debug("Added cmd part: " + str);
}
} else {
fAllRequirementsMet = false;
LOG.error("Required parameter '" + param.getParamName() + "' is missing!");
}
}
}
if (fAllRequirementsMet) {
command.createArgument().setValue(strDTRangeParam);
LOG.debug("Added DTRangeParam: " + strDTRangeParam);
try {
final Process process = command.execute();
// logs process error stream at info level
final Thread stderr = new Thread(new StreamPumper(process.getErrorStream(),
StreamLogger.getInfoLogger(LOG)));
stderr.start();
final InputStream input = process.getInputStream();
listMods = parseCLIOutput(input);
process.waitFor();
stderr.join();
IO.close(process);
} catch (IOException e) {
LOG.error("Problem trying to execute command line process", e);
} catch (InterruptedException e) {
LOG.error("Problem trying to execute command line process", e);
}
}
return listMods;
}
protected List<Modification> parseCLIOutput(final InputStream input) throws IOException {
final List<Modification> listMods = new ArrayList<Modification>();
final BufferedReader reader = new BufferedReader(new InputStreamReader(input));
String line = reader.readLine();
LOG.debug("\nSSCM mod line: " + line + "\n");
// -meh. Kind of lame, but total-0 will work.
if (!"total-0".equals(line)) {
while ((line = reader.readLine()) != null) {
final Modification mod = parseOutputLine(line);
if (mod != null) {
listMods.add(mod);
}
}
}
return listMods;
}
protected Modification parseOutputLine(final String str) {
LOG.debug("Output-" + str + "-\n");
if (str == null || str.length() == 0) {
return null;
}
Modification mod = new Modification("sscm");
final Modification.ModifiedFile modfile = mod.createModifiedFile(null, null);
boolean fValid = false;
final String strToken = "><";
int iLeft = 1;
// Repository
int iRight = str.indexOf(strToken, iLeft);
if (iRight > iLeft) {
modfile.folderName = str.substring(iLeft, iRight);
iLeft = iRight + strToken.length();
// Filename
iRight = str.indexOf(strToken, iLeft);
if (iRight > iLeft) {
modfile.fileName = str.substring(iLeft, iRight);
iLeft = iRight + strToken.length();
// Revision
iRight = str.indexOf(strToken, iLeft);
if (iRight > iLeft) {
mod.revision = str.substring(iLeft, iRight);
iLeft = iRight + strToken.length();
// Event
iRight = str.indexOf(strToken, iLeft);
if (iRight > iLeft) {
modfile.action = str.substring(iLeft, iRight);
iLeft = iRight + strToken.length();
// Date
iRight = str.indexOf(strToken, iLeft);
if (iRight > iLeft) {
mod.modifiedTime = buildDateTimeFromCLIOutput(str.substring(iLeft, iRight));
iLeft = iRight + strToken.length();
// Comment
iRight = str.indexOf(strToken, iLeft);
if (iRight >= iLeft) {
mod.comment = str.substring(iLeft, iRight);
iLeft = iRight + strToken.length();
// User
iRight = str.indexOf(strToken, iLeft);
if (iRight > iLeft) {
mod.userName = str.substring(iLeft, iRight);
iLeft = iRight + strToken.length();
// Email
iRight = str.indexOf(">", iLeft);
if (iRight >= iLeft) {
mod.emailAddress = str.substring(iLeft, iRight);
fValid = true;
}
}
}
}
}
}
}
}
if (!fValid) {
mod = null;
LOG.debug("Invalid output; skipping this entry");
}
return (mod);
}
protected String buildDateTimeRangeCLIParam(final Date lastBuild, final Date now) {
final String strLast = formatter.format(lastBuild);
final String strNow = formatter.format(now);
return "-d" + strLast + ":" + strNow;
}
protected Date buildDateTimeFromCLIOutput(final String str) {
Date dt;
try {
dt = formatter.parse(str);
} catch (ParseException e) {
dt = null;
LOG.error("Unable to parse DateTime from Surround", e);
}
return dt;
}
public abstract static class SSCMCLIParam {
public SSCMCLIParam(String strParamNameIN, String strParamIN, boolean fIsRequiredIN) {
strParamName = strParamNameIN;
strParam = strParamIN;
fIsRequired = fIsRequiredIN;
fIsSet = false;
}
public String getParamName() {
return (strParamName);
}
public String getParam() {
return (strParam);
}
public void setRequired(boolean f) {
fIsRequired = f;
}
public boolean isRequired() {
return fIsRequired;
}
public boolean isSet() {
return fIsSet;
}
public boolean checkRequired() {
return !(isRequired() && !isSet());
}
public abstract String getFormatted();
public abstract void setData(Object obj);
protected void setSet(boolean f) {
fIsSet = f;
}
private final String strParamName;
private final String strParam;
private boolean fIsRequired;
private boolean fIsSet;
}
public static class SSCMCLIBoolParam extends SSCMCLIParam {
public SSCMCLIBoolParam(String strParamNameIN, String strParamIN, boolean fIsRequiredIN) {
super(strParamNameIN, strParamIN, fIsRequiredIN);
}
public void setData(Object obj) {
fData = true;
setSet(true);
}
public String getFormatted() {
String str = null;
if (isSet() && fData) {
str = getParam();
}
return str;
}
private boolean fData;
}
public static class SSCMCLIStringParam extends SSCMCLIParam {
public SSCMCLIStringParam(String strParamNameIN, String strParamIN, boolean fIsRequiredIN) {
super(strParamNameIN, strParamIN, fIsRequiredIN);
}
public void setData(Object obj) {
strData = (String) obj;
setSet(true);
}
public String getFormatted() {
String str = null;
if (isSet()) {
str = getParam() + strData;
}
return str;
}
private String strData;
}
}