hudson.scm.IntegritySCM Maven / Gradle / Ivy
package hudson.scm;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Map;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.Util;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.TaskListener;
import hudson.scm.ChangeLogParser;
import hudson.scm.PollingResult;
import hudson.scm.SCMDescriptor;
import hudson.scm.SCM;
import hudson.scm.SCMRevisionState;
import hudson.util.FormValidation;
import net.sf.json.JSONObject;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.export.Exported;
import com.mks.api.Command;
import com.mks.api.Option;
import com.mks.api.MultiValue;
import com.mks.api.response.APIException;
import com.mks.api.response.Response;
import com.mks.api.response.WorkItem;
import com.mks.api.util.Base64;
/**
* This class provides an integration between Hudson for Continuous Builds and
* MKS Integrity for Configuration Management
*/
public class IntegritySCM extends SCM implements Serializable
{
private static final long serialVersionUID = 7559894846609712683L;
public static final String NL = System.getProperty("line.separator");
public static final String FS = System.getProperty("file.separator");
public static final SimpleDateFormat SDF = new SimpleDateFormat("MMM dd, yyyy h:mm:ss a");
private final Log logger = LogFactory.getLog(getClass());
private String integrityURL;
private IntegrityRepositoryBrowser browser;
private String ipHostName;
private String hostName;
private int ipPort = 0;
private int port;
private boolean secure;
private String configPath;
private String userName;
private String password;
private boolean cleanCopy;
private boolean skipAuthorInfo = false;
private String lineTerminator = "native";
private boolean restoreTimestamp = true;
private boolean checkpointBeforeBuild = false;
private String alternateWorkspace;
private transient IntegrityCMProject siProject; /* This will get initialized when checkout is executed */
/**
* Create a constructor that takes non-transient fields, and add the annotation @DataBoundConstructor to it.
* Using the annotation helps the Stapler class to find which constructor that should be used when
* automatically copying values from a web form to a class.
*/
@DataBoundConstructor
public IntegritySCM(IntegrityRepositoryBrowser browser, String hostName, int port, boolean secure, String configPath,
String userName, String password, String ipHostName, int ipPort, boolean cleanCopy,
String lineTerminator, boolean restoreTimestamp, boolean skipAuthorInfo, boolean checkpointBeforeBuild,
String alternateWorkspace)
{
// Log the construction
logger.info("IntegritySCM constructor has been invoked!");
// Initialize the class variables
this.browser = browser;
this.ipHostName = ipHostName;
this.hostName = hostName;
this.ipPort = ipPort;
this.port = port;
this.secure = secure;
this.configPath = configPath;
this.userName = userName;
this.password = Base64.encode(password);
this.cleanCopy = cleanCopy;
this.lineTerminator = lineTerminator;
this.restoreTimestamp = restoreTimestamp;
this.skipAuthorInfo = skipAuthorInfo;
this.checkpointBeforeBuild = checkpointBeforeBuild;
this.alternateWorkspace = alternateWorkspace;
// Initialize the Integrity URL
initIntegrityURL();
// Log the parameters received
logger.info("URL: " + this.integrityURL);
logger.info("IP Host: " + this.ipHostName);
logger.info("Host: " + this.hostName);
logger.info("IP Port: " + this.ipPort);
logger.info("Port: " + this.port);
logger.info("User: " + this.userName);
logger.info("Password: " + this.password);
logger.info("Secure: " + this.secure);
logger.info("Project: " + this.configPath);
logger.info("Line Terminator: " + this.lineTerminator);
logger.info("Restore Timestamp: " + this.restoreTimestamp);
logger.info("Clean: " + this.cleanCopy);
logger.info("Skip Author Info: " + this.skipAuthorInfo);
logger.info("Checkpoint Before Build: " + this.checkpointBeforeBuild);
logger.info("Alternate Workspace Directory: " + this.alternateWorkspace);
}
@Override
@Exported
/**
* Returns the MKS Integrity Repository Browser
*/
public IntegrityRepositoryBrowser getBrowser()
{
return browser;
}
/**
* Returns the Integration Point host name of the MKS API Session
* @return
*/
public String getipHostName()
{
return ipHostName;
}
/**
* Returns the host name of the MKS Integrity Server
* @return
*/
public String getHostName()
{
return hostName;
}
/**
* Returns the Integration Point port of the MKS API Session
* @return
*/
public int getipPort()
{
return ipPort;
}
/**
* Returns the port of the MKS Integrity Server
* @return
*/
public int getPort()
{
return port;
}
/**
* Returns true/false depending on secure sockets are enabled
* @return
*/
public boolean getSecure()
{
return secure;
}
/**
* Returns the Project or Configuration Path for a MKS Integrity Source Project
* @return
*/
public String getConfigPath()
{
return configPath;
}
/**
* Returns the User connecting to the MKS Integrity Server
* @return
*/
public String getUserName()
{
return userName;
}
/**
* Returns the clear password of the user connecting to the MKS Integrity Server
* @return
*/
public String getPassword()
{
return Base64.decode(password);
}
/**
* Returns true/false depending on whether or not the workspace is required to be cleaned
* @return
*/
public boolean getCleanCopy()
{
return cleanCopy;
}
/**
* Returns the line terminator to apply when obtaining files from the MKS Integrity Server
* @return
*/
public String getLineTerminator()
{
return lineTerminator;
}
/**
* Returns true/false depending on whether or not the restore timestamp option is in effect
* @return
*/
public boolean getRestoreTimestamp()
{
return restoreTimestamp;
}
/**
* Returns true/false depending on whether or not to use 'si revisioninfo' to determine author information
* @return
*/
public boolean getSkipAuthorInfo()
{
return skipAuthorInfo;
}
/**
* Returns true/false depending on whether or not perform a checkpoint before the build
* @return
*/
public boolean getCheckpointBeforeBuild()
{
return checkpointBeforeBuild;
}
/**
* Returns the alternate workspace directory
* @return
*/
public String getAlternateWorkspace()
{
return alternateWorkspace;
}
/**
* Sets the Integration Point host name of the MKS API Session
* @return
*/
public void setipHostName(String ipHostName)
{
this.ipHostName = ipHostName;
}
/**
* Sets the host name of the MKS Integrity Server
* @return
*/
public void setHostName(String hostName)
{
this.hostName = hostName;
initIntegrityURL();
}
/**
* Sets the Integration Point port of the MKS API Session
* @return
*/
public void setipPort(int ipPort)
{
this.ipPort = ipPort;
}
/**
* Sets the port of the MKS Integrity Server
* @return
*/
public void setPort(int port)
{
this.port = port;
initIntegrityURL();
}
/**
* Toggles whether or not secure sockets are enabled
* @return
*/
public void setSecure(boolean secure)
{
this.secure = secure;
initIntegrityURL();
}
/**
* Sets the Project or Configuration Path for a MKS Integrity Source Project
* @return
*/
public void setConfigPath(String configPath)
{
this.configPath = configPath;
}
/**
* Sets the User connecting to the MKS Integrity Server
* @return
*/
public void setUserName(String userName)
{
this.userName = userName;
}
/**
* Sets the encrypted Password of the user connecting to the MKS Integrity Server
* @return
*/
public void setPassword(String password)
{
this.password = Base64.encode(password);;
}
/**
* Toggles whether or not the workspace is required to be cleaned
* @return
*/
public void setCleanCopy(boolean cleanCopy)
{
this.cleanCopy = cleanCopy;
}
/**
* Sets the line terminator to apply when obtaining files from the MKS Integrity Server
* @return
*/
public void setLineTerminator(String lineTerminator)
{
this.lineTerminator = lineTerminator;
}
/**
* Toggles whether or not to restore the timestamp for individual files
* @return
*/
public void setRestoreTimestamp(boolean restoreTimestamp)
{
this.restoreTimestamp = restoreTimestamp;
}
/**
* Toggles whether or not to use 'si revisioninfo' to determine author information
* @return
*/
public void setSkipAuthorInfo(boolean skipAuthorInfo)
{
this.skipAuthorInfo = skipAuthorInfo;
}
/**
* Toggles whether or not a checkpoint should be performed before the build
* @param checkpointBeforeBuild
*/
public void setCheckpointBeforeBuild(boolean checkpointBeforeBuild)
{
this.checkpointBeforeBuild = checkpointBeforeBuild;
}
/**
* Sets an alternate workspace for the checkout directory
* @param alternateWorkspace
*/
public void setAlternateWorkspace(String alternateWorkspace)
{
this.alternateWorkspace = alternateWorkspace;
}
/**
* Provides a mechanism to update the Integrity URL, based on updates
* to the hostName/port/secure variables
*/
private void initIntegrityURL()
{
// Initialize the Integrity URL
if( secure )
{
integrityURL = "https://" + hostName + ":" + String.valueOf(port);
}
else
{
integrityURL = "http://" + hostName + ":" + String.valueOf(port);
}
}
/**
* Creates an authenticated API Session against the MKS Integrity Server
* @return An authenticated API Session
*/
public APISession createAPISession()
{
// Attempt to open a connection to the MKS Integrity Server
try
{
logger.info("Creating MKS API Session...");
return new APISession(ipHostName, ipPort, hostName, port, userName, Base64.decode(password), secure);
}
catch(APIException aex)
{
logger.error("API Exception caught...");
ExceptionHandler eh = new ExceptionHandler(aex);
logger.error(eh.getMessage());
logger.info(eh.getCommand() + " returned exit code " + eh.getExitCode());
aex.printStackTrace();
return null;
}
}
/**
* Returns the MKS Integrity Configuration Management Project
* @return
*/
public IntegrityCMProject getIntegrityProject()
{
return siProject;
}
/**
* Adds MKS Integrity CM Project info to the build variables
*/
@Override
public void buildEnvVars(AbstractBuild, ?> build, Map env)
{
super.buildEnvVars(build, env);
logger.info("buildEnvVars() invoked...!");
env.put("MKSSI_PROJECT", configPath);
env.put("MKSSI_HOST", hostName);
env.put("MKSSI_PORT", String.valueOf(port));
env.put("MKSSI_USER", userName);
}
/**
* Overridden calcRevisionsFromBuild function
* Returns the current project configuration which can be used to difference any future configurations
* @see hudson.scm.SCM#calcRevisionsFromBuild(hudson.model.AbstractBuild, hudson.Launcher, hudson.model.TaskListener)
*/
@Override
public SCMRevisionState calcRevisionsFromBuild(AbstractBuild, ?> build, Launcher launcher, TaskListener listener) throws IOException, InterruptedException
{
// Just logging the call for now
logger.info("calcRevisionsFromBuild() invoked...!");
Object obj = getIntegrityCMProjectState(build);
// Now that we've loaded the object, lets make sure it is an IntegrityCMProject!
if( obj instanceof IntegrityCMProject && null != obj )
{
// Cast object to an IntegrityCMProject
IntegrityCMProject cmProject = (IntegrityCMProject) obj;
// Returns the current Project Configuration for the requested build
return new IntegrityRevisionState(cmProject);
}
else
{
// Not sure what object we've loaded, but its no IntegrityCMProject!
logger.info("Cannot construct project state for build " + build.getNumber() + "!");
// Returns the current Project Configuration for the requested build
return new IntegrityRevisionState(null);
}
}
/**
* Primes the MKS Integrity Project metadata information
* @param api MKS API Session
* @return response MKS API Response
* @throws APIException
*/
private Response initializeCMProject(APISession api) throws APIException
{
// Get the project information for this project
Command siProjectInfoCmd = new Command(Command.SI, "projectinfo");
siProjectInfoCmd.addOption(new Option("project", configPath));
logger.info("Preparing to execute si projectinfo for " + configPath);
Response infoRes = api.runCommand(siProjectInfoCmd);
logger.info(infoRes.getCommandString() + " returned " + infoRes.getExitCode());
// Initialize our siProject class variable
siProject = new IntegrityCMProject(infoRes.getWorkItems().next());
// Set the project options
siProject.setLineTerminator(lineTerminator);
siProject.setRestoreTimestamp(restoreTimestamp);
siProject.setSkipAuthorInfo(skipAuthorInfo);
return infoRes;
}
/**
* Primes the MKS Integrity Project Member metadata information
* @param api MKS API Session
* @return response MKS API Response
* @throws APIException
*/
private Response initializeCMProjectMembers(APISession api) throws APIException
{
// Lets parse this project
Command siViewProjectCmd = new Command(Command.SI, "viewproject");
siViewProjectCmd.addOption(new Option("recurse"));
siViewProjectCmd.addOption(new Option("project", siProject.getConfigurationPath()));
MultiValue mvFields = new MultiValue(",");
mvFields.add("name");
mvFields.add("context");
mvFields.add("memberrev");
mvFields.add("membertimestamp");
mvFields.add("memberdescription");
siViewProjectCmd.addOption(new Option("fields", mvFields));
logger.info("Preparing to execute si viewproject for " + siProject.getConfigurationPath());
Response viewRes = api.runCommand(siViewProjectCmd);
logger.info(viewRes.getCommandString() + " returned " + viewRes.getExitCode());
siProject.parseProject(viewRes.getWorkItems(), api);
return viewRes;
}
/**
* Toggles whether or not a workspace is required for polling
* Since, we're using a Server Integration Point in the MKS API,
* we do not require a workspace.
*/
@Override
public boolean requiresWorkspaceForPolling()
{
return false;
}
private Object getIntegrityCMProjectState(AbstractBuild,?> build) throws IOException
{
// Make sure this build is not null, before processing it!
if( null != build )
{
// Lets make absolutely certain we've found a useful build,
File viewProjectFile = getViewProjectResponseFile(build);
if( ! viewProjectFile.exists() )
{
// There is no project state for this build!
logger.info("Project state not found for build " + build.getNumber() + "!");
return null;
}
else
{
// We've found a build that contains the project state... load it up!
logger.info("Attempting to load up project state for build " + build.getNumber() + "...");
FileInputStream fis = new FileInputStream(viewProjectFile);
ObjectInputStream ois = new ObjectInputStream(fis);
Object obj = null;
try
{
// Read the serialized Project object
obj = ois.readObject();
logger.info("Project state re-constructed successfully for build " + build.getNumber() + "!");
}
catch( ClassNotFoundException cne )
{
// Not sure what we've found, but its no good...
logger.info("Caught Exception: " + cne.getMessage());
logger.info("Cannot construct project state for build" + build.getNumber() + "!");
}
finally
{
ois.close();
fis.close();
}
return obj;
}
}
return null;
}
/**
* Overridden checkout function
* This is the real invocation of this plugin.
* Currently, we will do a project info and determine the true nature of the project
* Subsequent to that we will run a view project command and cache the information
* on each member, so that we can execute project checkout commands. This obviously
* eliminates the need for a sandbox and can wily nilly delete the workspace directory as needed
* @see hudson.scm.SCM#checkout(hudson.model.AbstractBuild, hudson.Launcher, hudson.FilePath, hudson.model.BuildListener, java.io.File)
*/
@Override
public boolean checkout(AbstractBuild, ?> build, Launcher launcher, FilePath workspace,
BuildListener listener, File changeLogFile) throws IOException, InterruptedException
{
// Log the invocation...
logger.info("Start execution of checkout() routine...!");
// Lets start with creating an authenticated MKS API Session for various parts of this operation...
APISession api = createAPISession();
// Ensure we've successfully created an API Session
if( null == api )
{
listener.getLogger().println("Failed to establish an API connection to the MKS Integrity Server!");
return false;
}
// Lets also open the change log file for writing...
PrintWriter writer = new PrintWriter(new FileWriter(changeLogFile));
try
{
// Next, load up the information for this MKS Integrity Project's configuration
listener.getLogger().println("Preparing to execute si projectinfo for " + configPath);
initializeCMProject(api);
// Check to see we need to checkpoint before the build
if( checkpointBeforeBuild )
{
// Make sure we don't have a build project configuration
if( ! siProject.isBuild() )
{
// Execute a pre-build checkpoint...
listener.getLogger().println("Preparing to execute pre-build si checkpoint for " + siProject.getConfigurationPath());
Response res = siProject.checkpoint(api, "");
logger.info(res.getCommandString() + " returned " + res.getExitCode());
WorkItem wi = res.getWorkItem(siProject.getConfigurationPath());
String chkpt = wi.getResult().getField("resultant").getItem().getId();
listener.getLogger().println("Successfully executed pre-build checkpoint for project " +
siProject.getConfigurationPath() + ", new revision is " + chkpt);
// Update the siProject to use the new checkpoint as the basis for this build
Command siProjectInfoCmd = new Command(Command.SI, "projectinfo");
siProjectInfoCmd.addOption(new Option("project", siProject.getProjectName()));
siProjectInfoCmd.addOption(new Option("projectRevision", chkpt));
Response infoRes = api.runCommand(siProjectInfoCmd);
siProject.initializeProject(infoRes.getWorkItems().next());
}
else
{
listener.getLogger().println("Cannot perform a pre-build checkpoint for build project configuration!");
}
}
listener.getLogger().println("Preparing to execute si viewproject for " + siProject.getConfigurationPath());
initializeCMProjectMembers(api);
// Figure out what our previous build was...
AbstractBuild,?> previousBuild = build.getPreviousBuild();
// Check to see if we've had a previous build...
if( null == previousBuild )
{
// Nothing worthwhile to compare against
logger.info("Cannot find a previous build!");
}
// Now, we need to find the project state from the previous build.
for( AbstractBuild,?> b = build.getPreviousBuild(); null != b; b = b.getPreviousBuild() )
{
// For each previous build, lets make sure we can find a project state
if( getViewProjectResponseFile(b).exists())
{
logger.info("Found previous project state in build " + b.getNumber());
previousBuild = b;
break;
}
}
// Load up the project state for this previous build...
Object obj = getIntegrityCMProjectState(previousBuild);
// Now that we've loaded the object, lets make sure it is an IntegrityCMProject!
if( obj instanceof IntegrityCMProject && null != obj )
{
// Cast object to an IntegrityCMProject
IntegrityCMProject oldProject = (IntegrityCMProject) obj;
// Compare this project with the old
siProject.compareBaseline(oldProject);
}
else
{
// Not sure what object we've loaded, but its no IntegrityCMProject!
logger.info("Cannot construct project state for any of the pevious builds!");
}
// After all that insane interrogation, we have the current Project state that is
// correctly initialized and either compared against its baseline or is a fresh baseline itself
// Now, lets figure out how to populate the workspace...
IntegrityCheckoutTask coTask = null;
if( null == obj )
{
// If we we were not able to establish the previous project state,
// then always do full checkout. cleanCopy = true
coTask = new IntegrityCheckoutTask(this, siProject, true, listener);
}
else
{
// Otherwise, update the workspace in accordance with the user's cleanCopy option
coTask = new IntegrityCheckoutTask(this, siProject, cleanCopy, listener);
}
// Execute the IntegrityCheckoutTask.invoke() method to do the actual synchronization...
if( workspace.act(coTask) )
{
// Now that the workspace is updated, lets save the current project state for future comparisons
listener.getLogger().println("Saving current MKS Integrity Project configuration...");
printViewProjectResponse(build, listener, siProject);
// Write out the change log file, which will be used by the parser to report the updates
listener.getLogger().println("Writing build change log...");
writer.println(siProject.getChangeLog(String.valueOf(build.getNumber()), api));
listener.getLogger().println("Change log successfully generated: " + changeLogFile.getAbsolutePath());
}
else
{
// Checkout failed! Returning false...
return false;
}
}
catch(APIException aex)
{
logger.error("API Exception caught...");
listener.getLogger().println("An API Exception was caught!");
ExceptionHandler eh = new ExceptionHandler(aex);
logger.error(eh.getMessage());
listener.getLogger().println(eh.getMessage());
logger.info(eh.getCommand() + " returned exit code " + eh.getExitCode());
listener.getLogger().println(eh.getCommand() + " returned exit code " + eh.getExitCode());
aex.printStackTrace();
return false;
}
finally
{
writer.close();
api.Terminate();
}
//If we got here, everything is good on the checkout...
return true;
}
/**
* Called from checkout to save the current state of the project for this build
* @param build Hudson AbstractBuild
* @param listener Hudson Build Listener
* @param response MKS API Response
* @throws IOException
*/
private void printViewProjectResponse(AbstractBuild,?> build, BuildListener listener, IntegrityCMProject pj) throws IOException
{
// For every build we will basically save our view project output, so it can be compared build over build
File viewProjectFile = new File(build.getRootDir(), "viewproject.dat");
FileOutputStream fos = new FileOutputStream(viewProjectFile);
ObjectOutputStream pjOut = new ObjectOutputStream(fos);
try
{
pjOut.writeObject(pj);
listener.getLogger().println("API Response for si viewproject successfully saved to file!");
}
finally
{
pjOut.close();
fos.close();
}
listener.getLogger().println("Successfully saved current MKS Integrity Project configuration to " + viewProjectFile.getAbsolutePath());
}
/**
* Returns the MKS API Response xml file for the specified build
* @param build Hudson AbstractBuild Object
* @return
*/
public static File getViewProjectResponseFile(AbstractBuild,?> build)
{
return new File(build.getRootDir(),"viewproject.dat");
}
/**
* Overridden compareRemoteRevisionWith function
* Loads up the previous project configuration and compares
* that against the current to determine if the project has changed
* @see hudson.scm.SCM#compareRemoteRevisionWith(hudson.model.AbstractProject, hudson.Launcher, hudson.FilePath, hudson.model.TaskListener, hudson.scm.SCMRevisionState)
*/
@Override
protected PollingResult compareRemoteRevisionWith(AbstractProject, ?> project, Launcher launcher, FilePath workspace,
final TaskListener listener, SCMRevisionState _baseline) throws IOException, InterruptedException
{
// Log the call for now...
logger.info("compareRemoteRevisionWith() invoked...!");
IntegrityRevisionState baseline;
// Lets get the baseline from our last build
if( _baseline instanceof IntegrityRevisionState )
{
baseline = (IntegrityRevisionState)_baseline;
// Get the baseline that contains the last build
AbstractBuild,?> lastBuild = project.getLastBuild();
if( null == lastBuild )
{
// We've got no previous builds, build now!
logger.info("No prior successful builds found! Advice to build now!");
return PollingResult.BUILD_NOW;
}
else
{
// Lets trying to get the baseline associated with the last build
baseline = (IntegrityRevisionState)calcRevisionsFromBuild(lastBuild, launcher, listener);
if( null != baseline && null != baseline.getSIProject() )
{
// Obtain the details on the old project configuration
IntegrityCMProject oldProject = baseline.getSIProject();
// Next, load up the information for the current MKS Integrity Project
// Lets start with creating an authenticated MKS API Session for various parts of this operation...
APISession api = createAPISession();
if( null != api )
{
try
{
listener.getLogger().println("Preparing to execute si projectinfo for " + configPath);
initializeCMProject(api);
listener.getLogger().println("Preparing to execute si viewproject for " + configPath);
initializeCMProjectMembers(api);
// Compare this project with the old project
siProject.compareBaseline(oldProject);
// Finally decide whether or not we need to build again
if( siProject.hasProjectChanged() )
{
listener.getLogger().println("Project contains changes a total of " + siProject.getChangeCount() + " changes!");
return PollingResult.SIGNIFICANT;
}
else
{
listener.getLogger().println("No new changes detected in project!");
return PollingResult.NO_CHANGES;
}
}
catch(APIException aex)
{
logger.error("API Exception caught...");
listener.getLogger().println("An API Exception was caught!");
ExceptionHandler eh = new ExceptionHandler(aex);
logger.error(eh.getMessage());
listener.getLogger().println(eh.getMessage());
logger.info(eh.getCommand() + " returned exit code " + eh.getExitCode());
listener.getLogger().println(eh.getCommand() + " returned exit code " + eh.getExitCode());
aex.printStackTrace();
return PollingResult.NO_CHANGES;
}
finally
{
api.Terminate();
}
}
else
{
listener.getLogger().println("Failed to establish an API connection to the MKS Integrity Server!");
return PollingResult.NO_CHANGES;
}
}
else
{
// Can't construct a previous project state, lets build now!
logger.info("No prior MKS Integrity Project state can be found! Advice to build now!");
return PollingResult.BUILD_NOW;
}
}
}
else
{
// This must be an error, no changes to report
logger.error("This method was called with the wrong SCMRevisionState class!");
return PollingResult.NO_CHANGES;
}
}
/**
* Overridden createChangeLogParser function
* Creates a custom Integrity Change Log Parser, which compares two view project outputs
* @see hudson.scm.SCM#createChangeLogParser()
*/
@Override
public ChangeLogParser createChangeLogParser()
{
// Log the call
logger.info("createChangeLogParser() invoked...!");
return new IntegrityChangeLogParser(integrityURL);
}
/**
* Returns the SCMDescriptor> for the SCM object.
* The SCMDescriptor is used to create new instances of the SCM.
*/
@Override
public SCMDescriptor getDescriptor()
{
// Log the call
logger.info("IntegritySCM.getDescriptor() invoked...!");
return DescriptorImpl.INTEGRITY_DESCRIPTOR;
}
/**
* The relationship of Descriptor and SCM (the describable) is akin to class and object.
* This means the descriptor is used to create instances of the describable.
* Usually the Descriptor is an internal class in the SCM class named DescriptorImpl.
* The Descriptor should also contain the global configuration options as fields,
* just like the SCM class contains the configurations options for a job.
*/
public static class DescriptorImpl extends SCMDescriptor
{
private static Log desLogger = LogFactory.getLog(DescriptorImpl.class);
@Extension
public static final DescriptorImpl INTEGRITY_DESCRIPTOR = new DescriptorImpl();
private String globalOptions;
protected DescriptorImpl()
{
super(IntegritySCM.class, IntegrityRepositoryBrowser.class);
load();
// Log the construction...
desLogger.info("IntegritySCM DescriptorImpl() constructed!");
}
@Override
public SCM newInstance(StaplerRequest req, JSONObject formData) throws FormException
{
IntegritySCM scm = (IntegritySCM) super.newInstance(req, formData);
scm.browser = RepositoryBrowsers.createInstance(IntegrityRepositoryBrowser.class, req, formData, "browser");
return scm;
}
/**
* Returns the name of the SCM, this is the name that will show up next to
* CVS, Subversion, etc. when configuring a job.
*/
@Override
public String getDisplayName()
{
return "MKS Integrity - CM";
}
/**
* This method is invoked when the global configuration page is submitted.
* In the method the data in the web form should be copied to the Descriptor's fields.
* To persist the fields to the global configuration XML file, the save() method must be called.
* Data is defined in the global.jelly page.
*/
@Override
public boolean configure(StaplerRequest req, JSONObject formData) throws FormException
{
// Log the request to configure
desLogger.info("Request to configure IntegritySCM invoked...");
// This is where we can get any global settings...
// Can't thing of any integrity globals to define, so we'll just save() and return true!
globalOptions = Util.fixEmpty(req.getParameter("mks.globalOptions").trim());
save();
return true;
}
/**
* Returns the MKS Integrity global options configured for this plugin
*/
public String getGlobalOptions()
{
return globalOptions;
}
/**
* Sets the MKS Integrity global options configured for this plugin
*/
public void setGlobalOptions(String globalOptions)
{
this.globalOptions = globalOptions;
}
/**
* The field mks.globalOptions will be validated through the checkUrl.
* When the user has entered some information and moves the focus away from field,
* Hudson will call DescriptorImpl.doGlobalOptionsCheck to validate that data entered.
*/
public FormValidation doGlobalOptionsCheck(@QueryParameter String value)
{
// This is where we can validate the entry of the Global Options field
// If there is an error, we return:
// return FormValidation.error("This is an error!");
// If we want to throw a warning:
// return FormValidation.warning("This is a warning!");
// Nothing to validate, so we'll return all good!
return FormValidation.ok();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy