001: /********************************************************************************
002: * CruiseControl, a Continuous Integration Toolkit
003: * Copyright (c) 2001, ThoughtWorks, Inc.
004: * 200 E. Randolph, 25th Floor
005: * Chicago, IL 60601 USA
006: * All rights reserved.
007: *
008: * Redistribution and use in source and binary forms, with or without
009: * modification, are permitted provided that the following conditions
010: * are met:
011: *
012: * + Redistributions of source code must retain the above copyright
013: * notice, this list of conditions and the following disclaimer.
014: *
015: * + Redistributions in binary form must reproduce the above
016: * copyright notice, this list of conditions and the following
017: * disclaimer in the documentation and/or other materials provided
018: * with the distribution.
019: *
020: * + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the
021: * names of its contributors may be used to endorse or promote
022: * products derived from this software without specific prior
023: * written permission.
024: *
025: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
026: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
027: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
028: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
029: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
030: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
031: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
032: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
033: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
034: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
035: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
036: ********************************************************************************/package net.sourceforge.cruisecontrol.sourcecontrols;
037:
038: import java.io.File;
039: import java.io.IOException;
040: import java.text.ParseException;
041: import java.text.SimpleDateFormat;
042: import java.util.ArrayList;
043: import java.util.Arrays;
044: import java.util.Date;
045: import java.util.Iterator;
046: import java.util.List;
047: import java.util.Locale;
048: import java.util.Map;
049: import java.util.Properties;
050:
051: import net.sourceforge.cruisecontrol.CruiseControlException;
052: import net.sourceforge.cruisecontrol.SourceControl;
053: import net.sourceforge.cruisecontrol.util.ManagedCommandline;
054: import net.sourceforge.cruisecontrol.util.Util;
055: import net.sourceforge.cruisecontrol.util.ValidationHelper;
056:
057: import org.apache.log4j.Logger;
058:
059: /**
060: * Checks for modifications made to a Telelogic CM Synergy repository. It does
061: * this by examining a provided reference project, getting the tasks from all
062: * folders in that project, and checking the completion time of those tasks
063: * against the last build.
064: *
065: * @author <a href="mailto:rjmpsmith@gmail.com">Robert J. Smith</a>
066: */
067: public class CMSynergy implements SourceControl {
068:
069: /**
070: * A delimiter used for data values returned from a CM Synergy query
071: */
072: public static final String CCM_ATTR_DELIMITER = "@#@#@#@";
073:
074: /**
075: * A delimiter used to mark the end of a multi-lined result from a query
076: */
077: public static final String CCM_END_OBJECT = "<<<#@#@#>>>";
078:
079: /**
080: * The default CM Synergy command line client executable
081: */
082: public static final String CCM_EXE = "ccm";
083:
084: /**
085: * The environment variable used by CM Synergy to determine which backend
086: * ccmSession to use when issuing commands.
087: */
088: public static final String CCM_SESSION_VAR = "CCM_ADDR";
089:
090: /**
091: * The default CM Synergy session map file
092: */
093: public static final String CCM_SESSION_FILE = System
094: .getProperty("user.home")
095: + File.separator + ".ccmsessionmap";
096:
097: /**
098: * An instance of the logging class
099: */
100: private static final Logger LOG = Logger.getLogger(CMSynergy.class);
101:
102: /**
103: * A collection of properties which will be passed to and set within the
104: * builder.
105: */
106: private SourceControlProperties properties = new SourceControlProperties();
107:
108: /**
109: * The name of the property which will be set and passed to the builder if
110: * any object has changed since the last build.
111: */
112: private String property = "cc.ccm.haschanged";
113:
114: /**
115: * The version number delimeter used by the database with which this CM
116: * Synergy session is connected.
117: */
118: private String ccmDelimiter = "-";
119:
120: /**
121: * The URL for your installation of Change Synergy
122: */
123: private String changeSynergyURL;
124:
125: /**
126: * The CCM database with which we wish to connect
127: */
128: private String ccmDb;
129:
130: /**
131: * The CM Synergy executable used for executing commands. If not set, we
132: * will use the default value "ccm".
133: */
134: private String ccmExe;
135:
136: /**
137: * The CM Synergy project spec (2 part name).
138: */
139: private String projectSpec;
140:
141: /**
142: * The instance number of the project. This is almost always "1", but might
143: * need to be overridden if you are using DCM?
144: */
145: private String projectInstance = "1";
146:
147: /**
148: * The CM Synergy project four part name we will use as a template to
149: * determine if any new tasks have been completed.
150: */
151: private String projectFourPartName;
152:
153: /**
154: * If set to true, the contents of the folders contained within the
155: * project's reconfigure properties will be updated before we query to find
156: * new tasks.
157: */
158: private boolean updateFolders = true;
159:
160: /**
161: * The file which contains the mapping between CM Synergy session names and
162: * IDs.
163: */
164: private File sessionFile;
165:
166: /**
167: * The name of the CM Synergy session to use.
168: */
169: private String sessionName;
170:
171: /**
172: * The date format as returned by your installation of CM Synergy.
173: */
174: private String ccmDateFormat = "EEE MMM dd HH:mm:ss yyyy"; // Fri Dec 3
175:
176: // 17:51:56 2004
177:
178: /**
179: * If set to true, the project will be reconfigured when changes are
180: * detected.
181: */
182: private boolean reconfigure = false;
183:
184: /**
185: * Used in conjunction with reconfigure. If set to true, all subprojects
186: * will be reconfigured when changes are detected.
187: */
188: private boolean recurse = true;
189:
190: /**
191: * If set to true, the time a task came into a reconfigure folder is used
192: * to determine modified tasks instead of the tasks completion time. Works
193: * for Synergy 6.3SP1 and newer only.
194: */
195: private boolean useBindTime = false;
196:
197: /**
198: * If set to true, the work area location will not be queried and passed to
199: * the builder.
200: */
201: private boolean ignoreWorkarea = false;
202:
203: /**
204: * The locale used for parsing dates.
205: */
206: private Locale locale;
207:
208: /**
209: * The language used to set the locale for parsing CM Synergy dates.
210: */
211: private String language = "en";
212:
213: /**
214: * A reusable commandline for issuing CM Synergy commands
215: */
216: private ManagedCommandline cmd;
217:
218: /**
219: * The country used to set the locale for parsing CM Synergy dates.
220: */
221: private String country = "US";
222:
223: /**
224: * The number of modified tasks found
225: */
226: private int numTasks;
227:
228: /**
229: * The number of modified objects found
230: */
231: private int numObjects;
232:
233: /**
234: * Sets the name of the CM Synergy executable to use when issuing commands.
235: *
236: * @param ccmExe
237: * the name of the CM Synergy executable
238: */
239: public void setCcmExe(String ccmExe) {
240: this .ccmExe = ccmExe;
241: }
242:
243: /**
244: * Sets the CM Synergy project spec to be used as a template for calculating
245: * changes. The value set here can be accessed from within the build as the
246: * property "cc.ccm.project".
247: *
248: * @param projectSpec
249: * The project spec (in 2 part name format).
250: */
251: public void setProject(String projectSpec) {
252: this .projectSpec = projectSpec;
253: }
254:
255: /**
256: * Sets the project's instance value. This value will be used in any query
257: * which involves the project. Defaults to "1". This default should work for
258: * most people. You might, however, need to override this value when using
259: * DCM?
260: *
261: * @param projectInstance
262: * The instance number of the project.
263: */
264: public void setInstance(String projectInstance) {
265: this .projectInstance = projectInstance;
266: }
267:
268: /**
269: * Sets the URL for your installation of Change Synergy. This is used to
270: * create active links from the modification report to the Change Requests
271: * associated with the modified tasks. If not set, the links will not be
272: * created. If you wish to use this feature, you must also set the ccmdb
273: * attribute to the remote location of the Synergy database.
274: *
275: * @param url
276: * The URL of your ChangeSynergy installation
277: */
278: public void setChangeSynergyURL(String url) {
279: this .changeSynergyURL = url;
280: }
281:
282: /**
283: * Sets the remote Synergy database with which to connect. This is only
284: * needed if you wish to create active links from the build results page to
285: * your installation of Change Synergy. If you set this attribute, you must
286: * also set the changesynergyurl attribute.
287: *
288: * @param db
289: * The remote Synergy database with which to connect (e.g.
290: * /ccmdb/mydb).
291: */
292: public void setCcmDb(String db) {
293: this .ccmDb = db;
294: }
295:
296: /**
297: * Sets the value of the updateFolders attribute. If set to true, the
298: * contents of the folders contained within the project's reconfigure
299: * properties will be updated before we query to find new tasks.
300: */
301: public void setUpdateFolders(boolean updateFolders) {
302: this .updateFolders = updateFolders;
303: }
304:
305: /**
306: * Sets the file which contains the mapping between CM Synergy session names
307: * and IDs. This file should be in the standard properties file format. Each
308: * line should map one name to a CM Synergy session ID (as returned by the
309: * "ccm status" command).
310: * <p>
311: * example: <br>
312: * <br>
313: * session1=localhost:65024:192.168.1.17
314: *
315: * @param sessionFile
316: * The session file
317: */
318: public void setSessionFile(String sessionFile) {
319: this .sessionFile = new File(sessionFile);
320: }
321:
322: /**
323: * Sets the name of the CM Synergy session to use with this plugin. This
324: * name should appear in the specified session file.
325: *
326: * @param sessionName
327: * The session name
328: *
329: * @see #setSessionFile(String)
330: */
331: public void setSessionName(String sessionName) {
332: this .sessionName = sessionName;
333: }
334:
335: /**
336: * Sets the date format used by your installation of CM Synergy. The format
337: * string should use the syntax described in <code>SimpleDateFormat</code>.
338: * The default is "EEE MMM dd HH:mm:ss yyyy" The value set here can be
339: * accessed from within the build as the property "cc.ccm.dateformat".
340: *
341: * @param format
342: * the date format
343: */
344: public void setCcmDateFormat(String format) {
345: this .ccmDateFormat = format;
346: }
347:
348: /**
349: * Sets the value of the reconfigure attribute. If set to true, the project
350: * will be reconfigured when changes are detected. Default value is false.
351: */
352: public void setReconfigure(boolean reconfigure) {
353: this .reconfigure = reconfigure;
354: }
355:
356: /**
357: * Sets the value of the useBindtime attribute. If set to true, the time the
358: * task came into the reconfigure folders is used to query the modifications
359: * instead of the time the task was completed. Works
360: * for Synergy 6.3SP1 and newer only.
361: * Default value is false.
362: */
363: public void setUseBindTime(boolean useBindTime) {
364: this .useBindTime = useBindTime;
365: }
366:
367: /**
368: * Sets the value of the recurse attribute. Used in conjuction with the
369: * reconfigure attribute. If set to true, all subprojects will also be
370: * reconfigured when changes are detected. Default is true.
371: */
372: public void setRecurse(boolean recurse) {
373: this .recurse = recurse;
374: }
375:
376: /**
377: * Sets the value of the ignoreWorkarea attribute. If set to true, we will
378: * not attempt to determine the location of the project's workarea, nor will
379: * we pass the cc.ccm.workarea attribute to the builders. Default is false.
380: */
381: public void setIgnoreWorkarea(boolean ignoreWorkarea) {
382: this .ignoreWorkarea = ignoreWorkarea;
383: }
384:
385: /**
386: * Sets the language used to create the locale for parsing CM Synergy dates.
387: * The format should follow the ISO standard as specified by
388: * <code>java.util.Locale</code>. The default is "en" (English).
389: *
390: * @param language
391: * The language to use when creating the <code>Locale</code>
392: */
393: public void setLanguage(String language) {
394: this .language = language;
395: }
396:
397: /**
398: * Sets the country used to create the locale for parsing CM Synergy dates.
399: * The format should follow the ISO standard as specified by
400: * <code>java.util.Locale</code>. The default is "US" (United States).
401: *
402: * @param country
403: * The ISO country code to use
404: */
405: public void setCountry(String country) {
406: this .country = country;
407: }
408:
409: public Map getProperties() {
410: return properties.getPropertiesAndReset();
411: }
412:
413: public void setProperty(String property) {
414: this .property = property;
415: }
416:
417: public void validate() throws CruiseControlException {
418: ValidationHelper.assertIsSet(projectSpec, "project", this
419: .getClass());
420: }
421:
422: public List getModifications(Date lastBuild, Date now) {
423: // Create a Locale appropriate for this installation
424: locale = new Locale(language, country);
425: if (!locale.equals(Locale.US)) {
426: LOG.info("Locale has been set to " + locale.toString());
427: }
428:
429: // Attempt to get the database delimiter
430: cmd = createCcmCommand(ccmExe, sessionName, sessionFile);
431: cmd.createArgument("delimiter");
432: try {
433: cmd.execute();
434: cmd.assertExitCode(0);
435: this .ccmDelimiter = cmd.getStdoutAsString().trim();
436: } catch (Exception e) {
437: StringBuffer buff = new StringBuffer(
438: "Could not connect to provided CM Synergy session");
439: LOG.error(buff.toString(), e);
440: return null;
441: }
442:
443: // Create the projectFourPartName needed for projects with instance
444: // other than 1 to reconfigure properly
445: projectFourPartName = projectSpec + ":project:"
446: + projectInstance;
447:
448: LOG.info("Checking for modifications between "
449: + lastBuild.toString() + " and " + now.toString());
450:
451: // If we were asked to update the folders, do so
452: if (updateFolders) {
453: refreshReconfigureProperties();
454: }
455:
456: // Create a list of modifications based upon tasks completed
457: // since the last build.
458: numObjects = 0;
459: numTasks = 0;
460: List modifications = getModifiedTasks(lastBuild);
461:
462: LOG.info("Found " + numObjects + " modified object(s) in "
463: + numTasks + " new task(s).");
464:
465: // If we were asked to reconfigure the project, do so
466: if (reconfigure && (numObjects > 0)) {
467: reconfigureProject();
468: }
469:
470: // Pass to the build any relevent properties
471: properties.put("cc.ccm.project", projectFourPartName);
472: properties.put("cc.ccm.dateformat", ccmDateFormat);
473: String sessionID = cmd.getVariable(CCM_SESSION_VAR);
474: if (sessionID != null) {
475: properties.put("cc.ccm.session", sessionID);
476: }
477: if (numObjects > 0) {
478: properties.put(property, "true");
479: }
480: if (!ignoreWorkarea) {
481: properties.put("cc.ccm.workarea", getWorkarea());
482: }
483:
484: return modifications;
485: }
486:
487: /**
488: * Update the folders within the given project's reconfigure properties.
489: */
490: private void refreshReconfigureProperties() {
491: // Construct the CM Synergy command
492: cmd.clearArgs();
493: cmd.createArgument("reconfigure_properties");
494: if (recurse) {
495: cmd.createArgument("-recurse");
496: }
497: cmd.createArguments("-refresh", projectFourPartName);
498: try {
499: cmd.execute();
500: cmd.assertExitCode(0);
501: } catch (Exception e) {
502: LOG.warn(
503: "Could not refresh reconfigure properties for project \""
504: + projectFourPartName + "\".", e);
505: }
506: }
507:
508: /**
509: * Get a list of all tasks which are contained in all folders in the
510: * reconfigure properties of the specified project and were completed after
511: * the last build. If useBindTime is <code>true</code> not the completion time of
512: * the task is considered but the time the task came into the folder.
513: *
514: * @return A list of <code>CMSynergyModifications</code> which represent
515: * the new tasks
516: */
517: private List getModifiedTasks(Date lastBuild) {
518:
519: // The format used for converting Java dates into CM Synergy dates
520: // Note that the format used to submit commands differs from the
521: // format used in the results of that command!?!
522: SimpleDateFormat toCcmDate = new SimpleDateFormat(
523: "yyyy/MM/dd HH:mm:ss", locale);
524:
525: // Construct the CM Synergy command
526: cmd.clearArgs();
527: cmd.createArgument("query");
528: cmd.createArgument("-u");
529:
530: // Set up the output format
531: cmd.createArgument("-f");
532: cmd.createArgument("%displayname" + CCM_ATTR_DELIMITER + // 0
533: "%release" + CCM_ATTR_DELIMITER + // 1
534: "%owner" + CCM_ATTR_DELIMITER + // 2
535: "%completion_date" + CCM_ATTR_DELIMITER + // 3
536: "%task_synopsis" + CCM_END_OBJECT); // 4
537:
538: // Construct the query string
539: if (useBindTime) {
540: cmd
541: .createArgument("is_task_in_folder_of(is_folder_in_rp_of('"
542: + projectFourPartName
543: + "'), '>', time('"
544: + toCcmDate.format(lastBuild) + "'))");
545: } else {
546: cmd
547: .createArgument("is_task_in_folder_of(is_folder_in_rp_of('"
548: + projectFourPartName
549: + "')) and completion_date>time('"
550: + toCcmDate.format(lastBuild) + "')");
551: }
552:
553: // Execute the command
554: try {
555: cmd.execute();
556: } catch (Exception e) {
557: LOG.error(
558: "Could not query for new tasks. The modification list "
559: + "will be empty!", e);
560: }
561:
562: // create a modification list with discovered tasks
563: List modificationList = new ArrayList();
564: Iterator tasks = format(cmd.getStdoutAsList()).iterator();
565: while (tasks.hasNext()) {
566: numTasks++;
567: String[] attributes = tokeniseEntry((String) tasks.next(),
568: 5);
569: if (attributes == null) {
570: LOG
571: .warn("Could not determine attributes for at least one "
572: + "discovered task! The modification set is suspect.");
573: continue;
574: }
575: CMSynergyModification mod = new CMSynergyModification();
576: mod.taskNumber = attributes[0];
577: mod.revision = attributes[1];
578: mod.userName = attributes[2];
579: mod.modifiedTime = getDateFromSynergy(attributes[3]);
580: mod.comment = attributes[4];
581:
582: // Populate the included files by quering for objects in the task
583: getModifiedObjects(mod);
584:
585: // Find any Change Requests with which the task is associated
586: getAssociatedCRs(mod);
587:
588: // Add the modification to the list
589: modificationList.add(mod);
590: }
591:
592: return modificationList;
593: }
594:
595: /**
596: * Split the results of a CM Synergy query into individual tokens. This
597: * method was added for compatibility with the 1.3 JRE.
598: *
599: * @param line
600: * The line to be tokenised.
601: * @param maxTokens
602: * The maximum number of tokens in the line
603: *
604: * @return The tokens found
605: */
606: private String[] tokeniseEntry(String line, int maxTokens) {
607: int minTokens = maxTokens - 1; // comment may be absent.
608: String[] tokens = new String[maxTokens];
609: Arrays.fill(tokens, "");
610: int tokenIndex = 0;
611: for (int oldIndex = 0, index = line.indexOf(CCM_ATTR_DELIMITER,
612: 0); true; oldIndex = index
613: + CCM_ATTR_DELIMITER.length(), index = line.indexOf(
614: CCM_ATTR_DELIMITER, oldIndex), tokenIndex++) {
615: if (tokenIndex > maxTokens) {
616: LOG.debug("Too many tokens; skipping entry");
617: return null;
618: }
619: if (index == -1) {
620: tokens[tokenIndex] = line.substring(oldIndex);
621: break;
622: }
623:
624: tokens[tokenIndex] = line.substring(oldIndex, index);
625: }
626: if (tokenIndex < minTokens) {
627: LOG.debug("Not enough tokens; skipping entry");
628: return null;
629: }
630: return tokens;
631: }
632:
633: /**
634: * Populate the object list of a Modification by querying for objects
635: * associated with the task.
636: */
637: private void getModifiedObjects(CMSynergyModification mod) {
638: // Construct the CM Synergy command
639: cmd.clearArgs();
640: cmd.createArgument("task");
641: cmd.createArguments("-show", "objects");
642:
643: // Set up the output format
644: cmd.createArgument("-f");
645: cmd.createArgument("%name" + CCM_ATTR_DELIMITER + // 0
646: "%version" + CCM_ATTR_DELIMITER + // 1
647: "%type" + CCM_ATTR_DELIMITER + // 2
648: "%instance" + CCM_ATTR_DELIMITER + // 3
649: "%project" + CCM_ATTR_DELIMITER + // 4
650: "%comment" + CCM_END_OBJECT); // 5
651:
652: // Construct the query string
653: cmd.createArgument(mod.taskNumber);
654:
655: // Execute the command
656: try {
657: cmd.execute();
658: } catch (Exception e) {
659: LOG.warn("Could not query for objects in task \""
660: + mod.taskNumber
661: + "\". The modification list will be incomplete!",
662: e);
663: }
664:
665: // Populate the modification with the object data from the task
666: Iterator objects = format(cmd.getStdoutAsList()).iterator();
667: while (objects.hasNext()) {
668: numObjects++;
669: String object = (String) objects.next();
670: String[] attributes = tokeniseEntry(object, 6);
671: if (attributes == null) {
672: LOG
673: .warn("Could not determine attributes for object associated "
674: + "with task \"" + mod.revision + "\".");
675: continue;
676: }
677: // Add each object to the CMSynergyModification
678: mod.createModifiedObject(attributes[0], attributes[1],
679: attributes[2], attributes[3], attributes[4],
680: attributes[5]);
681: }
682: }
683:
684: /**
685: * Queries the CM Synergy repository to find any Change Requests with which
686: * a task is associated. If the Change Synergy URL and database were
687: * provided, we will add HTML based links to those CRs.
688: *
689: * @param mod
690: * The modification object
691: */
692: private void getAssociatedCRs(CMSynergyModification mod) {
693: // Construct the CM Synergy command
694: cmd.clearArgs();
695: cmd.createArgument("query");
696: cmd.createArgument("-u");
697:
698: // Set up the output format
699: cmd.createArguments("-f", "%displayname");
700:
701: // Construct the query string
702: cmd
703: .createArgument("cvtype='problem' and has_associated_task('task"
704: + mod.taskNumber
705: + ccmDelimiter
706: + "1:task:probtrac')");
707:
708: // Execute the command
709: try {
710: cmd.execute();
711: } catch (Exception e) {
712: LOG.warn(
713: "Could not query for associated CRs. The modification list "
714: + "may be incomplete!", e);
715: }
716:
717: // Add the Change Request(s) to the modification
718: List crList = cmd.getStdoutAsList();
719: if (crList != null) {
720: Iterator crs = crList.iterator();
721: while (crs.hasNext()) {
722: String crNum = ((String) crs.next()).trim();
723: CMSynergyModification.ChangeRequest cr = mod
724: .createChangeRequest(crNum);
725: if (changeSynergyURL != null && ccmDb != null) {
726: StringBuffer href = new StringBuffer(
727: changeSynergyURL);
728: href
729: .append("/servlet/com.continuus.webpt.servlet.PTweb?");
730: href
731: .append("ACTION_FLAG=frameset_form&TEMPLATE_FLAG=ProblemReportView&database=");
732: href.append(ccmDb);
733: href.append("&role=User&problem_number=");
734: href.append(crNum);
735: cr.href = href.toString();
736: }
737: }
738: }
739: }
740:
741: /**
742: * Determine the work area location for the specified project.
743: *
744: * @return The work area location
745: */
746: private String getWorkarea() {
747: String defaultWorkarea = ".";
748:
749: // Get the literal workarea from Synergy
750: cmd.clearArgs();
751: cmd.createArgument("attribute");
752: cmd.createArguments("-show", "wa_path");
753: cmd.createArguments("-project", projectFourPartName);
754:
755: try {
756: cmd.execute();
757: cmd.assertExitCode(0);
758: } catch (Exception e) {
759: LOG.warn(
760: "Could not determine the workarea location for project \""
761: + projectFourPartName + "\".", e);
762: return defaultWorkarea;
763: }
764:
765: // The command will return the literal work area, but what we are
766: // really interested in is the top level directory within that work
767: // area.
768: File workareaPath = new File(cmd.getStdoutAsString().trim());
769: if (!workareaPath.isDirectory()) {
770: LOG
771: .warn("The workarea reported by Synergy does not exist or is not accessible by this session - \""
772: + workareaPath.toString() + "\".");
773: return defaultWorkarea;
774: }
775: String[] dirs = workareaPath.list();
776: if (dirs.length != 1) {
777: LOG.warn("The workarea reported by Synergy is invalid - \""
778: + workareaPath.toString() + "\".");
779: return defaultWorkarea;
780: }
781:
782: // Found it!
783: return workareaPath.getAbsolutePath() + File.separator
784: + dirs[0];
785: }
786:
787: /**
788: * Reconfigure the project
789: */
790: private void reconfigureProject() {
791: LOG.info("Reconfiguring project " + projectFourPartName + ".");
792:
793: // Construct the CM Synergy command
794: cmd.clearArgs();
795: cmd.createArgument("reconfigure");
796: if (recurse) {
797: cmd.createArgument("-recurse");
798: }
799: cmd.createArguments("-project", projectFourPartName);
800:
801: try {
802: cmd.execute();
803: cmd.assertExitCode(0);
804: } catch (Exception e) {
805: LOG.warn("Could not reconfigure project \""
806: + projectFourPartName + "\".", e);
807: }
808: }
809:
810: /**
811: * Format the output of a CM Synergy query by removing newlines introduced
812: * by comments.
813: *
814: * @param in
815: * The <code>List</code> to be formated
816: * @return The formated <code>List</code>
817: */
818: private List format(List in) {
819: // Concatenate output lines until we hit the end of object delimiter.
820: List out = new ArrayList();
821: Iterator it = in.iterator();
822: StringBuffer buff = new StringBuffer();
823: while (it.hasNext()) {
824: buff.append((String) it.next());
825: int index = buff.toString().lastIndexOf(CCM_END_OBJECT);
826: if (index > -1) {
827: buff.delete(index, buff.length());
828: out.add(buff.toString());
829: buff = new StringBuffer();
830: }
831: }
832: return out;
833: }
834:
835: /**
836: * Parse a CM Synergy date string into a Java <code>Date</code>. If the
837: * string cannot be parsed, a warning is written to the log, and the current
838: * date is returned.
839: *
840: * @param dateString
841: * the date string to parse
842: * @return The date
843: *
844: * @see #setCcmDateFormat(String)
845: */
846: private Date getDateFromSynergy(String dateString) {
847: SimpleDateFormat fromCcmDate = new SimpleDateFormat(
848: ccmDateFormat, locale);
849: Date date;
850: try {
851: date = fromCcmDate.parse(dateString);
852: } catch (ParseException e) {
853: LOG.warn("Could not parse CM Synergy date \"" + dateString
854: + "\" into Java Date using format \""
855: + ccmDateFormat + "\".", e);
856: date = new Date();
857: }
858: return date;
859: }
860:
861: /**
862: * Given a CM Synergy session name, looks up the corresponding session ID.
863: *
864: * @param sessionName
865: * The CM Synergy session name
866: * @param sessionFile
867: * The session map file
868: * @return The session ID.
869: */
870: public static String getSessionID(String sessionName,
871: File sessionFile) throws CruiseControlException {
872:
873: // If no session file was provided, try to use the default
874: if (sessionFile == null) {
875: sessionFile = new File(CCM_SESSION_FILE);
876: }
877:
878: // Load the persisted session information from file
879: Properties sessionProperties;
880: try {
881: sessionProperties = Util
882: .loadPropertiesFromFile(sessionFile);
883: } catch (IOException e) {
884: throw new CruiseControlException(e);
885: }
886:
887: // Look up and return the full session ID
888: return sessionProperties.getProperty(sessionName);
889: }
890:
891: /**
892: * Creates a <code>ManagedCommandline</code> configured to run CM Synergy
893: * commands.
894: *
895: * @param ccmExe
896: * Full path of the CM Synergy command line client (or
897: * <code>null</code> to use the default).
898: * @param sessionName
899: * The name of the session as stored in the map file (or
900: * <code>null</code> to use the default session).
901: * @param sessionFile
902: * The CM Synergy session map file (or <code>null</code> to use
903: * the default).
904: * @return A configured <code>ManagedCommandline</code>
905: */
906: public static ManagedCommandline createCcmCommand(String ccmExe,
907: String sessionName, File sessionFile) {
908:
909: // If no executable name was provided, use the default
910: if (ccmExe == null) {
911: ccmExe = CCM_EXE;
912: }
913:
914: // Attempt to get the appropriate CM Synergy session
915: String sessionID = null;
916: if (sessionName != null) {
917: try {
918: sessionID = getSessionID(sessionName, sessionFile);
919: if (sessionID == null) {
920: LOG
921: .error("Could not find a session ID for CM Synergy session named \""
922: + sessionName
923: + "\". Attempting to use the default (current) session.");
924: }
925: } catch (CruiseControlException e) {
926: LOG
927: .error(
928: "Failed to look up CM Synergy session named \""
929: + sessionName
930: + "\". Attempting to use the default (current) session.",
931: e);
932: }
933: }
934:
935: // Create a managed command line
936: ManagedCommandline command = new ManagedCommandline(ccmExe);
937:
938: // If we were able to find a CM Synergy session ID, use it
939: if (sessionID != null) {
940: command.setVariable(CCM_SESSION_VAR, sessionID);
941: }
942:
943: return command;
944: }
945: }
|