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 com.starbase.starteam.File;
039: import com.starbase.starteam.Folder;
040: import com.starbase.starteam.Item;
041: import com.starbase.starteam.TypeNotFoundException;
042: import com.starbase.starteam.PropertyNames;
043: import com.starbase.starteam.Server;
044: import com.starbase.starteam.ServerException;
045: import com.starbase.starteam.StarTeamFinder;
046: import com.starbase.starteam.TypeNames;
047: import com.starbase.starteam.User;
048: import com.starbase.starteam.UserAccount;
049: import com.starbase.starteam.View;
050: import com.starbase.starteam.ViewConfiguration;
051: import com.starbase.starteam.PropertyEnums;
052: import com.starbase.util.OLEDate;
053: import net.sourceforge.cruisecontrol.CruiseControlException;
054: import net.sourceforge.cruisecontrol.Modification;
055: import net.sourceforge.cruisecontrol.SourceControl;
056: import net.sourceforge.cruisecontrol.util.ValidationHelper;
057: import org.apache.log4j.Logger;
058:
059: import java.util.ArrayList;
060: import java.util.Date;
061: import java.util.HashMap;
062: import java.util.Iterator;
063: import java.util.List;
064: import java.util.Map;
065:
066: /**
067: * This class logs into StarTeam and collects information on any modifications
068: * made since the last successful build.
069: *
070: * @author Christopher Charlier -- ThoughtWorks Inc. 2001
071: * @author <a href="mailto:jcyip@thoughtworks.com">Jason Yip</a>
072: * @author Neill
073: * @author Ben Burgess
074: */
075: public class StarTeam implements SourceControl {
076:
077: private static final Logger LOG = Logger.getLogger(StarTeam.class);
078:
079: private String userName;
080: private String password;
081: private String folder;
082: private String url;
083: private List modifications = new ArrayList();
084: private OLEDate nowDate;
085: private Folder lastBuildRoot;
086: private PropertyNames stPropertyNames;
087: private PropertyEnums stPropertyEnums;
088: private Server server;
089: private TypeNames stTypeNames;
090:
091: private SourceControlProperties properties = new SourceControlProperties();
092: private String property;
093: private String propertyOnDelete;
094:
095: private boolean preloadFileInformation = true;
096: private boolean canLookupEmails = true;
097: private boolean canLookupDeletions = true;
098:
099: /**
100: * Set StarTeam user name
101: */
102: public void setUsername(String userName) {
103: this .userName = userName;
104: }
105:
106: /**
107: * Set password for StarTeam user
108: */
109: public void setPassword(String password) {
110: this .password = password;
111: }
112:
113: /**
114: * Set repository folder
115: */
116: public void setFolder(String folder) {
117: this .folder = folder;
118: }
119:
120: public void setPreloadFileInformation(boolean preloadFileInformation) {
121: this .preloadFileInformation = preloadFileInformation;
122: }
123:
124: public void setStarteamurl(String url) {
125: this .url = url;
126: }
127:
128: public void setProperty(String property) {
129: this .property = property;
130: }
131:
132: public void setPropertyOnDelete(String propertyOnDelete) {
133: this .propertyOnDelete = propertyOnDelete;
134: }
135:
136: public Map getProperties() {
137: return properties.getPropertiesAndReset();
138: }
139:
140: public void validate() throws CruiseControlException {
141: ValidationHelper.assertIsSet(folder, "folder", this .getClass());
142: ValidationHelper.assertIsSet(url, "url", this .getClass());
143: ValidationHelper.assertIsSet(userName, "username", this
144: .getClass());
145: ValidationHelper.assertIsSet(password, "password", this
146: .getClass());
147:
148: // uncomment below for debugging which properties to populateNow
149: //com.starbase.starteam.vts.comm.NetMonitor.onConsole();
150: }
151:
152: /**
153: * Populates the modification set with all appropriate information based on
154: * the changes since the last successful build.
155: */
156: public List getModifications(Date lastBuild, Date now) {
157: // Clean out the modifications list. Otherwise we get duplicate entries
158: // when this function is called more than once in a quiet period breach
159: // We normally would need to clean out the email list as well, except we
160: // know that all entries in current list will still be required
161: modifications.clear();
162:
163: // Store OLEDate equivalents of now and lastbuild for performance
164: nowDate = new OLEDate(now.getTime());
165: OLEDate lastBuildDate = new OLEDate(lastBuild.getTime());
166:
167: server = null;
168: try {
169: // Set up two view snapshots, one at lastbuild time, one now
170: View view = StarTeamFinder.openView(userName + ":"
171: + password + "@" + url);
172: server = view.getServer();
173:
174: View snapshotAtNow = new View(view, ViewConfiguration
175: .createFromTime(nowDate));
176: View snapshotAtLastBuild = new View(view, ViewConfiguration
177: .createFromTime(lastBuildDate));
178:
179: Map nowFiles = new HashMap();
180: Map lastBuildFiles = new HashMap();
181:
182: Folder nowRoot = StarTeamFinder.findFolder(snapshotAtNow
183: .getRootFolder(), folder);
184:
185: stPropertyNames = server.getPropertyNames();
186: stTypeNames = server.getTypeNames();
187: stPropertyEnums = server.getPropertyEnums();
188: // properties to fetch immediately and cache
189: final String[] propertiesToCache = new String[] {
190: stPropertyNames.FILE_CONTENT_REVISION,
191: stPropertyNames.MODIFIED_TIME,
192: stPropertyNames.FILE_FILE_TIME_AT_CHECKIN,
193: stPropertyNames.COMMENT,
194: stPropertyNames.MODIFIED_USER_ID,
195: stPropertyNames.FILE_NAME };
196:
197: if (preloadFileInformation) {
198: // cache information for now
199: nowRoot.populateNow(stTypeNames.FILE,
200: propertiesToCache, -1);
201: }
202:
203: // Visit all files in the snapshots and add to Maps
204: addFolderModsToList(nowFiles, nowRoot);
205:
206: try {
207: lastBuildRoot = StarTeamFinder.findFolder(
208: snapshotAtLastBuild.getRootFolder(), folder);
209:
210: if (preloadFileInformation) {
211: // cache information for last build
212: lastBuildRoot.populateNow(stTypeNames.FILE,
213: propertiesToCache, -1);
214: }
215:
216: addFolderModsToList(lastBuildFiles, lastBuildRoot);
217: } catch (ServerException se) {
218: LOG
219: .error(
220: url
221: + ": Server Exception occurred visiting last build view: ",
222: se);
223: }
224:
225: compareFileLists(nowFiles, lastBuildFiles);
226:
227: // Discard cached items so memory is not eaten up
228: snapshotAtNow.getRootFolder().discardItems(
229: stTypeNames.FILE, -1);
230:
231: try {
232: snapshotAtLastBuild.getRootFolder().discardItems(
233: stTypeNames.FILE, -1);
234: } catch (ServerException se) {
235: LOG
236: .error(
237: url
238: + ": Server Exception occurred discarding last build file cache: ",
239: se);
240: }
241:
242: LOG.info(url + ": " + modifications.size()
243: + " modifications in "
244: + nowRoot.getFolderHierarchy());
245: return modifications;
246: } catch (Exception e) {
247: LOG
248: .error(
249: url
250: + ": Problem looking up modifications in StarTeam.",
251: e);
252: modifications.clear();
253: return modifications;
254: } finally {
255: if (server != null) {
256: server.disconnect();
257: }
258: }
259: }
260:
261: /**
262: * Compare old and new file lists to determine what happened
263: */
264: private void compareFileLists(Map nowFiles, Map lastBuildFiles) {
265: for (Iterator iter = nowFiles.keySet().iterator(); iter
266: .hasNext();) {
267: Integer currentItemID = (Integer) iter.next();
268: File currentFile = (File) nowFiles.get(currentItemID);
269:
270: if (lastBuildFiles.containsKey(currentItemID)) {
271: File lastBuildFile = (File) lastBuildFiles
272: .get(currentItemID);
273:
274: if (fileHasBeenModified(currentFile, lastBuildFile)) {
275: addRevision(currentFile, "modified");
276: } else if (fileHasBeenMoved(currentFile, lastBuildFile)) {
277: addRevision(currentFile, "moved");
278: }
279: // Remove the identified last build file from the list of
280: // last build files. It will make processing the delete
281: // check on the last builds quicker
282: lastBuildFiles.remove(currentItemID);
283: } else {
284: // File is new
285: addRevision(currentFile, "new");
286: }
287: }
288: examineOldFiles(lastBuildFiles);
289: }
290:
291: /**
292: * Now examine old files. They have to have been deleted as we know they
293: * are not in the new list from the processing above.
294: */
295: private void examineOldFiles(Map lastBuildFiles) {
296:
297: // START - 1st Comment out if StarTeam 4.2 SP2 or older
298: if (canLookupDeletions && preloadFileInformation
299: && !lastBuildFiles.isEmpty()) {
300: try {
301: // properties to fetch immediately and cache
302: final String[] propertiesToCacheForDeletes = new String[] {
303: stPropertyNames.AUDIT_CLASS_ID,
304: stPropertyNames.AUDIT_OBJECT_ID,
305: stPropertyNames.AUDIT_EVENT_ID,
306: stPropertyNames.MODIFIED_TIME,
307: stPropertyNames.AUDIT_USER_ID };
308:
309: lastBuildRoot.populateNow(stTypeNames.AUDIT,
310: propertiesToCacheForDeletes, -1);
311: } catch (TypeNotFoundException tnfx) {
312: LOG
313: .debug(
314: url
315: + ": Error caching Audit information (StarTeam 4.2 SP2 SDK or older in use)."
316: + " Deletions will be reported as by Unknown.",
317: tnfx);
318: canLookupDeletions = false;
319: }
320: }
321: // END - 1st Comment out if StarTeam 4.2 SP2 or older
322:
323: for (Iterator iter = lastBuildFiles.values().iterator(); iter
324: .hasNext();) {
325: File currentLastBuildFile = (File) iter.next();
326: addRevision((File) currentLastBuildFile
327: .getFromHistoryByDate(nowDate), "deleted");
328: }
329: }
330:
331: private boolean fileHasBeenModified(File currentFile,
332: File lastBuildFile) {
333: return currentFile.getContentVersion() != lastBuildFile
334: .getContentVersion();
335: }
336:
337: private boolean fileHasBeenMoved(File currentFile,
338: File lastBuildFile) {
339: return !currentFile.getParentFolder().getFolderHierarchy()
340: .equals(
341: lastBuildFile.getParentFolder()
342: .getFolderHierarchy());
343: }
344:
345: private void addFolderModsToList(Map fileList, Folder folder) {
346: //try {
347: // Thread.sleep(100);
348: //} catch (InterruptedException ignoredInterruptedException) {}
349:
350: Item[] files = folder.getItems(stTypeNames.FILE);
351: for (int i = 0; i < files.length; i++) {
352: File file = (File) files[i];
353: fileList.put(new Integer(file.getItemID()), file);
354: }
355:
356: Folder[] folders = folder.getSubFolders();
357: for (int i = 0; i < folders.length; i++) {
358: addFolderModsToList(fileList, folders[i]);
359: }
360: }
361:
362: /**
363: * Adds the revision to the modification set.
364: *
365: * @param revision
366: */
367: private void addRevision(File revision, String status) {
368:
369: Modification mod = new Modification();
370: mod.type = "StarTeam";
371: String fileName = revision.getName();
372: Folder parentFolder = revision.getParentFolder();
373: String folderName = parentFolder.getFolderHierarchy();
374: if (folderName.length() > 0
375: && (folderName.endsWith("\\") || folderName
376: .endsWith("/"))) {
377: folderName = folderName.substring(0,
378: folderName.length() - 1);
379: }
380: Modification.ModifiedFile modFile = mod.createModifiedFile(
381: fileName, folderName);
382: modFile.action = status;
383:
384: User user = null;
385: if (property != null) {
386: properties.put(property, "true");
387: }
388: if (status.equals("deleted")) {
389: if (propertyOnDelete != null) {
390: properties.put(propertyOnDelete, "true");
391: }
392:
393: boolean foundAudit = false;
394:
395: // START - 2nd Comment out if StarTeam 4.2 SP2 or older
396: if (canLookupDeletions) {
397: try {
398: Item[] audits = parentFolder
399: .getItems(stTypeNames.AUDIT);
400: for (int i = 0; i < audits.length && !foundAudit; i++) {
401: com.starbase.starteam.Audit audit = (com.starbase.starteam.Audit) (audits[i]);
402: if (audit.getItemDescriptor().equals(fileName)
403: && audit
404: .getInt(stPropertyNames.AUDIT_EVENT_ID) == stPropertyEnums.AUDIT_EVENT_ID_DELETED) {
405: foundAudit = true;
406: mod.modifiedTime = audit.getModifiedTime()
407: .createDate();
408: user = server
409: .getUser(audit
410: .getInt(stPropertyNames.AUDIT_USER_ID));
411: mod.userName = user.getName();
412: }
413: }
414: } catch (TypeNotFoundException tnfx) {
415: LOG
416: .debug(
417: url
418: + ": Error looking up Audit information (StarTeam 4.2 SP2 SDK or older in use)."
419: + " Deletions will be reported as by Unknown.",
420: tnfx);
421: canLookupDeletions = false;
422: }
423: }
424: // END - 2nd Comment out if StarTeam 4.2 SP2 or older
425:
426: if (!foundAudit) {
427: mod.modifiedTime = revision.getModifiedTime()
428: .createDate();
429: mod.userName = "Unknown";
430: }
431: } else {
432: mod.modifiedTime = revision.getModifiedTime().createDate();
433: user = server.getUser(revision.getModifiedBy());
434: mod.userName = user.getName();
435: mod.comment = revision.getComment();
436: mod.revision = "" + revision.getContentVersion();
437: }
438:
439: // Only get emails for users still on the system
440: if (user != null && canLookupEmails) {
441:
442: // Try to obtain email to add. This is only allowed if logged on
443: // user is SERVER ADMINISTRATOR
444: try {
445: // check if user account exists
446: UserAccount useracct = server.getAdministration()
447: .findUserAccount(user.getID());
448: if (useracct == null) {
449: LOG.warn(url + ": User account for "
450: + user.getName() + " with ID "
451: + user.getID() + " not found.");
452: } else {
453: mod.emailAddress = useracct.getEmailAddress();
454: }
455: } catch (ServerException sx) {
456: // Logged on user does not have permission to get user's email.
457: // Return the modifying user's name instead. Then use the
458: // email.properties file to map the name to an email address
459: // outside of StarTeam
460: LOG.debug(url
461: + ": Error looking up user email address.", sx);
462: canLookupEmails = false;
463: }
464: }
465:
466: modifications.add(mod);
467: }
468:
469: }
|