1. /*
  2. * Copyright 2000-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.taskdefs.optional.vss;
  18. import org.apache.tools.ant.types.EnumeratedAttribute;
  19. import java.io.File;
  20. import java.io.IOException;
  21. import java.text.DateFormat;
  22. import java.text.ParseException;
  23. import java.util.Calendar;
  24. import java.util.Date;
  25. import java.util.GregorianCalendar;
  26. import org.apache.tools.ant.BuildException;
  27. import org.apache.tools.ant.Project;
  28. import org.apache.tools.ant.Task;
  29. import org.apache.tools.ant.taskdefs.Execute;
  30. import org.apache.tools.ant.taskdefs.LogStreamHandler;
  31. import org.apache.tools.ant.types.Commandline;
  32. /**
  33. * A base class for creating tasks for executing commands on Visual SourceSafe.
  34. * <p>
  35. * The class extends the 'exec' task as it operates by executing the ss.exe program
  36. * supplied with SourceSafe. By default the task expects ss.exe to be in the path,
  37. * you can override this be specifying the ssdir attribute.
  38. * </p>
  39. * <p>
  40. * This class provides set and get methods for 'login' and 'vsspath' attributes. It
  41. * also contains constants for the flags that can be passed to SS.
  42. * </p>
  43. *
  44. */
  45. public abstract class MSVSS extends Task implements MSVSSConstants {
  46. private String m_SSDir = null;
  47. private String m_vssLogin = null;
  48. private String m_vssPath = null;
  49. private String m_serverPath = null;
  50. /** Version */
  51. private String m_Version = null;
  52. /** Date */
  53. private String m_Date = null;
  54. /** Label */
  55. private String m_Label = null;
  56. /** Auto response */
  57. private String m_AutoResponse = null;
  58. /** Local path */
  59. private String m_LocalPath = null;
  60. /** Comment */
  61. private String m_Comment = null;
  62. /** From label */
  63. private String m_FromLabel = null;
  64. /** To label */
  65. private String m_ToLabel = null;
  66. /** Output file name */
  67. private String m_OutputFileName = null;
  68. /** User */
  69. private String m_User = null;
  70. /** From date */
  71. private String m_FromDate = null;
  72. /** To date */
  73. private String m_ToDate = null;
  74. /** History style */
  75. private String m_Style = null;
  76. /** Quiet defaults to false */
  77. private boolean m_Quiet = false;
  78. /** Recursive defaults to false */
  79. private boolean m_Recursive = false;
  80. /** Writable defaults to false */
  81. private boolean m_Writable = false;
  82. /** Fail on error defaults to true */
  83. private boolean m_FailOnError = true;
  84. /** Get local copy for checkout defaults to true */
  85. private boolean m_getLocalCopy = true;
  86. /** Number of days offset for History */
  87. private int m_NumDays = Integer.MIN_VALUE;
  88. /** Date format for History */
  89. private DateFormat m_DateFormat = DateFormat.getDateInstance(DateFormat.SHORT);
  90. /** Timestamp for retreived files */
  91. private CurrentModUpdated m_timestamp = null;
  92. /** Behaviour for writable files */
  93. private WritableFiles m_writablefiles = null;
  94. /**
  95. * Each sub-class must implemnt this method and return the constructed
  96. * command line to be executed. It is up to the sub-task to determine the
  97. * required attrubutes and their order.
  98. *
  99. * @return The Constructed command line.
  100. */
  101. abstract Commandline buildCmdLine();
  102. /**
  103. * Directory where <code>ss.exe</code> resides.
  104. * By default the task expects it to be in the PATH.
  105. *
  106. * @param dir The directory containing ss.exe.
  107. */
  108. public final void setSsdir(String dir) {
  109. m_SSDir = Project.translatePath(dir);
  110. }
  111. /**
  112. * Login to use when accessing VSS, formatted as "username,password".
  113. * <p>
  114. * You can omit the password if your database is not password protected.
  115. * If you have a password and omit it, Ant will hang.
  116. *
  117. * @param login The login string to use.
  118. */
  119. public final void setLogin(String login) {
  120. m_vssLogin = login;
  121. }
  122. /**
  123. * SourceSafe path which specifies the project/file(s) you wish to perform
  124. * the action on.
  125. * <p>
  126. * A prefix of 'vss://' will be removed if specified.
  127. *
  128. * @param vssPath The VSS project path.
  129. *
  130. * @ant.attribute group="required"
  131. */
  132. public final void setVsspath(String vssPath) {
  133. String projectPath;
  134. if (vssPath.startsWith("vss://")) {
  135. projectPath = vssPath.substring(5);
  136. } else {
  137. projectPath = vssPath;
  138. }
  139. if (projectPath.startsWith(PROJECT_PREFIX)) {
  140. m_vssPath = projectPath;
  141. } else {
  142. m_vssPath = PROJECT_PREFIX + projectPath;
  143. }
  144. }
  145. /**
  146. * Directory where <code>srssafe.ini</code> resides.
  147. *
  148. * @param serverPath The path to the VSS server.
  149. */
  150. public final void setServerpath(String serverPath) {
  151. m_serverPath = serverPath;
  152. }
  153. /**
  154. * Indicates if the build should fail if the Sourcesafe command does. Defaults to true.
  155. *
  156. * @param failOnError True if task should fail on any error.
  157. */
  158. public final void setFailOnError (boolean failOnError) {
  159. m_FailOnError = failOnError;
  160. }
  161. /**
  162. * Executes the task. <br>
  163. * Builds a command line to execute ss.exe and then calls Exec's run method
  164. * to execute the command line.
  165. * @throws BuildException if the command cannot execute.
  166. */
  167. public void execute() throws BuildException {
  168. int result = 0;
  169. Commandline commandLine = buildCmdLine();
  170. result = run(commandLine);
  171. if (Execute.isFailure(result) && getFailOnError()) {
  172. String msg = "Failed executing: " + formatCommandLine(commandLine)
  173. + " With a return code of " + result;
  174. throw new BuildException(msg, getLocation());
  175. }
  176. }
  177. // Special setters for the sub-classes
  178. protected void setInternalComment(String text) {
  179. m_Comment = text;
  180. }
  181. protected void setInternalAutoResponse(String text) {
  182. m_AutoResponse = text;
  183. }
  184. protected void setInternalDate(String text) {
  185. m_Date = text;
  186. }
  187. protected void setInternalDateFormat(DateFormat date) {
  188. m_DateFormat = date;
  189. }
  190. protected void setInternalFailOnError(boolean fail) {
  191. m_FailOnError = fail;
  192. }
  193. protected void setInternalFromDate(String text) {
  194. m_FromDate = text;
  195. }
  196. protected void setInternalFromLabel(String text) {
  197. m_FromLabel = text;
  198. }
  199. protected void setInternalLabel(String text) {
  200. m_Label = text;
  201. }
  202. protected void setInternalLocalPath(String text) {
  203. m_LocalPath = text;
  204. }
  205. protected void setInternalNumDays(int days) {
  206. m_NumDays = days;
  207. }
  208. protected void setInternalOutputFilename(String text) {
  209. m_OutputFileName = text;
  210. }
  211. protected void setInternalQuiet(boolean quiet) {
  212. m_Quiet = quiet;
  213. }
  214. protected void setInternalRecursive(boolean recursive) {
  215. m_Recursive = recursive;
  216. }
  217. protected void setInternalStyle(String style) {
  218. m_Style = style;
  219. }
  220. protected void setInternalToDate(String text) {
  221. m_ToDate = text;
  222. }
  223. protected void setInternalToLabel(String text) {
  224. m_ToLabel = text;
  225. }
  226. protected void setInternalUser(String user) {
  227. m_User = user;
  228. }
  229. protected void setInternalVersion(String text) {
  230. m_Version = text;
  231. }
  232. protected void setInternalWritable(boolean writable) {
  233. m_Writable = writable;
  234. }
  235. protected void setInternalFileTimeStamp(CurrentModUpdated timestamp) {
  236. m_timestamp = timestamp;
  237. }
  238. protected void setInternalWritableFiles(WritableFiles files) {
  239. m_writablefiles = files;
  240. }
  241. protected void setInternalGetLocalCopy(boolean get) {
  242. m_getLocalCopy = get;
  243. }
  244. /**
  245. * Gets the sscommand string. "ss" or "c:\path\to\ss"
  246. *
  247. * @return The path to ss.exe or just ss if sscommand is not set.
  248. */
  249. protected String getSSCommand() {
  250. if (m_SSDir == null) {
  251. return SS_EXE;
  252. }
  253. return m_SSDir.endsWith(File.separator) ? m_SSDir + SS_EXE : m_SSDir
  254. + File.separator + SS_EXE;
  255. }
  256. /**
  257. * Gets the vssserverpath string.
  258. *
  259. * @return null if vssserverpath is not set.
  260. */
  261. protected String getVsspath() {
  262. return m_vssPath;
  263. }
  264. /**
  265. * Gets the quiet string. -O-
  266. *
  267. * @return An empty string if quiet is not set or is false.
  268. */
  269. protected String getQuiet() {
  270. return m_Quiet ? FLAG_QUIET : "";
  271. }
  272. /**
  273. * Gets the recursive string. "-R"
  274. *
  275. * @return An empty string if recursive is not set or is false.
  276. */
  277. protected String getRecursive() {
  278. return m_Recursive ? FLAG_RECURSION : "";
  279. }
  280. /**
  281. * Gets the writable string. "-W"
  282. *
  283. * @return An empty string if writable is not set or is false.
  284. */
  285. protected String getWritable() {
  286. return m_Writable ? FLAG_WRITABLE : "";
  287. }
  288. /**
  289. * Gets the label string. "-Lbuild1"
  290. *
  291. * Max label length is 32 chars
  292. *
  293. * @return An empty string if label is not set.
  294. */
  295. protected String getLabel() {
  296. if (m_Label != null && m_Label.length() > 0) {
  297. return FLAG_LABEL + getShortLabel();
  298. } else {
  299. return "";
  300. }
  301. }
  302. /**
  303. * return at most the 30 first chars of the label, logging a warning message about the truncation
  304. * @return at most the 30 first chars of the label
  305. */
  306. private String getShortLabel() {
  307. if (m_Label != null && m_Label.length() > 31) {
  308. String label = m_Label.substring(0, 30);
  309. log("Label is longer than 31 characters, truncated to: " + label, Project.MSG_WARN);
  310. return label;
  311. } else {
  312. return m_Label;
  313. }
  314. }
  315. /**
  316. * Gets the style string. "-Lbuild1"
  317. *
  318. * @return An empty string if label is not set.
  319. */
  320. protected String getStyle() {
  321. return m_Style != null ? m_Style : "";
  322. }
  323. /**
  324. * Gets the version string. Returns the first specified of version "-V1.0",
  325. * date "-Vd01.01.01", label "-Vlbuild1".
  326. *
  327. * @return An empty string if a version, date and label are not set.
  328. */
  329. protected String getVersionDateLabel() {
  330. if (m_Version != null) {
  331. return FLAG_VERSION + m_Version;
  332. } else if (m_Date != null) {
  333. return FLAG_VERSION_DATE + m_Date;
  334. } else {
  335. // Use getShortLabel() so labels longer then 30 char are truncated
  336. // and the user is warned
  337. String label = getShortLabel();
  338. if (label != null && !label.equals("")) {
  339. return FLAG_VERSION_LABEL + label;
  340. }
  341. }
  342. return "";
  343. }
  344. /**
  345. * Gets the version string.
  346. *
  347. * @return An empty string if a version is not set.
  348. */
  349. protected String getVersion() {
  350. return m_Version != null ? FLAG_VERSION + m_Version : "";
  351. }
  352. /**
  353. * Gets the localpath string. "-GLc:\source" <p>
  354. *
  355. * The localpath is created if it didn't exist.
  356. *
  357. * @return An empty string if localpath is not set.
  358. */
  359. protected String getLocalpath() {
  360. if (m_LocalPath == null) {
  361. return "";
  362. } else {
  363. // make sure m_LocalDir exists, create it if it doesn't
  364. File dir = getProject().resolveFile(m_LocalPath);
  365. if (!dir.exists()) {
  366. boolean done = dir.mkdirs();
  367. if (!done) {
  368. String msg = "Directory " + m_LocalPath + " creation was not "
  369. + "successful for an unknown reason";
  370. throw new BuildException(msg, getLocation());
  371. }
  372. getProject().log("Created dir: " + dir.getAbsolutePath());
  373. }
  374. return FLAG_OVERRIDE_WORKING_DIR + m_LocalPath;
  375. }
  376. }
  377. /**
  378. * Gets the comment string. "-Ccomment text"
  379. *
  380. * @return A comment of "-" if comment is not set.
  381. */
  382. protected String getComment() {
  383. return m_Comment != null ? FLAG_COMMENT + m_Comment : FLAG_COMMENT + "-";
  384. }
  385. /**
  386. * Gets the auto response string. This can be Y "-I-Y" or N "-I-N".
  387. *
  388. * @return The default value "-I-" if autoresponse is not set.
  389. */
  390. protected String getAutoresponse() {
  391. if (m_AutoResponse == null) {
  392. return FLAG_AUTORESPONSE_DEF;
  393. } else if (m_AutoResponse.equalsIgnoreCase("Y")) {
  394. return FLAG_AUTORESPONSE_YES;
  395. } else if (m_AutoResponse.equalsIgnoreCase("N")) {
  396. return FLAG_AUTORESPONSE_NO;
  397. } else {
  398. return FLAG_AUTORESPONSE_DEF;
  399. }
  400. }
  401. /**
  402. * Gets the login string. This can be user and password, "-Yuser,password"
  403. * or just user "-Yuser".
  404. *
  405. * @return An empty string if login is not set.
  406. */
  407. protected String getLogin() {
  408. return m_vssLogin != null ? FLAG_LOGIN + m_vssLogin : "";
  409. }
  410. /**
  411. * Gets the output file string. "-Ooutput.file"
  412. *
  413. * @return An empty string if user is not set.
  414. */
  415. protected String getOutput() {
  416. return m_OutputFileName != null ? FLAG_OUTPUT + m_OutputFileName : "";
  417. }
  418. /**
  419. * Gets the user string. "-Uusername"
  420. *
  421. * @return An empty string if user is not set.
  422. */
  423. protected String getUser() {
  424. return m_User != null ? FLAG_USER + m_User : "";
  425. }
  426. /**
  427. * Gets the version string. This can be to-from "-VLbuild2~Lbuild1", from
  428. * "~Lbuild1" or to "-VLbuild2".
  429. *
  430. * @return An empty string if neither tolabel or fromlabel are set.
  431. */
  432. protected String getVersionLabel() {
  433. if (m_FromLabel == null && m_ToLabel == null) {
  434. return "";
  435. }
  436. if (m_FromLabel != null && m_ToLabel != null) {
  437. if (m_FromLabel.length() > 31) {
  438. m_FromLabel = m_FromLabel.substring(0, 30);
  439. log("FromLabel is longer than 31 characters, truncated to: "
  440. + m_FromLabel, Project.MSG_WARN);
  441. }
  442. if (m_ToLabel.length() > 31) {
  443. m_ToLabel = m_ToLabel.substring(0, 30);
  444. log("ToLabel is longer than 31 characters, truncated to: "
  445. + m_ToLabel, Project.MSG_WARN);
  446. }
  447. return FLAG_VERSION_LABEL + m_ToLabel + VALUE_FROMLABEL + m_FromLabel;
  448. } else if (m_FromLabel != null) {
  449. if (m_FromLabel.length() > 31) {
  450. m_FromLabel = m_FromLabel.substring(0, 30);
  451. log("FromLabel is longer than 31 characters, truncated to: "
  452. + m_FromLabel, Project.MSG_WARN);
  453. }
  454. return FLAG_VERSION + VALUE_FROMLABEL + m_FromLabel;
  455. } else {
  456. if (m_ToLabel.length() > 31) {
  457. m_ToLabel = m_ToLabel.substring(0, 30);
  458. log("ToLabel is longer than 31 characters, truncated to: "
  459. + m_ToLabel, Project.MSG_WARN);
  460. }
  461. return FLAG_VERSION_LABEL + m_ToLabel;
  462. }
  463. }
  464. /**
  465. * Gets the Version date string.
  466. * @return An empty string if neither Todate or from date are set.
  467. * @throws BuildException
  468. */
  469. protected String getVersionDate() throws BuildException {
  470. if (m_FromDate == null && m_ToDate == null
  471. && m_NumDays == Integer.MIN_VALUE) {
  472. return "";
  473. }
  474. if (m_FromDate != null && m_ToDate != null) {
  475. return FLAG_VERSION_DATE + m_ToDate + VALUE_FROMDATE + m_FromDate;
  476. } else if (m_ToDate != null && m_NumDays != Integer.MIN_VALUE) {
  477. try {
  478. return FLAG_VERSION_DATE + m_ToDate + VALUE_FROMDATE
  479. + calcDate(m_ToDate, m_NumDays);
  480. } catch (ParseException ex) {
  481. String msg = "Error parsing date: " + m_ToDate;
  482. throw new BuildException(msg, getLocation());
  483. }
  484. } else if (m_FromDate != null && m_NumDays != Integer.MIN_VALUE) {
  485. try {
  486. return FLAG_VERSION_DATE + calcDate(m_FromDate, m_NumDays)
  487. + VALUE_FROMDATE + m_FromDate;
  488. } catch (ParseException ex) {
  489. String msg = "Error parsing date: " + m_FromDate;
  490. throw new BuildException(msg, getLocation());
  491. }
  492. } else {
  493. return m_FromDate != null ? FLAG_VERSION + VALUE_FROMDATE
  494. + m_FromDate : FLAG_VERSION_DATE + m_ToDate;
  495. }
  496. }
  497. /**
  498. * Builds and returns the -G- flag if required.
  499. *
  500. * @return An empty string if get local copy is true.
  501. */
  502. protected String getGetLocalCopy() {
  503. return (!m_getLocalCopy) ? FLAG_NO_GET : "";
  504. }
  505. /**
  506. * Gets the value of the fail on error flag.
  507. *
  508. * @return True if the FailOnError flag has been set or if 'writablefiles=skip'.
  509. */
  510. private boolean getFailOnError() {
  511. return getWritableFiles().equals(WRITABLE_SKIP) ? false : m_FailOnError;
  512. }
  513. /**
  514. * Gets the value set for the FileTimeStamp.
  515. * if it equals "current" then we return -GTC
  516. * if it equals "modified" then we return -GTM
  517. * if it equals "updated" then we return -GTU
  518. * otherwise we return -GTC
  519. *
  520. * @return The default file time flag, if not set.
  521. */
  522. public String getFileTimeStamp() {
  523. if (m_timestamp == null) {
  524. return "";
  525. } else if (m_timestamp.getValue().equals(TIME_MODIFIED)) {
  526. return FLAG_FILETIME_MODIFIED;
  527. } else if (m_timestamp.getValue().equals(TIME_UPDATED)) {
  528. return FLAG_FILETIME_UPDATED;
  529. } else {
  530. return FLAG_FILETIME_DEF;
  531. }
  532. }
  533. /**
  534. * Gets the value to determine the behaviour when encountering writable files.
  535. * @return An empty String, if not set.
  536. */
  537. public String getWritableFiles() {
  538. if (m_writablefiles == null) {
  539. return "";
  540. } else if (m_writablefiles.getValue().equals(WRITABLE_REPLACE)) {
  541. return FLAG_REPLACE_WRITABLE;
  542. } else if (m_writablefiles.getValue().equals(WRITABLE_SKIP)) {
  543. // ss.exe exits with '100', when files have been skipped
  544. // so we have to ignore the failure
  545. m_FailOnError = false;
  546. return FLAG_SKIP_WRITABLE;
  547. } else {
  548. return "";
  549. }
  550. }
  551. /**
  552. * Sets up the required environment and executes the command line.
  553. *
  554. * @param cmd The command line to execute.
  555. * @return The return code from the exec'd process.
  556. */
  557. private int run(Commandline cmd) {
  558. try {
  559. Execute exe = new Execute(new LogStreamHandler(this,
  560. Project.MSG_INFO,
  561. Project.MSG_WARN));
  562. // If location of ss.ini is specified we need to set the
  563. // environment-variable SSDIR to this value
  564. if (m_serverPath != null) {
  565. String[] env = exe.getEnvironment();
  566. if (env == null) {
  567. env = new String[0];
  568. }
  569. String[] newEnv = new String[env.length + 1];
  570. for (int i = 0; i < env.length; i++) {
  571. newEnv[i] = env[i];
  572. }
  573. newEnv[env.length] = "SSDIR=" + m_serverPath;
  574. exe.setEnvironment(newEnv);
  575. }
  576. exe.setAntRun(getProject());
  577. exe.setWorkingDirectory(getProject().getBaseDir());
  578. exe.setCommandline(cmd.getCommandline());
  579. // Use the OS launcher so we get environment variables
  580. exe.setVMLauncher(false);
  581. return exe.execute();
  582. } catch (IOException e) {
  583. throw new BuildException(e, getLocation());
  584. }
  585. }
  586. /**
  587. * Calculates the start date for version comparison.
  588. * <p>
  589. * Calculates the date numDay days earlier than startdate.
  590. * @param fromDate The start date.
  591. * @param numDays The number of days to add.
  592. * @return The calculated date.
  593. * @throws ParseException
  594. */
  595. private String calcDate(String fromDate, int numDays) throws ParseException {
  596. String toDate = null;
  597. Date currdate = new Date();
  598. Calendar calend = new GregorianCalendar();
  599. currdate = m_DateFormat.parse(fromDate);
  600. calend.setTime(currdate);
  601. calend.add(Calendar.DATE, numDays);
  602. toDate = m_DateFormat.format(calend.getTime());
  603. return toDate;
  604. }
  605. /**
  606. * Changes the password to '***' so it isn't displayed on screen if the build fails
  607. *
  608. * @param cmd The command line to clean
  609. * @return The command line as a string with out the password
  610. */
  611. private String formatCommandLine(Commandline cmd) {
  612. StringBuffer sBuff = new StringBuffer(cmd.toString());
  613. int indexUser = sBuff.substring(0).indexOf(FLAG_LOGIN);
  614. if (indexUser > 0) {
  615. int indexPass = sBuff.substring(0).indexOf(",", indexUser);
  616. int indexAfterPass = sBuff.substring(0).indexOf(" ", indexPass);
  617. for (int i = indexPass + 1; i < indexAfterPass; i++) {
  618. sBuff.setCharAt(i, '*');
  619. }
  620. }
  621. return sBuff.toString();
  622. }
  623. /**
  624. * Extention of EnumeratedAttribute to hold the values for file time stamp.
  625. */
  626. public static class CurrentModUpdated extends EnumeratedAttribute {
  627. /**
  628. * Gets the list of allowable values.
  629. * @return The values.
  630. */
  631. public String[] getValues() {
  632. return new String[] {TIME_CURRENT, TIME_MODIFIED, TIME_UPDATED};
  633. }
  634. }
  635. /**
  636. * Extention of EnumeratedAttribute to hold the values for writable filess.
  637. */
  638. public static class WritableFiles extends EnumeratedAttribute {
  639. /**
  640. * Gets the list of allowable values.
  641. * @return The values.
  642. */
  643. public String[] getValues() {
  644. return new String[] {WRITABLE_REPLACE, WRITABLE_SKIP, WRITABLE_FAIL};
  645. }
  646. }
  647. }