package org.hudson.serena; import org.hudson.serena.model.Dimension10Installation; import com.serena.dmclient.api.DimensionsConnection; import com.serena.dmclient.api.DimensionsRelatedObject; import com.serena.dmclient.api.DimensionsResult; import com.serena.dmclient.api.DownloadCommandDetails; import com.serena.dmclient.api.Filter; import com.serena.dmclient.api.Project; import com.serena.dmclient.api.SystemAttributes; import hudson.FilePath; import hudson.Launcher; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.BuildListener; import hudson.model.TaskListener; import hudson.scm.ChangeLogParser; import hudson.scm.ChangeLogSet; import hudson.scm.ChangeLogSet.Entry; import hudson.scm.SCM; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.Serializable; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.logging.Logger; import org.kohsuke.stapler.DataBoundConstructor; import org.xml.sax.SAXException; /** * Hudson SCM implementation able to connect to a Serena Dimensions 10 * server. * * @author Jose Noheda [jose.noheda@gmail.com] */ public class DimensionsSCM extends SCM implements Serializable { private static final long serialVersionUID = 2061202506815349346L; private static final Logger LOGGER = Logger.getLogger(DimensionsSCM.class.getName()); private String product; private String project; private String subfolder; private boolean canUseUpdate; private Dimension10Installation installation; @DataBoundConstructor public DimensionsSCM(String server, String product, String project, String subfolder, boolean canUseUpdate) { setInstallation(getDescriptor().getInstallation(server)); setProduct(product); setProject(project); setSubfolder(subfolder); setCanUseUpdate(canUseUpdate); } public final String getProduct() { return product; } public final void setProduct(String product) { this.product = product; } public final String getProject() { return project; } public final void setProject(String project) { this.project = project; } public final String getSubfolder() { return subfolder; } public final void setSubfolder(String subfolder) { this.subfolder = subfolder; } public final Dimension10Installation getInstallation() { return installation; } public final void setInstallation(Dimension10Installation installation) { this.installation = installation; } public final boolean isCanUseUpdate() { return canUseUpdate; } public final void setCanUseUpdate(boolean canUseUpdate) { this.canUseUpdate = canUseUpdate; } @Override public FilePath getModuleRoot(FilePath workspace) { if ((subfolder != null) && (subfolder.trim().length() > 0)) { workspace = new FilePath(workspace, subfolder); } return workspace; } @Override public boolean pollChanges(AbstractProject build, Launcher launcher, FilePath workspace, TaskListener arg3) throws IOException, InterruptedException { FilePath projectPath = getModuleRoot(workspace); if (projectPath.exists()) { Date lastModified = new Date(projectPath.lastModified()); DimensionsConnection conn = ConnectionManager.getConnection(getInstallation()); conn.initialise(); Project dimensionsProject = conn.getObjectFactory().getProject(product + ":" + project); Filter filter = new Filter(); try { filter.criteria().add(new Filter.Criterion(SystemAttributes.IS_LATEST_REV, true, Filter.Criterion.EQUALS)); String lastModifiedFormat = new SimpleDateFormat("dd-MMM-yyyy").format(lastModified); filter.criteria().add(new Filter.Criterion(SystemAttributes.LAST_UPDATED_DATE, lastModifiedFormat, Filter.Criterion.GREATER_EQUAL)); if (nullify(subfolder) != null) { filter.criteria().add(new Filter.Criterion(SystemAttributes.FULL_PATH_NAME, subfolder + "/%", Filter.Criterion.EQUALS)); } List<DimensionsRelatedObject> items = dimensionsProject.getChildItems(filter); return (items != null) && (items.size() > 0); } catch (Exception e) { LOGGER.warning(e.getMessage()); return false; } } return true; } @Override public ChangeLogParser createChangeLogParser() { return new ChangeLogParser() { @Override public ChangeLogSet<? extends Entry> parse(final AbstractBuild build, final File changelogFile) throws IOException, SAXException { return new DimensionsChangeLogSet(build, changelogFile); } }; } @Override public boolean checkout(AbstractBuild build, Launcher launcher, FilePath workspace, BuildListener listener, File changelogFile) throws IOException, InterruptedException { FilePath projectPath = getModuleRoot(workspace); if (canUseUpdate && isUpdatable(projectPath)) { LOGGER.info("Updating [" + product + ":" + project + (subfolder == null ? "/" + subfolder : "") + "] from Dimensions server to [" + projectPath.toURI() + "]"); return doUpdate(projectPath, changelogFile); } return doCheckout(projectPath, changelogFile); } private boolean isUpdatable(FilePath projectPath) { boolean updatable = false; try { updatable = projectPath.exists() && projectPath.isDirectory(); if (updatable) { FilePath dimensionsMetadata = new FilePath(projectPath, ".metadata"); updatable = dimensionsMetadata.exists() && dimensionsMetadata.isDirectory(); } } catch (Exception ex) { LOGGER.fine("[" + projectPath + "] is not updatable: " + ex.getMessage()); updatable = false; } return updatable; } private boolean doCheckout(FilePath projectPath, File changelogFile) { try { if (projectPath.exists()) { LOGGER.info("Cleaning [" + projectPath.toURI() + "]..."); projectPath.deleteRecursive(); } projectPath.getParent().mkdirs(); LOGGER.info("Checking out [" + product + ":" + project + "/" + subfolder + "] from Dimensions server to [" + projectPath.toURI() + "]"); return doUpdate(projectPath, changelogFile); } catch (Exception ex) { LOGGER.warning("Unexpected exception preparing checkout: " + ex.getMessage()); return false; } } private boolean doUpdate(FilePath projectPath, File changelogFile) { try { DimensionsConnection conn = ConnectionManager.getConnection(getInstallation()); conn.initialise(); DownloadCommandDetails downloadDetails = new DownloadCommandDetails(); downloadDetails.setRecursive(true); String basePath = projectPath.toURI().toString().replace("file:/", "").replace("%20", " "); if ((subfolder != null) && (subfolder.trim().length() > 0)) { basePath = basePath.substring(0, basePath.length() - subfolder.trim().length()); downloadDetails.setDirectory(subfolder); } downloadDetails.setUserDirectory(basePath); downloadDetails.setVerbose(true); downloadDetails.setIgnoreErrors(true); DimensionsResult result = conn.getObjectFactory().getProject(product + ":" + project).download(downloadDetails); projectPath.touch(new Date().getTime()); generateLogFile(result.getMessage(), changelogFile); return true; } catch (Exception ex) { LOGGER.warning("Unexpected exception processing checkout: " + ex.getMessage()); try { projectPath.deleteRecursive(); } catch (Exception dex) { LOGGER.warning("Could not delete [" + projectPath + "]: " + dex.getMessage()); } return false; } } protected void generateLogFile(String contents, File changelogFile) { FileWriter writer = null; try { writer = new FileWriter(changelogFile); writer.write(contents); writer.flush(); } catch (IOException ioe) { LOGGER.warning("Unexpected exception writing Dimensions change log file: " + ioe.getMessage()); } finally { try { if (writer != null) { writer.close(); } } catch (IOException ioe) { LOGGER.warning("Could not close Dimensions change log after writing: " + ioe.getMessage()); } } } @Override public DimensionsDescriptor getDescriptor() { return DimensionsDescriptor.DESCRIPTOR; } }