/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.tools.ant.taskdefs.optional.vss; import java.io.File; import java.io.IOException; import java.text.DateFormat; import java.text.ParseException; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; import org.apache.tools.ant.taskdefs.Execute; import org.apache.tools.ant.taskdefs.LogStreamHandler; import org.apache.tools.ant.types.Commandline; import org.apache.tools.ant.types.EnumeratedAttribute; import org.apache.tools.ant.util.FileUtils; /** * A base class for creating tasks for executing commands on Visual SourceSafe. * <p> * The class extends the 'exec' task as it operates by executing the ss.exe program * supplied with SourceSafe. By default the task expects ss.exe to be in the path, * you can override this be specifying the ssdir attribute. * </p> * <p> * This class provides set and get methods for 'login' and 'vsspath' attributes. It * also contains constants for the flags that can be passed to SS. * </p> * */ public abstract class MSVSS extends Task implements MSVSSConstants { private String ssDir = null; private String vssLogin = null; private String vssPath = null; private String serverPath = null; /** Version */ private String version = null; /** Date */ private String date = null; /** Label */ private String label = null; /** Auto response */ private String autoResponse = null; /** Local path */ private String localPath = null; /** Comment */ private String comment = null; /** From label */ private String fromLabel = null; /** To label */ private String toLabel = null; /** Output file name */ private String outputFileName = null; /** User */ private String user = null; /** From date */ private String fromDate = null; /** To date */ private String toDate = null; /** History style */ private String style = null; /** Quiet defaults to false */ private boolean quiet = false; /** Recursive defaults to false */ private boolean recursive = false; /** Writable defaults to false */ private boolean writable = false; /** Fail on error defaults to true */ private boolean failOnError = true; /** Get local copy for checkout defaults to true */ private boolean getLocalCopy = true; /** Number of days offset for History */ private int numDays = Integer.MIN_VALUE; /** Date format for History */ private DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT); /** Timestamp for retreived files */ private CurrentModUpdated timestamp = null; /** Behaviour for writable files */ private WritableFiles writableFiles = null; /** * Each sub-class must implemnt this method and return the constructed * command line to be executed. It is up to the sub-task to determine the * required attrubutes and their order. * @return The Constructed command line. */ abstract Commandline buildCmdLine(); /** * Directory where <code>ss.exe</code> resides. * By default the task expects it to be in the PATH. * @param dir The directory containing ss.exe. */ public final void setSsdir(String dir) { this.ssDir = FileUtils.translatePath(dir); } /** * Login to use when accessing VSS, formatted as "username,password". * <p> * You can omit the password if your database is not password protected. * If you have a password and omit it, Ant will hang. * @param vssLogin The login string to use. */ public final void setLogin(final String vssLogin) { this.vssLogin = vssLogin; } /** * SourceSafe path which specifies the project/file(s) you wish to perform * the action on. * <p> * A prefix of 'vss://' will be removed if specified. * @param vssPath The VSS project path. * @ant.attribute group="required" */ public final void setVsspath(final String vssPath) { String projectPath; // CheckStyle:MagicNumber OFF if (vssPath.startsWith("vss://")) { //$NON-NLS-1$ projectPath = vssPath.substring(5); } else { projectPath = vssPath; } // CheckStyle:MagicNumber ON if (projectPath.startsWith(PROJECT_PREFIX)) { this.vssPath = projectPath; } else { this.vssPath = PROJECT_PREFIX + projectPath; } } /** * Directory where <code>srssafe.ini</code> resides. * @param serverPath The path to the VSS server. */ public final void setServerpath(final String serverPath) { this.serverPath = serverPath; } /** * Indicates if the build should fail if the Sourcesafe command does. Defaults to true. * @param failOnError True if task should fail on any error. */ public final void setFailOnError(final boolean failOnError) { this.failOnError = failOnError; } /** * Executes the task. <br> * Builds a command line to execute ss.exe and then calls Exec's run method * to execute the command line. * @throws BuildException if the command cannot execute. */ public void execute() throws BuildException { int result = 0; Commandline commandLine = buildCmdLine(); result = run(commandLine); if (Execute.isFailure(result) && getFailOnError()) { String msg = "Failed executing: " + formatCommandLine(commandLine) + " With a return code of " + result; throw new BuildException(msg, getLocation()); } } // Special setters for the sub-classes /** * Set the internal comment attribute. * @param comment the value to use. */ protected void setInternalComment(final String comment) { this.comment = comment; } /** * Set the auto response attribute. * @param autoResponse the value to use. */ protected void setInternalAutoResponse(final String autoResponse) { this.autoResponse = autoResponse; } /** * Set the date attribute. * @param date the value to use. */ protected void setInternalDate(final String date) { this.date = date; } /** * Set the date format attribute. * @param dateFormat the value to use. */ protected void setInternalDateFormat(final DateFormat dateFormat) { this.dateFormat = dateFormat; } /** * Set the failOnError attribute. * @param failOnError the value to use. */ protected void setInternalFailOnError(final boolean failOnError) { this.failOnError = failOnError; } /** * Set the from date attribute. * @param fromDate the value to use. */ protected void setInternalFromDate(final String fromDate) { this.fromDate = fromDate; } /** * Set the from label attribute. * @param fromLabel the value to use. */ protected void setInternalFromLabel(final String fromLabel) { this.fromLabel = fromLabel; } /** * Set the label attribute. * @param label the value to use. */ protected void setInternalLabel(final String label) { this.label = label; } /** * Set the local path comment attribute. * @param localPath the value to use. */ protected void setInternalLocalPath(final String localPath) { this.localPath = localPath; } /** * Set the num days attribute. * @param numDays the value to use. */ protected void setInternalNumDays(final int numDays) { this.numDays = numDays; } /** * Set the outputFileName comment attribute. * @param outputFileName the value to use. */ protected void setInternalOutputFilename(final String outputFileName) { this.outputFileName = outputFileName; } /** * Set the quiet attribute. * @param quiet the value to use. */ protected void setInternalQuiet(final boolean quiet) { this.quiet = quiet; } /** * Set the recursive attribute. * @param recursive the value to use. */ protected void setInternalRecursive(final boolean recursive) { this.recursive = recursive; } /** * Set the style attribute. * @param style the value to use. */ protected void setInternalStyle(final String style) { this.style = style; } /** * Set the to date attribute. * @param toDate the value to use. */ protected void setInternalToDate(final String toDate) { this.toDate = toDate; } /** * Set the to label attribute. * @param toLabel the value to use. */ protected void setInternalToLabel(final String toLabel) { this.toLabel = toLabel; } /** * Set the user attribute. * @param user the value to use. */ protected void setInternalUser(final String user) { this.user = user; } /** * Set the version attribute. * @param version the value to use. */ protected void setInternalVersion(final String version) { this.version = version; } /** * Set the writable attribute. * @param writable the value to use. */ protected void setInternalWritable(final boolean writable) { this.writable = writable; } /** * Set the timestamp attribute. * @param timestamp the value to use. */ protected void setInternalFileTimeStamp(final CurrentModUpdated timestamp) { this.timestamp = timestamp; } /** * Set the writableFiles attribute. * @param writableFiles the value to use. */ protected void setInternalWritableFiles(final WritableFiles writableFiles) { this.writableFiles = writableFiles; } /** * Set the getLocalCopy attribute. * @param getLocalCopy the value to use. */ protected void setInternalGetLocalCopy(final boolean getLocalCopy) { this.getLocalCopy = getLocalCopy; } /** * Gets the sscommand string. "ss" or "c:\path\to\ss" * @return The path to ss.exe or just ss if sscommand is not set. */ protected String getSSCommand() { if (ssDir == null) { return SS_EXE; } return ssDir.endsWith(File.separator) ? ssDir + SS_EXE : ssDir + File.separator + SS_EXE; } /** * Gets the vssserverpath string. * @return null if vssserverpath is not set. */ protected String getVsspath() { return vssPath; } /** * Gets the quiet string. -O- * @return An empty string if quiet is not set or is false. */ protected String getQuiet() { return quiet ? FLAG_QUIET : ""; } /** * Gets the recursive string. "-R" * @return An empty string if recursive is not set or is false. */ protected String getRecursive() { return recursive ? FLAG_RECURSION : ""; } /** * Gets the writable string. "-W" * @return An empty string if writable is not set or is false. */ protected String getWritable() { return writable ? FLAG_WRITABLE : ""; } /** * Gets the label string. "-Lbuild1" * Max label length is 32 chars * @return An empty string if label is not set. */ protected String getLabel() { String shortLabel = ""; if (label != null && label.length() > 0) { shortLabel = FLAG_LABEL + getShortLabel(); } return shortLabel; } /** * Return at most the 30 first chars of the label, * logging a warning message about the truncation * @return at most the 30 first chars of the label */ private String getShortLabel() { String shortLabel; // CheckStyle:MagicNumber OFF if (label != null && label.length() > 31) { shortLabel = this.label.substring(0, 30); log("Label is longer than 31 characters, truncated to: " + shortLabel, Project.MSG_WARN); } else { shortLabel = label; } // CheckStyle:MagicNumber ON return shortLabel; } /** * Gets the style string. "-Lbuild1" * @return An empty string if label is not set. */ protected String getStyle() { return style != null ? style : ""; } /** * Gets the version string. Returns the first specified of version "-V1.0", * date "-Vd01.01.01", label "-Vlbuild1". * @return An empty string if a version, date and label are not set. */ protected String getVersionDateLabel() { String versionDateLabel = ""; if (version != null) { versionDateLabel = FLAG_VERSION + version; } else if (date != null) { versionDateLabel = FLAG_VERSION_DATE + date; } else { // Use getShortLabel() so labels longer then 30 char are truncated // and the user is warned String shortLabel = getShortLabel(); if (shortLabel != null && !shortLabel.equals("")) { versionDateLabel = FLAG_VERSION_LABEL + shortLabel; } } return versionDateLabel; } /** * Gets the version string. * @return An empty string if a version is not set. */ protected String getVersion() { return version != null ? FLAG_VERSION + version : ""; } /** * Gets the localpath string. "-GLc:\source" <p> * The localpath is created if it didn't exist. * @return An empty string if localpath is not set. */ protected String getLocalpath() { String lclPath = ""; //set to empty str if no local path return if (localPath != null) { //make sure m_LocalDir exists, create it if it doesn't File dir = getProject().resolveFile(localPath); if (!dir.exists()) { boolean done = dir.mkdirs() || dir.exists(); if (!done) { String msg = "Directory " + localPath + " creation was not " + "successful for an unknown reason"; throw new BuildException(msg, getLocation()); } getProject().log("Created dir: " + dir.getAbsolutePath()); } lclPath = FLAG_OVERRIDE_WORKING_DIR + localPath; } return lclPath; } /** * Gets the comment string. "-Ccomment text" * @return A comment of "-" if comment is not set. */ protected String getComment() { return comment != null ? FLAG_COMMENT + comment : FLAG_COMMENT + "-"; } /** * Gets the auto response string. This can be Y "-I-Y" or N "-I-N". * @return The default value "-I-" if autoresponse is not set. */ protected String getAutoresponse() { if (autoResponse == null) { return FLAG_AUTORESPONSE_DEF; } if (autoResponse.equalsIgnoreCase("Y")) { return FLAG_AUTORESPONSE_YES; } else if (autoResponse.equalsIgnoreCase("N")) { return FLAG_AUTORESPONSE_NO; } else { return FLAG_AUTORESPONSE_DEF; } } /** * Gets the login string. This can be user and password, "-Yuser,password" * or just user "-Yuser". * @return An empty string if login is not set. */ protected String getLogin() { return vssLogin != null ? FLAG_LOGIN + vssLogin : ""; } /** * Gets the output file string. "-Ooutput.file" * @return An empty string if user is not set. */ protected String getOutput() { return outputFileName != null ? FLAG_OUTPUT + outputFileName : ""; } /** * Gets the user string. "-Uusername" * @return An empty string if user is not set. */ protected String getUser() { return user != null ? FLAG_USER + user : ""; } /** * Gets the version string. This can be to-from "-VLbuild2~Lbuild1", from * "~Lbuild1" or to "-VLbuild2". * @return An empty string if neither tolabel or fromlabel are set. */ protected String getVersionLabel() { if (fromLabel == null && toLabel == null) { return ""; } // CheckStyle:MagicNumber OFF if (fromLabel != null && toLabel != null) { if (fromLabel.length() > 31) { fromLabel = fromLabel.substring(0, 30); log("FromLabel is longer than 31 characters, truncated to: " + fromLabel, Project.MSG_WARN); } if (toLabel.length() > 31) { toLabel = toLabel.substring(0, 30); log("ToLabel is longer than 31 characters, truncated to: " + toLabel, Project.MSG_WARN); } return FLAG_VERSION_LABEL + toLabel + VALUE_FROMLABEL + fromLabel; } else if (fromLabel != null) { if (fromLabel.length() > 31) { fromLabel = fromLabel.substring(0, 30); log("FromLabel is longer than 31 characters, truncated to: " + fromLabel, Project.MSG_WARN); } return FLAG_VERSION + VALUE_FROMLABEL + fromLabel; } else { if (toLabel.length() > 31) { toLabel = toLabel.substring(0, 30); log("ToLabel is longer than 31 characters, truncated to: " + toLabel, Project.MSG_WARN); } return FLAG_VERSION_LABEL + toLabel; } // CheckStyle:MagicNumber ON } /** * Gets the Version date string. * @return An empty string if neither Todate or from date are set. * @throws BuildException if there is an error. */ protected String getVersionDate() throws BuildException { if (fromDate == null && toDate == null && numDays == Integer.MIN_VALUE) { return ""; } if (fromDate != null && toDate != null) { return FLAG_VERSION_DATE + toDate + VALUE_FROMDATE + fromDate; } else if (toDate != null && numDays != Integer.MIN_VALUE) { try { return FLAG_VERSION_DATE + toDate + VALUE_FROMDATE + calcDate(toDate, numDays); } catch (ParseException ex) { String msg = "Error parsing date: " + toDate; throw new BuildException(msg, getLocation()); } } else if (fromDate != null && numDays != Integer.MIN_VALUE) { try { return FLAG_VERSION_DATE + calcDate(fromDate, numDays) + VALUE_FROMDATE + fromDate; } catch (ParseException ex) { String msg = "Error parsing date: " + fromDate; throw new BuildException(msg, getLocation()); } } else { return fromDate != null ? FLAG_VERSION + VALUE_FROMDATE + fromDate : FLAG_VERSION_DATE + toDate; } } /** * Builds and returns the -G- flag if required. * @return An empty string if get local copy is true. */ protected String getGetLocalCopy() { return (!getLocalCopy) ? FLAG_NO_GET : ""; } /** * Gets the value of the fail on error flag. * @return True if the FailOnError flag has been set or if 'writablefiles=skip'. */ private boolean getFailOnError() { return getWritableFiles().equals(WRITABLE_SKIP) ? false : failOnError; } /** * Gets the value set for the FileTimeStamp. * if it equals "current" then we return -GTC * if it equals "modified" then we return -GTM * if it equals "updated" then we return -GTU * otherwise we return -GTC * * @return The default file time flag, if not set. */ public String getFileTimeStamp() { if (timestamp == null) { return ""; } else if (timestamp.getValue().equals(TIME_MODIFIED)) { return FLAG_FILETIME_MODIFIED; } else if (timestamp.getValue().equals(TIME_UPDATED)) { return FLAG_FILETIME_UPDATED; } else { return FLAG_FILETIME_DEF; } } /** * Gets the value to determine the behaviour when encountering writable files. * @return An empty String, if not set. */ public String getWritableFiles() { if (writableFiles == null) { return ""; } else if (writableFiles.getValue().equals(WRITABLE_REPLACE)) { return FLAG_REPLACE_WRITABLE; } else if (writableFiles.getValue().equals(WRITABLE_SKIP)) { // ss.exe exits with '100', when files have been skipped // so we have to ignore the failure failOnError = false; return FLAG_SKIP_WRITABLE; } else { return ""; } } /** * Sets up the required environment and executes the command line. * * @param cmd The command line to execute. * @return The return code from the exec'd process. */ private int run(Commandline cmd) { try { Execute exe = new Execute(new LogStreamHandler(this, Project.MSG_INFO, Project.MSG_WARN)); // If location of ss.ini is specified we need to set the // environment-variable SSDIR to this value if (serverPath != null) { String[] env = exe.getEnvironment(); if (env == null) { env = new String[0]; } String[] newEnv = new String[env.length + 1]; System.arraycopy(env, 0, newEnv, 0, env.length); newEnv[env.length] = "SSDIR=" + serverPath; exe.setEnvironment(newEnv); } exe.setAntRun(getProject()); exe.setWorkingDirectory(getProject().getBaseDir()); exe.setCommandline(cmd.getCommandline()); // Use the OS launcher so we get environment variables exe.setVMLauncher(false); return exe.execute(); } catch (IOException e) { throw new BuildException(e, getLocation()); } } /** * Calculates the start date for version comparison. * <p> * Calculates the date numDay days earlier than startdate. * @param startDate The start date. * @param daysToAdd The number of days to add. * @return The calculated date. * @throws ParseException */ private String calcDate(String startDate, int daysToAdd) throws ParseException { Calendar calendar = new GregorianCalendar(); Date currentDate = dateFormat.parse(startDate); calendar.setTime(currentDate); calendar.add(Calendar.DATE, daysToAdd); return dateFormat.format(calendar.getTime()); } /** * Changes the password to '***' so it isn't displayed on screen if the build fails * * @param cmd The command line to clean * @return The command line as a string with out the password */ private String formatCommandLine(Commandline cmd) { StringBuffer sBuff = new StringBuffer(cmd.toString()); int indexUser = sBuff.substring(0).indexOf(FLAG_LOGIN); if (indexUser > 0) { int indexPass = sBuff.substring(0).indexOf(",", indexUser); int indexAfterPass = sBuff.substring(0).indexOf(" ", indexPass); for (int i = indexPass + 1; i < indexAfterPass; i++) { sBuff.setCharAt(i, '*'); } } return sBuff.toString(); } /** * Extension of EnumeratedAttribute to hold the values for file time stamp. */ public static class CurrentModUpdated extends EnumeratedAttribute { /** * Gets the list of allowable values. * @return The values. */ public String[] getValues() { return new String[] {TIME_CURRENT, TIME_MODIFIED, TIME_UPDATED}; } } /** * Extension of EnumeratedAttribute to hold the values for writable filess. */ public static class WritableFiles extends EnumeratedAttribute { /** * Gets the list of allowable values. * @return The values. */ public String[] getValues() { return new String[] {WRITABLE_REPLACE, WRITABLE_SKIP, WRITABLE_FAIL}; } } }