001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.tools.ant.taskdefs.optional.vss;
020:
021: import org.apache.tools.ant.types.EnumeratedAttribute;
022: import java.io.File;
023: import java.io.IOException;
024: import java.text.DateFormat;
025: import java.text.ParseException;
026: import java.util.Calendar;
027: import java.util.Date;
028: import java.util.GregorianCalendar;
029:
030: import org.apache.tools.ant.BuildException;
031: import org.apache.tools.ant.Project;
032: import org.apache.tools.ant.Task;
033: import org.apache.tools.ant.taskdefs.Execute;
034: import org.apache.tools.ant.taskdefs.LogStreamHandler;
035: import org.apache.tools.ant.types.Commandline;
036: import org.apache.tools.ant.util.FileUtils;
037:
038: /**
039: * A base class for creating tasks for executing commands on Visual SourceSafe.
040: * <p>
041: * The class extends the 'exec' task as it operates by executing the ss.exe program
042: * supplied with SourceSafe. By default the task expects ss.exe to be in the path,
043: * you can override this be specifying the ssdir attribute.
044: * </p>
045: * <p>
046: * This class provides set and get methods for 'login' and 'vsspath' attributes. It
047: * also contains constants for the flags that can be passed to SS.
048: * </p>
049: *
050: */
051: public abstract class MSVSS extends Task implements MSVSSConstants {
052:
053: private String ssDir = null;
054: private String vssLogin = null;
055: private String vssPath = null;
056: private String serverPath = null;
057:
058: /** Version */
059: private String version = null;
060: /** Date */
061: private String date = null;
062: /** Label */
063: private String label = null;
064: /** Auto response */
065: private String autoResponse = null;
066: /** Local path */
067: private String localPath = null;
068: /** Comment */
069: private String comment = null;
070: /** From label */
071: private String fromLabel = null;
072: /** To label */
073: private String toLabel = null;
074: /** Output file name */
075: private String outputFileName = null;
076: /** User */
077: private String user = null;
078: /** From date */
079: private String fromDate = null;
080: /** To date */
081: private String toDate = null;
082: /** History style */
083: private String style = null;
084: /** Quiet defaults to false */
085: private boolean quiet = false;
086: /** Recursive defaults to false */
087: private boolean recursive = false;
088: /** Writable defaults to false */
089: private boolean writable = false;
090: /** Fail on error defaults to true */
091: private boolean failOnError = true;
092: /** Get local copy for checkout defaults to true */
093: private boolean getLocalCopy = true;
094: /** Number of days offset for History */
095: private int numDays = Integer.MIN_VALUE;
096: /** Date format for History */
097: private DateFormat dateFormat = DateFormat
098: .getDateInstance(DateFormat.SHORT);
099: /** Timestamp for retreived files */
100: private CurrentModUpdated timestamp = null;
101: /** Behaviour for writable files */
102: private WritableFiles writableFiles = null;
103:
104: /**
105: * Each sub-class must implemnt this method and return the constructed
106: * command line to be executed. It is up to the sub-task to determine the
107: * required attrubutes and their order.
108: * @return The Constructed command line.
109: */
110: abstract Commandline buildCmdLine();
111:
112: /**
113: * Directory where <code>ss.exe</code> resides.
114: * By default the task expects it to be in the PATH.
115: * @param dir The directory containing ss.exe.
116: */
117: public final void setSsdir(String dir) {
118: this .ssDir = FileUtils.translatePath(dir);
119: }
120:
121: /**
122: * Login to use when accessing VSS, formatted as "username,password".
123: * <p>
124: * You can omit the password if your database is not password protected.
125: * If you have a password and omit it, Ant will hang.
126: * @param vssLogin The login string to use.
127: */
128: public final void setLogin(final String vssLogin) {
129: this .vssLogin = vssLogin;
130: }
131:
132: /**
133: * SourceSafe path which specifies the project/file(s) you wish to perform
134: * the action on.
135: * <p>
136: * A prefix of 'vss://' will be removed if specified.
137: * @param vssPath The VSS project path.
138: * @ant.attribute group="required"
139: */
140: public final void setVsspath(final String vssPath) {
141: String projectPath;
142: if (vssPath.startsWith("vss://")) { //$NON-NLS-1$
143: projectPath = vssPath.substring(5);
144: } else {
145: projectPath = vssPath;
146: }
147:
148: if (projectPath.startsWith(PROJECT_PREFIX)) {
149: this .vssPath = projectPath;
150: } else {
151: this .vssPath = PROJECT_PREFIX + projectPath;
152: }
153: }
154:
155: /**
156: * Directory where <code>srssafe.ini</code> resides.
157: * @param serverPath The path to the VSS server.
158: */
159: public final void setServerpath(final String serverPath) {
160: this .serverPath = serverPath;
161: }
162:
163: /**
164: * Indicates if the build should fail if the Sourcesafe command does. Defaults to true.
165: * @param failOnError True if task should fail on any error.
166: */
167: public final void setFailOnError(final boolean failOnError) {
168: this .failOnError = failOnError;
169: }
170:
171: /**
172: * Executes the task. <br>
173: * Builds a command line to execute ss.exe and then calls Exec's run method
174: * to execute the command line.
175: * @throws BuildException if the command cannot execute.
176: */
177: public void execute() throws BuildException {
178: int result = 0;
179: Commandline commandLine = buildCmdLine();
180: result = run(commandLine);
181: if (Execute.isFailure(result) && getFailOnError()) {
182: String msg = "Failed executing: "
183: + formatCommandLine(commandLine)
184: + " With a return code of " + result;
185: throw new BuildException(msg, getLocation());
186: }
187: }
188:
189: // Special setters for the sub-classes
190:
191: /**
192: * Set the internal comment attribute.
193: * @param comment the value to use.
194: */
195: protected void setInternalComment(final String comment) {
196: this .comment = comment;
197: }
198:
199: /**
200: * Set the auto response attribute.
201: * @param autoResponse the value to use.
202: */
203: protected void setInternalAutoResponse(final String autoResponse) {
204: this .autoResponse = autoResponse;
205: }
206:
207: /**
208: * Set the date attribute.
209: * @param date the value to use.
210: */
211: protected void setInternalDate(final String date) {
212: this .date = date;
213: }
214:
215: /**
216: * Set the date format attribute.
217: * @param dateFormat the value to use.
218: */
219: protected void setInternalDateFormat(final DateFormat dateFormat) {
220: this .dateFormat = dateFormat;
221: }
222:
223: /**
224: * Set the failOnError attribute.
225: * @param failOnError the value to use.
226: */
227: protected void setInternalFailOnError(final boolean failOnError) {
228: this .failOnError = failOnError;
229: }
230:
231: /**
232: * Set the from date attribute.
233: * @param fromDate the value to use.
234: */
235: protected void setInternalFromDate(final String fromDate) {
236: this .fromDate = fromDate;
237: }
238:
239: /**
240: * Set the from label attribute.
241: * @param fromLabel the value to use.
242: */
243: protected void setInternalFromLabel(final String fromLabel) {
244: this .fromLabel = fromLabel;
245: }
246:
247: /**
248: * Set the label attribute.
249: * @param label the value to use.
250: */
251: protected void setInternalLabel(final String label) {
252: this .label = label;
253: }
254:
255: /**
256: * Set the local path comment attribute.
257: * @param localPath the value to use.
258: */
259: protected void setInternalLocalPath(final String localPath) {
260: this .localPath = localPath;
261: }
262:
263: /**
264: * Set the num days attribute.
265: * @param numDays the value to use.
266: */
267: protected void setInternalNumDays(final int numDays) {
268: this .numDays = numDays;
269: }
270:
271: /**
272: * Set the outputFileName comment attribute.
273: * @param outputFileName the value to use.
274: */
275: protected void setInternalOutputFilename(final String outputFileName) {
276: this .outputFileName = outputFileName;
277: }
278:
279: /**
280: * Set the quiet attribute.
281: * @param quiet the value to use.
282: */
283: protected void setInternalQuiet(final boolean quiet) {
284: this .quiet = quiet;
285: }
286:
287: /**
288: * Set the recursive attribute.
289: * @param recursive the value to use.
290: */
291: protected void setInternalRecursive(final boolean recursive) {
292: this .recursive = recursive;
293: }
294:
295: /**
296: * Set the style attribute.
297: * @param style the value to use.
298: */
299: protected void setInternalStyle(final String style) {
300: this .style = style;
301: }
302:
303: /**
304: * Set the to date attribute.
305: * @param toDate the value to use.
306: */
307: protected void setInternalToDate(final String toDate) {
308: this .toDate = toDate;
309: }
310:
311: /**
312: * Set the to label attribute.
313: * @param toLabel the value to use.
314: */
315: protected void setInternalToLabel(final String toLabel) {
316: this .toLabel = toLabel;
317: }
318:
319: /**
320: * Set the user attribute.
321: * @param user the value to use.
322: */
323: protected void setInternalUser(final String user) {
324: this .user = user;
325: }
326:
327: /**
328: * Set the version attribute.
329: * @param version the value to use.
330: */
331: protected void setInternalVersion(final String version) {
332: this .version = version;
333: }
334:
335: /**
336: * Set the writable attribute.
337: * @param writable the value to use.
338: */
339: protected void setInternalWritable(final boolean writable) {
340: this .writable = writable;
341: }
342:
343: /**
344: * Set the timestamp attribute.
345: * @param timestamp the value to use.
346: */
347: protected void setInternalFileTimeStamp(
348: final CurrentModUpdated timestamp) {
349: this .timestamp = timestamp;
350: }
351:
352: /**
353: * Set the writableFiles attribute.
354: * @param writableFiles the value to use.
355: */
356: protected void setInternalWritableFiles(
357: final WritableFiles writableFiles) {
358: this .writableFiles = writableFiles;
359: }
360:
361: /**
362: * Set the getLocalCopy attribute.
363: * @param getLocalCopy the value to use.
364: */
365: protected void setInternalGetLocalCopy(final boolean getLocalCopy) {
366: this .getLocalCopy = getLocalCopy;
367: }
368:
369: /**
370: * Gets the sscommand string. "ss" or "c:\path\to\ss"
371: * @return The path to ss.exe or just ss if sscommand is not set.
372: */
373: protected String getSSCommand() {
374: if (ssDir == null) {
375: return SS_EXE;
376: }
377: return ssDir.endsWith(File.separator) ? ssDir + SS_EXE : ssDir
378: + File.separator + SS_EXE;
379: }
380:
381: /**
382: * Gets the vssserverpath string.
383: * @return null if vssserverpath is not set.
384: */
385: protected String getVsspath() {
386: return vssPath;
387: }
388:
389: /**
390: * Gets the quiet string. -O-
391: * @return An empty string if quiet is not set or is false.
392: */
393: protected String getQuiet() {
394: return quiet ? FLAG_QUIET : "";
395: }
396:
397: /**
398: * Gets the recursive string. "-R"
399: * @return An empty string if recursive is not set or is false.
400: */
401: protected String getRecursive() {
402: return recursive ? FLAG_RECURSION : "";
403: }
404:
405: /**
406: * Gets the writable string. "-W"
407: * @return An empty string if writable is not set or is false.
408: */
409: protected String getWritable() {
410: return writable ? FLAG_WRITABLE : "";
411: }
412:
413: /**
414: * Gets the label string. "-Lbuild1"
415: * Max label length is 32 chars
416: * @return An empty string if label is not set.
417: */
418: protected String getLabel() {
419: String shortLabel = "";
420: if (label != null && label.length() > 0) {
421: shortLabel = FLAG_LABEL + getShortLabel();
422: }
423: return shortLabel;
424: }
425:
426: /**
427: * Return at most the 30 first chars of the label,
428: * logging a warning message about the truncation
429: * @return at most the 30 first chars of the label
430: */
431: private String getShortLabel() {
432: String shortLabel;
433: if (label != null && label.length() > 31) {
434: shortLabel = this .label.substring(0, 30);
435: log("Label is longer than 31 characters, truncated to: "
436: + shortLabel, Project.MSG_WARN);
437: } else {
438: shortLabel = label;
439: }
440: return shortLabel;
441: }
442:
443: /**
444: * Gets the style string. "-Lbuild1"
445: * @return An empty string if label is not set.
446: */
447: protected String getStyle() {
448: return style != null ? style : "";
449: }
450:
451: /**
452: * Gets the version string. Returns the first specified of version "-V1.0",
453: * date "-Vd01.01.01", label "-Vlbuild1".
454: * @return An empty string if a version, date and label are not set.
455: */
456: protected String getVersionDateLabel() {
457: String versionDateLabel = "";
458: if (version != null) {
459: versionDateLabel = FLAG_VERSION + version;
460: } else if (date != null) {
461: versionDateLabel = FLAG_VERSION_DATE + date;
462: } else {
463: // Use getShortLabel() so labels longer then 30 char are truncated
464: // and the user is warned
465: String shortLabel = getShortLabel();
466: if (shortLabel != null && !shortLabel.equals("")) {
467: versionDateLabel = FLAG_VERSION_LABEL + shortLabel;
468: }
469: }
470: return versionDateLabel;
471: }
472:
473: /**
474: * Gets the version string.
475: * @return An empty string if a version is not set.
476: */
477: protected String getVersion() {
478: return version != null ? FLAG_VERSION + version : "";
479: }
480:
481: /**
482: * Gets the localpath string. "-GLc:\source" <p>
483: * The localpath is created if it didn't exist.
484: * @return An empty string if localpath is not set.
485: */
486: protected String getLocalpath() {
487: String lclPath = ""; //set to empty str if no local path return
488: if (localPath != null) {
489: //make sure m_LocalDir exists, create it if it doesn't
490: File dir = getProject().resolveFile(localPath);
491: if (!dir.exists()) {
492: boolean done = dir.mkdirs();
493: if (!done) {
494: String msg = "Directory " + localPath
495: + " creation was not "
496: + "successful for an unknown reason";
497: throw new BuildException(msg, getLocation());
498: }
499: getProject().log(
500: "Created dir: " + dir.getAbsolutePath());
501: }
502: lclPath = FLAG_OVERRIDE_WORKING_DIR + localPath;
503: }
504: return lclPath;
505: }
506:
507: /**
508: * Gets the comment string. "-Ccomment text"
509: * @return A comment of "-" if comment is not set.
510: */
511: protected String getComment() {
512: return comment != null ? FLAG_COMMENT + comment : FLAG_COMMENT
513: + "-";
514: }
515:
516: /**
517: * Gets the auto response string. This can be Y "-I-Y" or N "-I-N".
518: * @return The default value "-I-" if autoresponse is not set.
519: */
520: protected String getAutoresponse() {
521: if (autoResponse == null) {
522: return FLAG_AUTORESPONSE_DEF;
523: } else if (autoResponse.equalsIgnoreCase("Y")) {
524: return FLAG_AUTORESPONSE_YES;
525: } else if (autoResponse.equalsIgnoreCase("N")) {
526: return FLAG_AUTORESPONSE_NO;
527: } else {
528: return FLAG_AUTORESPONSE_DEF;
529: }
530: }
531:
532: /**
533: * Gets the login string. This can be user and password, "-Yuser,password"
534: * or just user "-Yuser".
535: * @return An empty string if login is not set.
536: */
537: protected String getLogin() {
538: return vssLogin != null ? FLAG_LOGIN + vssLogin : "";
539: }
540:
541: /**
542: * Gets the output file string. "-Ooutput.file"
543: * @return An empty string if user is not set.
544: */
545: protected String getOutput() {
546: return outputFileName != null ? FLAG_OUTPUT + outputFileName
547: : "";
548: }
549:
550: /**
551: * Gets the user string. "-Uusername"
552: * @return An empty string if user is not set.
553: */
554: protected String getUser() {
555: return user != null ? FLAG_USER + user : "";
556: }
557:
558: /**
559: * Gets the version string. This can be to-from "-VLbuild2~Lbuild1", from
560: * "~Lbuild1" or to "-VLbuild2".
561: * @return An empty string if neither tolabel or fromlabel are set.
562: */
563: protected String getVersionLabel() {
564: if (fromLabel == null && toLabel == null) {
565: return "";
566: }
567: if (fromLabel != null && toLabel != null) {
568: if (fromLabel.length() > 31) {
569: fromLabel = fromLabel.substring(0, 30);
570: log(
571: "FromLabel is longer than 31 characters, truncated to: "
572: + fromLabel, Project.MSG_WARN);
573: }
574: if (toLabel.length() > 31) {
575: toLabel = toLabel.substring(0, 30);
576: log(
577: "ToLabel is longer than 31 characters, truncated to: "
578: + toLabel, Project.MSG_WARN);
579: }
580: return FLAG_VERSION_LABEL + toLabel + VALUE_FROMLABEL
581: + fromLabel;
582: } else if (fromLabel != null) {
583: if (fromLabel.length() > 31) {
584: fromLabel = fromLabel.substring(0, 30);
585: log(
586: "FromLabel is longer than 31 characters, truncated to: "
587: + fromLabel, Project.MSG_WARN);
588: }
589: return FLAG_VERSION + VALUE_FROMLABEL + fromLabel;
590: } else {
591: if (toLabel.length() > 31) {
592: toLabel = toLabel.substring(0, 30);
593: log(
594: "ToLabel is longer than 31 characters, truncated to: "
595: + toLabel, Project.MSG_WARN);
596: }
597: return FLAG_VERSION_LABEL + toLabel;
598: }
599: }
600:
601: /**
602: * Gets the Version date string.
603: * @return An empty string if neither Todate or from date are set.
604: * @throws BuildException if there is an error.
605: */
606: protected String getVersionDate() throws BuildException {
607: if (fromDate == null && toDate == null
608: && numDays == Integer.MIN_VALUE) {
609: return "";
610: }
611: if (fromDate != null && toDate != null) {
612: return FLAG_VERSION_DATE + toDate + VALUE_FROMDATE
613: + fromDate;
614: } else if (toDate != null && numDays != Integer.MIN_VALUE) {
615: try {
616: return FLAG_VERSION_DATE + toDate + VALUE_FROMDATE
617: + calcDate(toDate, numDays);
618: } catch (ParseException ex) {
619: String msg = "Error parsing date: " + toDate;
620: throw new BuildException(msg, getLocation());
621: }
622: } else if (fromDate != null && numDays != Integer.MIN_VALUE) {
623: try {
624: return FLAG_VERSION_DATE + calcDate(fromDate, numDays)
625: + VALUE_FROMDATE + fromDate;
626: } catch (ParseException ex) {
627: String msg = "Error parsing date: " + fromDate;
628: throw new BuildException(msg, getLocation());
629: }
630: } else {
631: return fromDate != null ? FLAG_VERSION + VALUE_FROMDATE
632: + fromDate : FLAG_VERSION_DATE + toDate;
633: }
634: }
635:
636: /**
637: * Builds and returns the -G- flag if required.
638: * @return An empty string if get local copy is true.
639: */
640: protected String getGetLocalCopy() {
641: return (!getLocalCopy) ? FLAG_NO_GET : "";
642: }
643:
644: /**
645: * Gets the value of the fail on error flag.
646: * @return True if the FailOnError flag has been set or if 'writablefiles=skip'.
647: */
648: private boolean getFailOnError() {
649: return getWritableFiles().equals(WRITABLE_SKIP) ? false
650: : failOnError;
651: }
652:
653: /**
654: * Gets the value set for the FileTimeStamp.
655: * if it equals "current" then we return -GTC
656: * if it equals "modified" then we return -GTM
657: * if it equals "updated" then we return -GTU
658: * otherwise we return -GTC
659: *
660: * @return The default file time flag, if not set.
661: */
662: public String getFileTimeStamp() {
663: if (timestamp == null) {
664: return "";
665: } else if (timestamp.getValue().equals(TIME_MODIFIED)) {
666: return FLAG_FILETIME_MODIFIED;
667: } else if (timestamp.getValue().equals(TIME_UPDATED)) {
668: return FLAG_FILETIME_UPDATED;
669: } else {
670: return FLAG_FILETIME_DEF;
671: }
672: }
673:
674: /**
675: * Gets the value to determine the behaviour when encountering writable files.
676: * @return An empty String, if not set.
677: */
678: public String getWritableFiles() {
679: if (writableFiles == null) {
680: return "";
681: } else if (writableFiles.getValue().equals(WRITABLE_REPLACE)) {
682: return FLAG_REPLACE_WRITABLE;
683: } else if (writableFiles.getValue().equals(WRITABLE_SKIP)) {
684: // ss.exe exits with '100', when files have been skipped
685: // so we have to ignore the failure
686: failOnError = false;
687: return FLAG_SKIP_WRITABLE;
688: } else {
689: return "";
690: }
691: }
692:
693: /**
694: * Sets up the required environment and executes the command line.
695: *
696: * @param cmd The command line to execute.
697: * @return The return code from the exec'd process.
698: */
699: private int run(Commandline cmd) {
700: try {
701: Execute exe = new Execute(new LogStreamHandler(this ,
702: Project.MSG_INFO, Project.MSG_WARN));
703:
704: // If location of ss.ini is specified we need to set the
705: // environment-variable SSDIR to this value
706: if (serverPath != null) {
707: String[] env = exe.getEnvironment();
708: if (env == null) {
709: env = new String[0];
710: }
711: String[] newEnv = new String[env.length + 1];
712: System.arraycopy(env, 0, newEnv, 0, env.length);
713: newEnv[env.length] = "SSDIR=" + serverPath;
714:
715: exe.setEnvironment(newEnv);
716: }
717:
718: exe.setAntRun(getProject());
719: exe.setWorkingDirectory(getProject().getBaseDir());
720: exe.setCommandline(cmd.getCommandline());
721: // Use the OS launcher so we get environment variables
722: exe.setVMLauncher(false);
723: return exe.execute();
724: } catch (IOException e) {
725: throw new BuildException(e, getLocation());
726: }
727: }
728:
729: /**
730: * Calculates the start date for version comparison.
731: * <p>
732: * Calculates the date numDay days earlier than startdate.
733: * @param startDate The start date.
734: * @param daysToAdd The number of days to add.
735: * @return The calculated date.
736: * @throws ParseException
737: */
738: private String calcDate(String startDate, int daysToAdd)
739: throws ParseException {
740: Calendar calendar = new GregorianCalendar();
741: Date currentDate = dateFormat.parse(startDate);
742: calendar.setTime(currentDate);
743: calendar.add(Calendar.DATE, daysToAdd);
744: return dateFormat.format(calendar.getTime());
745: }
746:
747: /**
748: * Changes the password to '***' so it isn't displayed on screen if the build fails
749: *
750: * @param cmd The command line to clean
751: * @return The command line as a string with out the password
752: */
753: private String formatCommandLine(Commandline cmd) {
754: StringBuffer sBuff = new StringBuffer(cmd.toString());
755: int indexUser = sBuff.substring(0).indexOf(FLAG_LOGIN);
756: if (indexUser > 0) {
757: int indexPass = sBuff.substring(0).indexOf(",", indexUser);
758: int indexAfterPass = sBuff.substring(0).indexOf(" ",
759: indexPass);
760:
761: for (int i = indexPass + 1; i < indexAfterPass; i++) {
762: sBuff.setCharAt(i, '*');
763: }
764: }
765: return sBuff.toString();
766: }
767:
768: /**
769: * Extention of EnumeratedAttribute to hold the values for file time stamp.
770: */
771: public static class CurrentModUpdated extends EnumeratedAttribute {
772: /**
773: * Gets the list of allowable values.
774: * @return The values.
775: */
776: public String[] getValues() {
777: return new String[] { TIME_CURRENT, TIME_MODIFIED,
778: TIME_UPDATED };
779: }
780: }
781:
782: /**
783: * Extention of EnumeratedAttribute to hold the values for writable filess.
784: */
785: public static class WritableFiles extends EnumeratedAttribute {
786: /**
787: * Gets the list of allowable values.
788: * @return The values.
789: */
790: public String[] getValues() {
791: return new String[] { WRITABLE_REPLACE, WRITABLE_SKIP,
792: WRITABLE_FAIL };
793: }
794: }
795: }
|