/* =========================================================================== * Copyright (c) 2007 Serena Software. All rights reserved. * * Use of the Sample Code provided by Serena is governed by the following * terms and conditions. By using the Sample Code, you agree to be bound by * the terms contained herein. If you do not agree to the terms herein, do * not install, copy, or use the Sample Code. * * 1. GRANT OF LICENSE. Subject to the terms and conditions herein, you * shall have the nonexclusive, nontransferable right to use the Sample Code * for the sole purpose of developing applications for use solely with the * Serena software product(s) that you have licensed separately from Serena. * Such applications shall be for your internal use only. You further agree * that you will not: (a) sell, market, or distribute any copies of the * Sample Code or any derivatives or components thereof; (b) use the Sample * Code or any derivatives thereof for any commercial purpose; or (c) assign * or transfer rights to the Sample Code or any derivatives thereof. * * 2. DISCLAIMER OF WARRANTIES. TO THE MAXIMUM EXTENT PERMITTED BY * APPLICABLE LAW, SERENA PROVIDES THE SAMPLE CODE AS IS AND WITH ALL * FAULTS, AND HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, EITHER * EXPRESSED, IMPLIED OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY * IMPLIED WARRANTIES OR CONDITIONS OF MERCHANTABILITY, OF FITNESS FOR A * PARTICULAR PURPOSE, OF LACK OF VIRUSES, OF RESULTS, AND OF LACK OF * NEGLIGENCE OR LACK OF WORKMANLIKE EFFORT, CONDITION OF TITLE, QUIET * ENJOYMENT, OR NON-INFRINGEMENT. THE ENTIRE RISK AS TO THE QUALITY OF * OR ARISING OUT OF USE OR PERFORMANCE OF THE SAMPLE CODE, IF ANY, * REMAINS WITH YOU. * * 3. EXCLUSION OF DAMAGES. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE * LAW, YOU AGREE THAT IN CONSIDERATION FOR RECEIVING THE SAMPLE CODE AT NO * CHARGE TO YOU, SERENA SHALL NOT BE LIABLE FOR ANY DAMAGES WHATSOEVER, * INCLUDING BUT NOT LIMITED TO DIRECT, SPECIAL, INCIDENTAL, INDIRECT, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, DAMAGES FOR LOSS OF * PROFITS OR CONFIDENTIAL OR OTHER INFORMATION, FOR BUSINESS INTERRUPTION, * FOR PERSONAL INJURY, FOR LOSS OF PRIVACY, FOR NEGLIGENCE, AND FOR ANY * OTHER LOSS WHATSOEVER) ARISING OUT OF OR IN ANY WAY RELATED TO THE USE * OF OR INABILITY TO USE THE SAMPLE CODE, EVEN IN THE EVENT OF THE FAULT, * TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY, OR BREACH OF CONTRACT, * EVEN IF SERENA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. THE * FOREGOING LIMITATIONS, EXCLUSIONS AND DISCLAIMERS SHALL APPLY TO THE * MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW. NOTWITHSTANDING THE ABOVE, * IN NO EVENT SHALL SERENA'S LIABILITY UNDER THIS AGREEMENT OR WITH RESPECT * TO YOUR USE OF THE SAMPLE CODE AND DERIVATIVES THEREOF EXCEED US$10.00. * * 4. INDEMNIFICATION. You hereby agree to defend, indemnify and hold * harmless Serena from and against any and all liability, loss or claim * arising from this agreement or from (i) your license of, use of or * reliance upon the Sample Code or any related documentation or materials, * or (ii) your development, use or reliance upon any application or * derivative work created from the Sample Code. * * 5. TERMINATION OF THE LICENSE. This agreement and the underlying * license granted hereby shall terminate if and when your license to the * applicable Serena software product terminates or if you breach any terms * and conditions of this agreement. * * 6. CONFIDENTIALITY. The Sample Code and all information relating to the * Sample Code (collectively "Confidential Information") are the * confidential information of Serena. You agree to maintain the * Confidential Information in strict confidence for Serena. You agree not * to disclose or duplicate, nor allow to be disclosed or duplicated, any * Confidential Information, in whole or in part, except as permitted in * this Agreement. You shall take all reasonable steps necessary to ensure * that the Confidential Information is not made available or disclosed by * you or by your employees to any other person, firm, or corporation. You * agree that all authorized persons having access to the Confidential * Information shall observe and perform under this nondisclosure covenant. * You agree to immediately notify Serena of any unauthorized access to or * possession of the Confidential Information. * * 7. AFFILIATES. Serena as used herein shall refer to Serena Software, * Inc. and its affiliates. An entity shall be considered to be an * affiliate of Serena if it is an entity that controls, is controlled by, * or is under common control with Serena. * * 8. GENERAL. Title and full ownership rights to the Sample Code, * including any derivative works shall remain with Serena. If a court of * competent jurisdiction holds any provision of this agreement illegal or * otherwise unenforceable, that provision shall be severed and the * remainder of the agreement shall remain in full force and effect. * =========================================================================== */ /* * This experimental plugin extends Hudson support for Dimensions SCM repositories * * @author Tim Payne * */ // Package name package hudson.plugins.dimensionsscm; // Dimensions imports import hudson.plugins.dimensionsscm.DimensionsAPI; import hudson.plugins.dimensionsscm.DimensionsSCMRepositoryBrowser; import hudson.plugins.dimensionsscm.Logger; import hudson.plugins.dimensionsscm.DimensionsChangeLogParser; import hudson.plugins.dimensionsscm.DimensionsBuildWrapper; import hudson.plugins.dimensionsscm.DimensionsBuildNotifier; import hudson.plugins.dimensionsscm.DimensionsChecker; import hudson.plugins.dimensionsscm.CheckOutAPITask; import hudson.plugins.dimensionsscm.CheckOutCmdTask; import hudson.plugins.dimensionsscm.GetHostDetailsTask; // Hudson imports import hudson.Extension; import hudson.FilePath.FileCallable; import hudson.FilePath; import hudson.Launcher; import hudson.Util; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.BuildListener; import hudson.model.Computer; import hudson.model.Hudson.MasterComputer; import hudson.model.Hudson; import hudson.model.ModelObject; import hudson.model.Node; import hudson.model.Run; import hudson.model.TaskListener; import hudson.remoting.Callable; import hudson.remoting.Channel; import hudson.remoting.DelegatingCallable; import hudson.remoting.VirtualChannel; import hudson.scm.ChangeLogParser; import hudson.scm.RepositoryBrowsers; import hudson.scm.SCM; import hudson.scm.SCMDescriptor; import hudson.util.FormValidation; import hudson.util.Scrambler; import hudson.util.VariableResolver; // General imports import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.io.Serializable; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TimeZone; import java.util.Vector; import java.net.InetSocketAddress; import java.net.InetAddress; import java.net.UnknownHostException; import javax.servlet.ServletException; import net.sf.json.JSONObject; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import org.apache.commons.lang.StringUtils; /* * Hudson requires the following functions to be implemented * * public boolean checkout(AbstractBuild build, Launcher launcher, FilePath workspace, BuildListener listener, File changelogFile) * throws IOException, InterruptedException; * public boolean pollChanges(AbstractProject project, Launcher launcher, FilePath workspace, TaskListener listener) * throws IOException, InterruptedException; * public ChangeLogParser createChangeLogParser(); * public SCMDescriptor<?> getDescriptor(); * * For this experimental plugin, only the main ones will be implemented * */ /* * Main Dimensions SCM class which creates the plugin logic */ public class DimensionsSCM extends SCM implements Serializable { // Hudson details private String project; private String directory; private String permissions; private String jobUserName; private String jobPasswd; private String jobServer; private String jobDatabase; private String[] folders = new String[0]; private String jobTimeZone; private String jobWebUrl; private boolean canJobUpdate; private boolean canJobDelete; private boolean canJobForce; private boolean canJobRevert; private boolean canJobExpand; private boolean canJobNoMetadata; DimensionsAPI dmSCM; DimensionsSCMRepositoryBrowser browser; public DimensionsSCM getSCM() { return this; } public DimensionsAPI getAPI() { return this.dmSCM; } public DimensionsSCMRepositoryBrowser getBrowser() { return this.browser; } /* * Gets the project ID for the connection. * @return the project ID */ public String getProject() { return this.project; } /* * Gets the project path. * @return the project path */ public String getDirectory() { return this.directory; } /* * Gets the permissions . * @return the permissions */ public String getPermissions() { return this.permissions; } /* * Gets the project paths. * @return the project paths */ public String[] getFolders() { return this.folders; } /* * Gets the job user ID for the connection. * @return the job user ID */ public String getJobUserName() { return this.jobUserName; } /* * Gets the job passwd for the connection. * @return the project ID */ public String getJobPasswd() { return Scrambler.descramble(jobPasswd); } /* * Gets the server ID for the connection. * @return the server ID */ public String getJobServer() { return this.jobServer; } /* * Gets the job database ID for the connection. * @return the job database ID */ public String getJobDatabase() { return this.jobDatabase; } /* * Gets the job timezone for the connection. * @return the job timezone */ public String getJobTimeZone() { return this.jobTimeZone; } /* * Gets the job weburl ID for the connection. * @return the job weburl */ public String getJobWebUrl() { return this.jobWebUrl; } /* * Gets the expand . * @return the expand */ public boolean isCanJobExpand() { return this.canJobExpand; } /* * Gets the no metadata . * @return the nometadata */ public boolean isCanJobNoMetadata() { return this.canJobNoMetadata; } /* * Gets the update . * @return the update */ public boolean isCanJobUpdate() { return this.canJobUpdate; } /* * Gets the delete . * @return the delete */ public boolean isCanJobDelete() { return this.canJobDelete; } /* * Gets the force . * @return the force */ public boolean isCanJobForce() { return this.canJobForce; } /* * Gets the revert . * @return the force */ public boolean isCanJobRevert() { return this.canJobRevert; } @Extension public static final DescriptorImpl DM_DESCRIPTOR = new DescriptorImpl(); /* *----------------------------------------------------------------- * FUNCTION SPECIFICATION * Name: * requiresWorkspaceForPolling * Description: * Does this SCM plugin require a workspace for polling? * Parameters: * Return: * @return boolean *----------------------------------------------------------------- */ @Override public boolean requiresWorkspaceForPolling() { return false; } /* *----------------------------------------------------------------- * FUNCTION SPECIFICATION * Name: * supportsPolling * Description: * Does this SCM plugin support polling? * Parameters: * Return: * @return boolean *----------------------------------------------------------------- */ @Override public boolean supportsPolling() { return true; } /* *----------------------------------------------------------------- * FUNCTION SPECIFICATION * Name: * buildEnvVars * Description: * Build up environment variables for build support * Parameters: * Return: *----------------------------------------------------------------- */ @Override public void buildEnvVars(AbstractBuild build, Map<String, String> env) { // To be implemented when build support put in super.buildEnvVars(build, env); return; } /* *----------------------------------------------------------------- * FUNCTION SPECIFICATION * Name: * Constructor * Description: * Default constructor for the plugin * Parameters: * @param String project * @param String workspaceName * @param String workarea * @param String jobServer * @param String jobUserName * @param String jobPasswd * @param String jobDatabase * Return: * @return void *----------------------------------------------------------------- */ public DimensionsSCM(String project, String directory, String workarea, boolean canJobDelete, boolean canJobForce, boolean canJobRevert, String jobUserName, String jobPasswd, String jobServer, String jobDatabase, boolean canJobUpdate, String jobTimeZone, String jobWebUrl) { this(project,null,workarea,canJobDelete, canJobForce,canJobRevert, jobUserName,jobPasswd, jobServer,jobDatabase, canJobUpdate,jobTimeZone, jobWebUrl,directory,"DEFAULT",false,false); } public DimensionsSCM(String project, String[] folders, String workarea, boolean canJobDelete, boolean canJobForce, boolean canJobRevert, String jobUserName, String jobPasswd, String jobServer, String jobDatabase, boolean canJobUpdate, String jobTimeZone, String jobWebUrl, String directory, String permissions) { this(project,null,workarea,canJobDelete, canJobForce,canJobRevert, jobUserName,jobPasswd, jobServer,jobDatabase, canJobUpdate,jobTimeZone, jobWebUrl,directory,permissions,false,false); } /* *----------------------------------------------------------------- * FUNCTION SPECIFICATION * Name: * Constructor * Description: * Default constructor for the plugin * Parameters: * @param String project * @param String[] folderNames * @param String workspaceName * @param String workarea * @param String jobServer * @param String jobUserName * @param String jobPasswd * @param String jobDatabase * @param String directory * @param String permissions * @param boolean canJobExpand * @param boolean canJobNoMetadata * Return: * @return void *----------------------------------------------------------------- */ @DataBoundConstructor public DimensionsSCM(String project, String[] folders, String workarea, boolean canJobDelete, boolean canJobForce, boolean canJobRevert, String jobUserName, String jobPasswd, String jobServer, String jobDatabase, boolean canJobUpdate, String jobTimeZone, String jobWebUrl, String directory, String permissions, boolean canJobExpand, boolean canJobNoMetadata) { // Check the folders specified have data specified if (folders != null) { Logger.Debug("Folders are populated"); Vector<String> x = new Vector<String>(); for(int t=0;t<folders.length;t++) { if (StringUtils.isNotEmpty(folders[t])) x.add(folders[t]); } this.folders = (String[])x.toArray(new String[1]); } else { if (directory != null) this.folders[0] = directory; } // If nothing specified, then default to '/' if (this.folders.length < 2) { if (this.folders[0] == null || this.folders[0].length() < 1) this.folders[0] = "/"; } // Copying arguments to fields this.project = (Util.fixEmptyAndTrim(project) == null ? "${JOB_NAME}" : project); this.directory = (Util.fixEmptyAndTrim(directory) == null ? null : directory); this.permissions = (Util.fixEmptyAndTrim(permissions) == null ? "DEFAULT" : permissions); this.jobServer = (Util.fixEmptyAndTrim(jobServer) == null ? getDescriptor().getServer() : jobServer); this.jobUserName = (Util.fixEmptyAndTrim(jobUserName) == null ? getDescriptor().getUserName() : jobUserName); this.jobDatabase = (Util.fixEmptyAndTrim(jobDatabase) == null ? getDescriptor().getDatabase() : jobDatabase); String passwd = (Util.fixEmptyAndTrim(jobPasswd) == null ? getDescriptor().getPasswd() : jobPasswd); this.jobPasswd = Scrambler.scramble(passwd); if ((Util.fixEmptyAndTrim(jobServer)) == null) { this.canJobUpdate = getDescriptor().isCanUpdate(); } else { this.canJobUpdate = canJobUpdate; } this.canJobDelete = canJobDelete; this.canJobForce = canJobForce; this.canJobRevert = canJobRevert; this.canJobExpand = canJobExpand; this.canJobNoMetadata = canJobNoMetadata; this.jobTimeZone = (Util.fixEmptyAndTrim(jobTimeZone) == null ? getDescriptor().getTimeZone() : jobTimeZone); this.jobWebUrl = (Util.fixEmptyAndTrim(jobWebUrl) == null ? getDescriptor().getWebUrl() : jobWebUrl); String dmS = this.jobServer + "-" + this.jobUserName + ":" + this.jobDatabase; Logger.Debug("Starting job for project '" + this.project + "' ('" + this.folders.length + "')" + ", connecting to " + dmS); } /* *----------------------------------------------------------------- * FUNCTION SPECIFICATION * Name: * checkout * Description: * Checkout method for the plugin * Parameters: * @param AbstractBuild build * @param Launcher launcher * @param FilePath workspace * @param BuildListener listener * @param File changelogFile * Return: * @return boolean *----------------------------------------------------------------- */ @Override public boolean checkout(final AbstractBuild build, final Launcher launcher, final FilePath workspace, final BuildListener listener, final File changelogFile) throws IOException, InterruptedException { boolean bRet = false; if (!isCanJobUpdate()) { Logger.Debug("Skipping checkout - " + this.getClass().getName()); } Logger.Debug("Invoking checkout - " + this.getClass().getName()); try { // Load other Dimensions plugins if set DimensionsBuildWrapper.DescriptorImpl bwplugin = (DimensionsBuildWrapper.DescriptorImpl) Hudson.getInstance().getDescriptor(DimensionsBuildWrapper.class); DimensionsBuildNotifier.DescriptorImpl bnplugin = (DimensionsBuildNotifier.DescriptorImpl) Hudson.getInstance().getDescriptor(DimensionsBuildNotifier.class); if (DimensionsChecker.isValidPluginCombination(build,listener)) { Logger.Debug("Plugins are ok"); } else { listener.fatalError("\n[DIMENSIONS] The plugin combinations you have selected are not valid."); listener.fatalError("\n[DIMENSIONS] Please review online help to determine valid plugin uses."); return false; } if (isCanJobUpdate()) { int version = 2009; long key = dmSCM.login(getJobUserName(),getJobPasswd(), getJobDatabase(),getJobServer()); if (key>0) { // Get the server version Logger.Debug("Login worked."); version = dmSCM.getDmVersion(); if (version == 0) { version = 2009; } dmSCM.logout(key); } // Get the details of the master InetAddress netAddr = InetAddress.getLocalHost(); byte[] ipAddr = netAddr.getAddress(); String hostname = netAddr.getHostName(); boolean master = false; GetHostDetailsTask buildHost = new GetHostDetailsTask(hostname); master = workspace.act(buildHost); if (master) { // Running on master... listener.getLogger().println("[DIMENSIONS] Running checkout on master..."); listener.getLogger().flush(); // Using Java API because this allows the plugin to work on platforms // where Dimensions has not been ported, e.g. MAC OS, which is what // I use CheckOutAPITask task = new CheckOutAPITask(build,this,workspace,listener,version); bRet = workspace.act(task); } else { // Running on slave... Have to use the command line as Java API will not // work on remote hosts. Cannot serialise it... { // VariableResolver does not appear to be serialisable either, so... VariableResolver<String> myResolver = build.getBuildVariableResolver(); String baseline = myResolver.resolve("DM_BASELINE"); String requests = myResolver.resolve("DM_REQUEST"); listener.getLogger().println("[DIMENSIONS] Running checkout on slave..."); listener.getLogger().flush(); CheckOutCmdTask task = new CheckOutCmdTask(getJobUserName(), getJobPasswd(), getJobDatabase(), getJobServer(), getProject(), baseline, requests, isCanJobDelete(), isCanJobRevert(), isCanJobForce(), isCanJobExpand(), isCanJobNoMetadata(), (build.getPreviousBuild() == null), getFolders(),version, permissions, workspace,listener); bRet = workspace.act(task); } } } else { bRet = true; } if (bRet) { bRet = generateChangeSet(build,listener,changelogFile); } } catch(Exception e) { String errMsg = e.getMessage(); if (errMsg == null) { errMsg = "An unknown error occurred. Please try the operation again."; } listener.fatalError("Unable to run checkout callout - " + errMsg); // e.printStackTrace(); //throw new IOException("Unable to run checkout callout - " + e.getMessage()); bRet = false; } return bRet; } /* *----------------------------------------------------------------- * FUNCTION SPECIFICATION * Name: * generateChangeSet * Description: * Generate the changeset * Parameters: * @param AbstractProject build * @param BuildListener listener * @param File changelogFile * Return: * @return boolean *----------------------------------------------------------------- */ private boolean generateChangeSet(final AbstractBuild build, final BuildListener listener, final File changelogFile) throws IOException, InterruptedException { long key = -1; boolean bRet = false; DimensionsAPI dmSCM = new DimensionsAPI(); try { // When are we building files for? // Looking for the last successful build and then going forward from there - could use the last build as well // // Calendar lastBuildCal = (build.getPreviousBuild() != null) ? build.getPreviousBuild().getTimestamp() : null; Calendar lastBuildCal = (build.getPreviousNotFailedBuild() != null) ? build.getPreviousNotFailedBuild().getTimestamp() : null; Calendar nowDateCal = Calendar.getInstance(); TimeZone tz = (getJobTimeZone() != null && getJobTimeZone().length() > 0) ? TimeZone.getTimeZone(getJobTimeZone()) : TimeZone.getDefault(); if (getJobTimeZone() != null && getJobTimeZone().length() > 0) Logger.Debug("Job timezone setting is " + getJobTimeZone()); Logger.Debug("Log updates between " + ((lastBuildCal != null) ? DateUtils.getStrDate(lastBuildCal,tz) : "0") + " -> " + DateUtils.getStrDate(nowDateCal,tz) + " (" + tz.getID() + ")"); dmSCM.setLogger(listener.getLogger()); // Connect to Dimensions... key = dmSCM.login(getJobUserName(),getJobPasswd(), getJobDatabase(),getJobServer()); if (key>0) { Logger.Debug("Login worked."); VariableResolver<String> myResolver = build.getBuildVariableResolver(); String baseline = myResolver.resolve("DM_BASELINE"); String requests = myResolver.resolve("DM_REQUEST"); if (baseline != null) { baseline = baseline.trim(); baseline = baseline.toUpperCase(); } if (requests != null) { requests = requests.replaceAll(" ",""); requests = requests.toUpperCase(); } Logger.Debug("Extra parameters - " + baseline + " " + requests); String[] folders = getFolders(); if (baseline != null && baseline.length() == 0) baseline = null; if (requests != null && requests.length() == 0) requests = null; bRet = true; // Iterate through the project folders and process them in Dimensions for (int ii=0;ii<folders.length; ii++) { if (!bRet) break; String folderN = folders[ii]; File fileName = new File(folderN); FilePath dname = new FilePath(fileName); Logger.Debug("Looking for changes in '" + folderN + "'..."); // Checkout the folder bRet = dmSCM.createChangeSetLogs(key,getProject(),dname, lastBuildCal,nowDateCal, changelogFile, tz, jobWebUrl, baseline,requests); if (requests != null) break; } // Close the changes log file { FileWriter logFile = null; try { logFile = new FileWriter(changelogFile,true); PrintWriter fmtWriter = new PrintWriter(logFile); fmtWriter.println("</changelog>"); logFile.flush(); bRet=true; } catch (Exception e) { throw new IOException("Unable to write change log - " + e.getMessage()); } finally { logFile.close(); } } } } catch(Exception e) { String errMsg = e.getMessage(); if (errMsg == null) { errMsg = "An unknown error occurred. Please try the operation again."; } listener.fatalError("Unable to run change set callout - " + errMsg); // e.printStackTrace(); //throw new IOException("Unable to run change set callout - " + e.getMessage()); bRet = false; } finally { dmSCM.logout(key); } return bRet; } /* *----------------------------------------------------------------- * FUNCTION SPECIFICATION * Name: * pollChanges * Description: * Has the repository had any changes? * Parameters: * @param AbstractProject project * @param Launcher launcher * @param FilePath workspace * @param TaskListener listener * Return: * @return boolean *----------------------------------------------------------------- */ @Override public boolean pollChanges(final AbstractProject project, final Launcher launcher, final FilePath workspace, final TaskListener listener) throws IOException, InterruptedException { boolean bChanged = false; Logger.Debug("Invoking pollChanges - " + this.getClass().getName() ); Logger.Debug("Checking job - " + project.getName()); long key = -1; if (getProject() == null || getProject().length() == 0) return false; if (project.getLastBuild() == null) return true; try { Calendar lastBuildCal = null; if (project.getLastSuccessfulBuild() != null && project.getLastSuccessfulBuild().getTimestamp() != null) lastBuildCal = project.getLastSuccessfulBuild().getTimestamp(); else lastBuildCal = project.getLastBuild().getTimestamp(); Calendar nowDateCal = Calendar.getInstance(); TimeZone tz = (getJobTimeZone() != null && getJobTimeZone().length() > 0) ? TimeZone.getTimeZone(getJobTimeZone()) : TimeZone.getDefault(); if (getJobTimeZone() != null && getJobTimeZone().length() > 0) Logger.Debug("Job timezone setting is " + getJobTimeZone()); Logger.Debug("Checking for any updates between " + ((lastBuildCal != null) ? DateUtils.getStrDate(lastBuildCal,tz) : "0") + " -> " + DateUtils.getStrDate(nowDateCal,tz) + " (" + tz.getID() + ")"); if (dmSCM == null) { Logger.Debug("Creating new API interface object"); dmSCM = new DimensionsAPI(); } dmSCM.setLogger(listener.getLogger()); // Connect to Dimensions... key = dmSCM.login(jobUserName, getJobPasswd(), jobDatabase, jobServer); if (key>0) { String[] folders = getFolders(); // Iterate through the project folders and process them in Dimensions for (int ii=0;ii<folders.length; ii++) { if (bChanged) break; String folderN = folders[ii]; File fileName = new File(folderN); FilePath dname = new FilePath(fileName); Logger.Debug("Polling using key "+key); Logger.Debug("Polling '" + folderN + "'..."); bChanged = dmSCM.hasRepositoryBeenUpdated(key,getProject(), dname,lastBuildCal, nowDateCal, tz); } } } catch(Exception e) { String errMsg = e.getMessage(); if (errMsg == null) { errMsg = "An unknown error occurred. Please try the operation again."; } listener.fatalError("Unable to run pollChanges callout - " + errMsg); // e.printStackTrace(); //throw new IOException("Unable to run pollChanges callout - " + e.getMessage()); bChanged = false; } finally { dmSCM.logout(key); } if (bChanged) Logger.Debug("Polling returned true"); return bChanged; } /* *----------------------------------------------------------------- * FUNCTION SPECIFICATION * Name: * createChangeLogParser * Description: * Create a log parser object * Parameters: * Return: * @return ChangeLogParser *----------------------------------------------------------------- */ @Override public ChangeLogParser createChangeLogParser() { Logger.Debug("Invoking createChangeLogParser - " + this.getClass().getName()); return new DimensionsChangeLogParser(); } /* *----------------------------------------------------------------- * FUNCTION SPECIFICATION * Name: * SCMDescriptor * Description: * Return an SCM descriptor * Parameters: * Return: * @return DescriptorImpl *----------------------------------------------------------------- */ @Override public DescriptorImpl getDescriptor() { return DM_DESCRIPTOR; } /* * Implementation class for Dimensions plugin */ public static class DescriptorImpl extends SCMDescriptor<DimensionsSCM> implements ModelObject { DimensionsAPI connectionCheck = null; private String server; private String userName; private String passwd; private String database; private String timeZone; private String webUrl; private boolean canUpdate; /* * Loads the SCM descriptor */ public DescriptorImpl() { super(DimensionsSCM.class, DimensionsSCMRepositoryBrowser.class); load(); Logger.Debug("Loading " + this.getClass().getName()); } public String getDisplayName() { return "Dimensions"; } /* * Save the SCM descriptor configuration */ @Override public boolean configure(StaplerRequest req, JSONObject jobj) throws FormException { // Get the values and check them userName = req.getParameter("dimensionsscm.userName"); passwd = req.getParameter("dimensionsscm.passwd"); server = req.getParameter("dimensionsscm.server"); database = req.getParameter("dimensionsscm.database"); timeZone = req.getParameter("dimensionsscm.timeZone"); webUrl = req.getParameter("dimensionsscm.webUrl"); if (userName != null) userName = Util.fixNull(req.getParameter("dimensionsscm.userName").trim()); if (passwd != null) passwd = Util.fixNull(req.getParameter("dimensionsscm.passwd").trim()); if (server != null) server = Util.fixNull(req.getParameter("dimensionsscm.server").trim()); if (database != null) database = Util.fixNull(req.getParameter("dimensionsscm.database").trim()); if (timeZone != null) timeZone = Util.fixNull(req.getParameter("dimensionsscm.timeZone").trim()); if (webUrl != null) webUrl = Util.fixNull(req.getParameter("dimensionsscm.webUrl").trim()); req.bindJSON(DM_DESCRIPTOR, jobj); this.save(); return super.configure(req, jobj); } @Override public SCM newInstance(StaplerRequest req, JSONObject formData) throws FormException { // Get variables and then construct a new object String[] folders = req.getParameterValues("dimensionsscm.folders"); String project = req.getParameter("dimensionsscm.project"); String directory = req.getParameter("dimensionsscm.directory"); String permissions = req.getParameter("dimensionsscm.permissions"); Boolean canJobDelete = Boolean.valueOf("on".equalsIgnoreCase(req.getParameter("dimensionsscm.canJobDelete"))); Boolean canJobForce = Boolean.valueOf("on".equalsIgnoreCase(req.getParameter("dimensionsscm.canJobForce"))); Boolean canJobRevert = Boolean.valueOf("on".equalsIgnoreCase(req.getParameter("dimensionsscm.canJobRevert"))); Boolean canJobUpdate = Boolean.valueOf("on".equalsIgnoreCase(req.getParameter("dimensionsscm.canJobUpdate"))); Boolean canJobExpand = Boolean.valueOf("on".equalsIgnoreCase(req.getParameter("dimensionsscm.canJobExpand"))); Boolean canJobNoMetadata = Boolean.valueOf("on".equalsIgnoreCase(req.getParameter("dimensionsscm.canJobNoMetadata"))); String jobUserName = req.getParameter("dimensionsscm.jobUserName"); String jobPasswd = req.getParameter("dimensionsscm.jobPasswd"); String jobServer = req.getParameter("dimensionsscm.jobServer"); String jobDatabase = req.getParameter("dimensionsscm.jobDatabase"); String jobTimeZone = req.getParameter("dimensionsscm.jobTimeZone"); String jobWebUrl = req.getParameter("dimensionsscm.jobWebUrl"); DimensionsSCM scm = new DimensionsSCM(project,folders,null,canJobDelete, canJobForce,canJobRevert, jobUserName,jobPasswd, jobServer,jobDatabase, canJobUpdate,jobTimeZone, jobWebUrl,directory, permissions,canJobExpand, canJobNoMetadata); scm.browser = RepositoryBrowsers.createInstance(DimensionsSCMRepositoryBrowser.class,req,formData,"browser"); if (scm.dmSCM == null) scm.dmSCM = new DimensionsAPI(); return scm; } /* * Gets the timezone for the connection. * @return the timezone */ public String getTimeZone() { return this.timeZone; } /* * Gets the weburl ID for the connection. * @return the weburl */ public String getWebUrl() { return this.webUrl; } /* * Gets the user ID for the connection. * @return the user ID of the user as whom to connect */ public String getUserName() { return this.userName; } /* * Gets the base database for the connection (as "NAME@CONNECTION"). * @return the name of the base database to connect to */ public String getDatabase() { return this.database; } /* * Gets the server for the connection. * @return the name of the server to connect to */ public String getServer() { return this.server; } /* * Gets the password . * @return the password */ public String getPasswd() { return Scrambler.descramble(passwd); } /* * Gets the update . * @return the update */ public boolean isCanUpdate() { return this.canUpdate; } /* * Sets the update . */ public void setCanUpdate(boolean x) { this.canUpdate = x; } /* * Sets the user ID for the connection. */ public void setUserName(String userName) { this.userName = userName; } /* * Sets the base database for the connection (as "NAME@CONNECTION"). */ public void setDatabase(String database) { this.database = database; } /* * Sets the server for the connection. */ public void setServer(String server) { this.server = server; } /* * Sets the password . */ public void setPasswd(String password) { this.passwd = Scrambler.scramble(password); } /* * Sets the timezone for the connection. */ public void setTimeZone(String x) { this.timeZone = x; } /* * Sets the weburl ID for the connection. */ public void setWebUrl(String x) { this.webUrl = x; } public FormValidation doCheck(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException { String value = Util.fixEmpty(req.getParameter("value")); String nullText = null; if (value == null) { if (nullText == null) return FormValidation.ok(); else return FormValidation.error(nullText); } else { return FormValidation.ok(); } } public FormValidation domanadatoryFieldCheck(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException { String value = Util.fixEmpty(req.getParameter("value")); String errorTxt = "This value is manadatory."; if (value == null) { return FormValidation.error(errorTxt); } else { // Some processing return FormValidation.ok(); } } public FormValidation domanadatoryJobFieldCheck(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException { String value = Util.fixEmpty(req.getParameter("value")); String errorTxt = "This value is manadatory."; // Some processing in the future return FormValidation.ok(); } /* * Check if the specified Dimensions server is valid */ public FormValidation docheckTz(StaplerRequest req, StaplerResponse rsp, @QueryParameter("dimensionsscm.timeZone") final String timezone, @QueryParameter("dimensionsscm.jobTimeZone") final String jobtimezone) throws IOException, ServletException { try { String xtz = (jobtimezone != null) ? jobtimezone : timezone; Logger.Debug("Invoking docheckTz - " + xtz); TimeZone ctz = TimeZone.getTimeZone(xtz); String lmt = ctz.getID(); if (lmt.equalsIgnoreCase("GMT") && !(xtz.equalsIgnoreCase("GMT") || xtz.equalsIgnoreCase("Greenwich Mean Time") || xtz.equalsIgnoreCase("UTC") || xtz.equalsIgnoreCase("Coordinated Universal Time"))) return FormValidation.error("Timezone specified is not valid."); else return FormValidation.ok("Timezone test succeeded!"); } catch (Exception e) { return FormValidation.error("timezone check error:" + e.getMessage()); } } /* * Check if the specified Dimensions server is valid */ public FormValidation docheckServer(StaplerRequest req, StaplerResponse rsp, @QueryParameter("dimensionsscm.userName") final String user, @QueryParameter("dimensionsscm.passwd") final String passwd, @QueryParameter("dimensionsscm.server") final String server, @QueryParameter("dimensionsscm.database") final String database, @QueryParameter("dimensionsscm.jobUserName") final String jobuser, @QueryParameter("dimensionsscm.jobPasswd") final String jobPasswd, @QueryParameter("dimensionsscm.jobServer") final String jobServer, @QueryParameter("dimensionsscm.jobDatabase") final String jobDatabase) throws IOException, ServletException { if (connectionCheck == null) connectionCheck = new DimensionsAPI(); try { String xserver = (jobServer != null) ? jobServer : server; String xuser = (jobuser != null) ? jobuser : user; String xpasswd = (jobPasswd != null) ? jobPasswd : passwd; String xdatabase = (jobDatabase != null) ? jobDatabase : database; long key = -1; String dmS = xserver + "-" + xuser + ":" + xdatabase; Logger.Debug("Invoking serverCheck - " + dmS); key = connectionCheck.login(xuser, xpasswd, xdatabase, xserver); if (key<1) { return FormValidation.error("Connection test failed"); } else { connectionCheck.logout(key); return FormValidation.ok("Connection test succeeded!"); } } catch (Exception e) { return FormValidation.error("Server connection error:" + e.getMessage()); } } } }