hudson.plugins.dimensionsscm.DimensionsSCM.java Source code

Java tutorial

Introduction

Here is the source code for hudson.plugins.dimensionsscm.DimensionsSCM.java

Source

/* ===========================================================================
 *  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());
            }
        }
    }
}