/** * */ package hudson.plugins.starteam; import hudson.FilePath; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.io.Serializable; import java.text.ParseException; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.apache.commons.lang.exception.ExceptionUtils; import com.starbase.starteam.ClientApplication; import com.starbase.starteam.File; import com.starbase.starteam.Folder; import com.starbase.starteam.Item; import com.starbase.starteam.LogonException; import com.starbase.starteam.Project; import com.starbase.starteam.PropertyNames; import com.starbase.starteam.Server; import com.starbase.starteam.ServerAdministration; import com.starbase.starteam.ServerConfiguration; import com.starbase.starteam.ServerInfo; import com.starbase.starteam.Status; import com.starbase.starteam.User; import com.starbase.starteam.UserAccount; import com.starbase.starteam.View; import com.starbase.starteam.vts.comm.NetMonitor; import com.starbase.util.OLEDate; /** * StarTeamActor is a class that implements connecting to a StarTeam repository, * to a given project, view and folder. * * Add functionality allowing to delete non starteam file in folder while * performing listing of all files. and to perform creation of changelog file * during the checkout * * @author Ilkka Laukkanen <ilkka.s.laukkanen@gmail.com> * @author Steve Favez <sfavez@verisign.com> * */ public class StarTeamConnection implements Serializable { private static final long serialVersionUID = 1L; public static final String FILE_POINT_FILENAME = "starteam-filepoints.csv"; private final String hostName; private final int port; private final String userName; private final String password; private final String projectName; private final String viewName; private final String folderName; private final StarTeamViewSelector configSelector; private transient Server server; private transient View view; private transient Folder rootFolder; private transient Project project; private transient boolean canReadUserAccts = true; static { try { String netmonFile = System.getProperty("st.netmon.out"); if (netmonFile != null) { NetMonitor.onFile(new java.io.File(netmonFile)); } } catch (Throwable t) { // catch throwable to make sure the class loads, logging isn't essential try { System.err.println("Can't write StarTeam network monitor logs to netmon.out: " + t); } catch (Throwable ignore) { } } } /** * Default constructor * * @param hostName * the starteam server host / ip name * @param port * starteam server port * @param userName * user used to connect starteam server * @param password * user password to connect to starteam server * @param projectName * starteam project's name * @param viewName * starteam view's name * @param folderName * starteam folder's name * @param configSelector * configuration selector * in case of checking from label, promotion state or time */ public StarTeamConnection(String hostName, int port, String userName, String password, String projectName, String viewName, String folderName, StarTeamViewSelector configSelector) { checkParameters(hostName, port, userName, password, projectName, viewName, folderName); this.hostName = hostName; this.port = port; this.userName = userName; this.password = password; this.projectName = projectName; this.viewName = viewName; this.folderName = folderName; this.configSelector = configSelector; } public StarTeamConnection(StarTeamConnection oldConnection, StarTeamViewSelector configSelector) { this(oldConnection.hostName, oldConnection.port, oldConnection.userName, oldConnection.password, oldConnection.projectName, oldConnection.viewName, oldConnection.folderName, configSelector); } private ServerInfo createServerInfo() { ServerInfo serverInfo = new ServerInfo(); serverInfo.setConnectionType(ServerConfiguration.PROTOCOL_TCP_IP_SOCKETS); serverInfo.setHost(this.hostName); serverInfo.setPort(this.port); populateDescription(serverInfo); return serverInfo; } /** * populate the description of the server info. * * @param serverInfo */ void populateDescription(ServerInfo serverInfo) { // Increment a counter until the description is unique int counter = 0; while (!setDescription(serverInfo, counter)) ++counter; } private boolean setDescription(ServerInfo serverInfo, int counter) { try { serverInfo.setDescription("StarTeam connection to " + this.hostName + ((counter == 0) ? "" : " (" + Integer.toString(counter) + ")")); return true; } catch (com.starbase.starteam.DuplicateServerListEntryException e) { return false; } } private void checkParameters(String hostName, int port, String userName, String password, String projectName, String viewName, String folderName) { if (null == hostName) throw new NullPointerException("hostName cannot be null"); if (null == userName) throw new NullPointerException("user cannot be null"); if (null == password) throw new NullPointerException("passwd cannot be null"); if (null == projectName) throw new NullPointerException("projectName cannot be null"); if (null == viewName) throw new NullPointerException("viewName cannot be null"); if (null == folderName) throw new NullPointerException("folderName cannot be null"); if ((port < 1) || (port > 65535)) throw new IllegalArgumentException("Invalid port: " + port); } /** * Initialize the connection. This means logging on to the server and * finding the project, view and folder we want. * * @param buildNumber a job build number, or -1 if not associated with a job. * @throws StarTeamSCMException if logging on fails. */ public void initialize(int buildNumber) throws StarTeamSCMException { /* Identify this as the StarTeam Hudson Plugin so that it can support the new AppControl capability in StarTeam 2009 which allows a StarTeam administrator to block or allow Unknown or specific client/SDK applications from accessing the repository; without this, the plugin will be seen as an Unknown Client, and may be blocked by StarTeam repositories that take advantage of this feature. This must be called before a connection to the server is established. */ ClientApplication.setName("StarTeam Plugin for Jenkins"); server = new Server(createServerInfo()); server.connect(); try { server.logOn(userName, password); } catch (LogonException e) { throw new StarTeamSCMException("Could not log on: " + e.getErrorMessage()); } project = findProjectOnServer(server, projectName); view = findViewInProject(project, viewName); if (configSelector != null) { View configuredView = null; try { configuredView = configSelector.configView(view, buildNumber); } catch (ParseException e) { throw new StarTeamSCMException("Could not correctly parse configuration date: " + e.getMessage()); } if (configuredView != null) view = configuredView; } rootFolder = StarTeamFunctions.findFolderInView(view, folderName); // Cache some folder data final PropertyNames pnames = rootFolder.getPropertyNames(); final String[] filePropsToCache = new String[] { pnames.FILE_LOCAL_FILE_EXISTS, pnames.FILE_LOCAL_TIMESTAMP, pnames.FILE_NAME, pnames.FILE_FILE_TIME_AT_CHECKIN, pnames.MODIFIED_TIME, pnames.MODIFIED_USER_ID, pnames.FILE_STATUS, pnames.COMMENT, }; final String[] folderPropsToCache = new String[] { pnames.FOLDER_WORKING_FOLDER }; rootFolder.populateNow(server.getTypeNames().FILE, filePropsToCache, -1); rootFolder.populateNow(server.getTypeNames().FOLDER, folderPropsToCache, -1); } /** * checkout the files from starteam * * @param changeSet a description of changes * @param filePointFilePath A FilePath reprensenting the file points file where to store the change set * @throws IOException if checkout fails. */ public void checkOut(StarTeamChangeSet changeSet, PrintStream logger, FilePath filePointFilePath) throws IOException { logger.println("*** Performing checkout on [" + changeSet.getFilesToCheckout().size() + "] files"); boolean quietCheckout = changeSet.getFilesToCheckout().size() >= 2000; if (quietCheckout) { logger.println("*** More than 2000 files, quiet mode enabled"); } for (File f : changeSet.getFilesToCheckout()) { boolean dirty = true; switch (f.getStatus()) { case Status.UNKNOWN: dirty = false; case Status.NEW: case Status.MERGE: case Status.MODIFIED: // clobber these new java.io.File(f.getFullName()).delete(); if (!quietCheckout) logger.println("[co] Deleted File: " + f.getFullName()); break; case Status.MISSING: case Status.OUTOFDATE: dirty = false; // just go on and check out break; default: // By default do nothing, go to next iteration continue; } if (!quietCheckout) logger.println("[co] " + f.getFullName() + "... attempt"); try { f.checkout(Item.LockType.UNCHANGED, // leave the lock as is, changing lock for item in the past is impossible true, // use timestamp from local time true, // convert EOL to native format true); // update status } catch (IOException e) { logger .print("[checkout] [exception] [Problem checking out file: " + f.getFullName() + "] \n" + ExceptionUtils.getFullStackTrace(e) + "\n"); throw e; } catch (RuntimeException e) { logger .print("[checkout] [exception] [Problem checking out file: " + f.getFullName() + "] \n" + ExceptionUtils.getFullStackTrace(e) + "\n"); throw e; } if (dirty) { changeSet.getChanges().add(FileToStarTeamChangeLogEntry(f,"dirty")); } if (!quietCheckout) logger.println("[co] " + f.getFullName() + "... ok"); } logger.println("*** removing [" + changeSet.getFilesToRemove().size() + "] files"); boolean quietDelete = changeSet.getFilesToRemove().size() > 100; if (quietDelete) { logger.println("*** More than 100 files, quiet mode enabled"); } for (java.io.File f : changeSet.getFilesToRemove()) { if (f.exists()) { if (!quietDelete) logger.println("[remove] [" + f + "]"); f.delete(); } else { logger.println("[remove:warn] Planned to remove [" + f + "]"); } } logger.println("*** storing change set"); OutputStream os = null; try { os = new BufferedOutputStream(filePointFilePath.write()); StarTeamFilePointFunctions.storeCollection(os, changeSet.getFilePointsToRemember()); } catch (InterruptedException e) { logger.println( "unable to store change set " + e.getMessage()) ; }finally{ if(os !=null){ os.close(); } } logger.println("***checkout done"); } /** * Returns the name of the user on the StarTeam server with the specified * id. StarTeam stores user IDs as int values and this method will translate * those into the actual user name. <br/> This can be used, for example, * with a StarTeam {@link Item}'s {@link Item#getModifiedBy()} property, to * determine the name of the user who made a modification to the item. * * @param userId * the id of the user on the StarTeam Server * @return the name of the user as provided by the StarTeam Server */ public String getUsername(int userId) { User stUser = server.getUser(userId); String userName =stUser.getName(); ServerAdministration srvAdmin = server.getAdministration(); UserAccount[] userAccts = null; if (canReadUserAccts) { try { userAccts = srvAdmin.getUserAccounts(); } catch (Exception e) { // System.out.println("WARNING: Looks like this user does not have the permission to access UserAccounts on the StarTeam Server!"); // System.out.println("WARNING: Please contact your administrator and ask to be given the permission \"Administer User Accounts\" on the server."); // System.out.println("WARNING: Defaulting to just using User Full Names which breaks the ability to send email to the individuals who break the build in Hudson!"); canReadUserAccts = false; } } if (userAccts != null) { for (int i=0; i<userAccts.length; i++) { UserAccount ua = userAccts[i]; if (ua.getName().equals(userName)) { System.out.println("INFO: From \'" + userName + "\' found existing user LogonName = " + ua.getLogOnName() + " with ID \'" + ua.getID() + "\' and email \'" + ua.getEmailAddress() +"\'"); return ua.getLogOnName(); } } } else { // Since the user account running the build does not have user admin perms // use the User Full Name return userName; } return "unknown"; } public Folder getRootFolder() { return rootFolder; } public OLEDate getServerTime() { return server.getCurrentTime(); } /** * @param server * @param projectname * @return Project specified by the projectname * @throws StarTeamSCMException */ static Project findProjectOnServer(final Server server, final String projectname) throws StarTeamSCMException { for (Project project : server.getProjects()) { if (project.getName().equals(projectname)) { return project; } } throw new StarTeamSCMException("Couldn't find project " + projectname + " on server " + server.getAddress()); } /** * @param project * @param viewname * @return * @throws StarTeamSCMException */ static View findViewInProject(final Project project, final String viewname) throws StarTeamSCMException { for (View view : project.getAccessibleViews()) { if (view.getName().equals(viewname)) { return view; } } throw new StarTeamSCMException("Couldn't find view " + viewname + " in project " + project.getName()); } /** * Close the connection. */ public void close() { if (server.isConnected()) { if (rootFolder != null) { rootFolder.discardItems(rootFolder.getTypeNames().FILE, -1); rootFolder.discardItems(rootFolder.getTypeNames().FOLDER, -1); } view.discard(); project.discard(); server.disconnect(); } } @Override protected void finalize() throws Throwable { close(); } @Override public boolean equals(Object object) { if (null == object) return false; if (!getClass().equals(object.getClass())) return false; StarTeamConnection other = (StarTeamConnection) object; return port == other.port && hostName.equals(other.hostName) && userName.equals(other.userName) && password.equals(other.password) && projectName.equals(other.projectName) && viewName.equals(other.viewName) && folderName.equals(other.folderName); } @Override public int hashCode() { return userName.hashCode(); } @Override public String toString() { return "host: " + hostName + ", port: " + Integer.toString(port) + ", user: " + userName + ", passwd: ******, project: " + projectName + ", view: " + viewName + ", folder: " + folderName; } /** * @param rootFolder main project directory * @param workspace a workspace directory * @param historicFilePoints a collection containing File Points to be compared (previous build) * @param logger a logger for consuming log messages * @return set of changes * @throws StarTeamSCMException * @throws IOException */ public StarTeamChangeSet computeChangeSet(Folder rootFolder, java.io.File workspace, final Collection<StarTeamFilePoint> historicFilePoints, PrintStream logger) throws StarTeamSCMException, IOException { // --- compute changes as per starteam final Collection<com.starbase.starteam.File> starteamFiles = StarTeamFunctions.listAllFiles(rootFolder, workspace); final Map<java.io.File, com.starbase.starteam.File> starteamFileMap = StarTeamFunctions.convertToFileMap(starteamFiles); final Collection<java.io.File> starteamFileSet = starteamFileMap.keySet(); final Collection<StarTeamFilePoint> starteamFilePoint = StarTeamFilePointFunctions.convertFilePointCollection(starteamFiles); final Collection<java.io.File> fileSystemFiles = StarTeamFilePointFunctions.listAllFiles(workspace); final Collection<java.io.File> fileSystemRemove = new TreeSet<java.io.File>(fileSystemFiles); fileSystemRemove.removeAll(starteamFileSet); final StarTeamChangeSet changeSet = new StarTeamChangeSet(); changeSet.setFilesToCheckout(starteamFiles); changeSet.setFilesToRemove(fileSystemRemove); changeSet.setFilePointsToRemember(starteamFilePoint); // --- compute differences as per historic storage file if (historicFilePoints != null && !historicFilePoints.isEmpty()) { try { changeSet.setComparisonAvailable(true); computeDifference(starteamFilePoint, historicFilePoints, changeSet, starteamFileMap); } catch (Throwable t) { t.printStackTrace(logger); } } else { for (File file: starteamFiles) { changeSet.addChange(FileToStarTeamChangeLogEntry(file)); } } return changeSet; } public StarTeamChangeLogEntry FileToStarTeamChangeLogEntry (File f) { return FileToStarTeamChangeLogEntry(f, "change"); } public StarTeamChangeLogEntry FileToStarTeamChangeLogEntry (File f, String change) { int revisionNumber = f.getContentVersion(); int userId = f.getModifiedBy(); String username = getUsername(userId); String msg = f.getComment(); Date date = new Date(f.getModifiedTime().getLongValue()); String fileName = f.getName(); return new StarTeamChangeLogEntry(fileName,revisionNumber,date,username,msg, change); } public StarTeamChangeSet computeDifference(final Collection<StarTeamFilePoint> currentFilePoint, final Collection<StarTeamFilePoint> historicFilePoint, StarTeamChangeSet changeSet, Map<java.io.File, com.starbase.starteam.File> starteamFileMap) { final Map<java.io.File, StarTeamFilePoint> starteamFilePointMap = StarTeamFilePointFunctions.convertToFilePointMap(currentFilePoint); Map<java.io.File, StarTeamFilePoint> historicFilePointMap = StarTeamFilePointFunctions.convertToFilePointMap(historicFilePoint); final Set<java.io.File> starteamOnly = new HashSet<java.io.File>(); starteamOnly.addAll(starteamFilePointMap.keySet()); starteamOnly.removeAll(historicFilePointMap.keySet()); final Set<java.io.File> historicOnly = new HashSet<java.io.File>(); historicOnly.addAll(historicFilePointMap.keySet()); historicOnly.removeAll(starteamFilePointMap.keySet()); final Set<java.io.File> common = new HashSet<java.io.File>(); common.addAll(starteamFilePointMap.keySet()); common.removeAll(starteamOnly); final Set<java.io.File> higher = new HashSet<java.io.File>(); // newer revision final Set<java.io.File> lower = new HashSet<java.io.File>(); // typically rollback of a revision StarTeamChangeLogEntry change; for (java.io.File f : common) { StarTeamFilePoint starteam = starteamFilePointMap.get(f); StarTeamFilePoint historic = historicFilePointMap.get(f); if (starteam.getRevisionNumber() == historic.getRevisionNumber()) { //unchanged files continue; } com.starbase.starteam.File stf = starteamFileMap.get(f); if (starteam.getRevisionNumber() > historic.getRevisionNumber()) { higher.add(f); changeSet.addChange(FileToStarTeamChangeLogEntry(stf,"change")); } if (starteam.getRevisionNumber() < historic.getRevisionNumber()) { lower.add(f); changeSet.addChange(FileToStarTeamChangeLogEntry(stf,"rollback")); } } for (java.io.File f : historicOnly) { StarTeamFilePoint historic = historicFilePointMap.get(f); change = new StarTeamChangeLogEntry(f.getName(), historic.getRevisionNumber(), new Date(), "", "", "removed"); changeSet.addChange(change); } for (java.io.File f : starteamOnly) { com.starbase.starteam.File stf = starteamFileMap.get(f); changeSet.addChange(FileToStarTeamChangeLogEntry(stf,"added")); } return changeSet; } }