/*
* The MIT License
*
* Copyright 2012 Sony Mobile Communications AB. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.sonyericsson.jenkins.plugins.bfa.sod;
import com.sonyericsson.jenkins.plugins.bfa.Messages;
import com.sonyericsson.jenkins.plugins.bfa.PluginImpl;
import com.sonyericsson.jenkins.plugins.bfa.model.FailureCauseBuildAction;
import com.sonyericsson.jenkins.plugins.bfa.model.FailureCauseMatrixBuildAction;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.Functions;
import hudson.matrix.MatrixBuild;
import hudson.matrix.MatrixRun;
import hudson.model.Action;
import hudson.model.Item;
import hudson.model.Job;
import hudson.model.Project;
import hudson.model.Result;
import hudson.model.Run;
import hudson.util.Iterators;
import jenkins.model.Jenkins;
import org.acegisecurity.AccessDeniedException;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.Ancestor;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import javax.annotation.Nonnull;
import javax.servlet.ServletException;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import static com.sonyericsson.jenkins.plugins.bfa.sod.ScanOnDemandBaseAction.ScanMode.BFA_SOD_BUILD_TYPE;
import static org.apache.commons.lang.StringUtils.isBlank;
/**
* Action class for scanning non scanned build.
*
* @author Shemeer Sulaiman <shemeer.x.sulaiman@sonymobile.com>
*/
public class ScanOnDemandBaseAction implements Action {
/** The project. */
private Job project;
/**
* SODBaseAction constructor.
*
* @param project current project.
*/
public ScanOnDemandBaseAction(final Job project) {
this.project = project;
}
@Override
public String getIconFileName() {
if (hasPermission()) {
return PluginImpl.getDefaultIcon();
} else {
return null;
}
}
@Override
public String getDisplayName() {
if (hasPermission()) {
return Messages.FailureScan_DisplayName();
} else {
return null;
}
}
@Override
public String getUrlName() {
if (hasPermission()) {
return "scan-on-demand";
} else {
return null;
}
}
/**
* The full url to this action instance.
*
* @return the full url
* @see Job#getUrl()
*/
@Nonnull
private String getFullUrl() {
return Functions.joinPath(project.getUrl(), getUrlName());
}
/**
* Checks if the current user has {@link Item#CONFIGURE} or {@link Project#BUILD} permission.
*
* @return true if so.
*/
public boolean hasPermission() {
return project.hasPermission(Item.CONFIGURE) || project.hasPermission(Project.BUILD);
}
/**
* Checks if the current user has {@link Item#CONFIGURE} or {@link Project#BUILD} permission.
*
* @see #hasPermission()
* @see hudson.security.ACL#checkPermission(hudson.security.Permission)
*/
public void checkPermission() {
if (!hasPermission()) {
throw new AccessDeniedException(
Messages.SodAccessDeniedException(Jenkins.getAuthentication().getName(),
Item.CONFIGURE.name, Project.BUILD.name));
}
}
/**
* Returns the project.
*
* @return project
*/
public final Job<?, ?> getProject() {
return project;
}
/**
* Method for remove matrix run actions.
*
* @param build the MatrixBuild.
*/
public void removeRunActions(MatrixBuild build) {
List<MatrixRun> runs = build.getRuns();
for (MatrixRun run : runs) {
if (run.getNumber() == build.getNumber()) {
FailureCauseBuildAction fcba = run.getAction(FailureCauseBuildAction.class);
if (fcba != null) {
run.getActions().remove(fcba);
}
FailureCauseMatrixBuildAction fcmba = run.getAction(FailureCauseMatrixBuildAction.class);
if (fcmba != null) {
run.getActions().remove(fcmba);
}
}
}
}
/**
* Shortcut method to
* {@link #getDefault()}.{@link ScanMode#doPerformScan(ScanOnDemandBaseAction, StaplerRequest, StaplerResponse)}
* If the user clicks on scan on the default scanmode page.
*
* @param request the request
* @param response the response
* @throws ServletException if so
* @throws InterruptedException if so
* @throws IOException if so
*/
public void doPerformScan(StaplerRequest request, StaplerResponse response)
throws ServletException, InterruptedException, IOException {
getDefault().doPerformScan(this, request, response);
}
/**
* Finds the user's default {@link ScanMode}.
* If no selection is found in the session, or not in the request scope then {@link NonScanned} is returned.
*
* @return the default mode.
*/
public ScanMode getDefault() {
StaplerRequest request = Stapler.getCurrentRequest();
if (request != null) {
String selected = (String)request.getSession(true).getAttribute(BFA_SOD_BUILD_TYPE);
if (!isBlank(selected)) {
ScanMode mode = getMode(selected);
if (mode != null) {
return mode;
}
}
}
return getMode(NonScanned.URL);
}
/**
* Stapler function to enable 'scan-on-demand/all' etc.
*
* @param url the scan mode
*
* @return most likely a {@link ScanMode}
*/
@SuppressWarnings("unused") //Stapler function
public Object getDynamic(String url) {
if (isBlank(url)) {
return getDefault();
} else {
return getMode(url);
}
}
/**
* Finds the mode with the provided url as returned by {@link ScanMode#getUrlName()}.
*
* @param url the url to match
*
* @return the scan mode or null if no matching scan mode is found.
*/
public ScanMode getMode(String url) {
for (ScanMode mode : ExtensionList.lookup(ScanMode.class)) {
if (mode.getUrlName().equals(url)) {
return mode;
}
}
return null;
}
/**
* Represents the different scan modes that can be used to re-scan the builds of a Job.
*
* @see NonScanned
* @see AllBuilds
*/
@Restricted(NoExternalUse.class)
public abstract static class ScanMode implements ExtensionPoint {
/**
* Session key used to store the default scan mode
*/
static final String BFA_SOD_BUILD_TYPE = "bfa-sod-buildType";
/**
* If there is any run in the job matching this scan mode's criteria.
* Default implementation is {@link #getRuns(Job)}{@code .hasNext()}
* @param job the job to check
*
* @return true if so.
*/
@SuppressWarnings("unused") //Called by the view
public boolean hasAnyRun(Job job) {
return getRuns(job).hasNext();
}
/**
* The short relative url name of this scan mode.
* Also used as a short identifier of the scan mode.
*
* @return the url name
*/
@Nonnull
public abstract String getUrlName();
/**
* The full url from the root of Jenkins.
*
* @return the full url
* @see ScanOnDemandBaseAction#getFullUrl()
*/
@Nonnull
public String getFullUrl() {
return Functions.joinPath(getParent().getFullUrl(), getUrlName());
}
/**
* Human readable name to display.
*
* @return the name
*/
@Nonnull
public abstract String getDisplayName();
/**
* Provides an iterator of the {@link Run}s of the provided job that matches this scan mode.
*
* @param job the job to filter builds from
* @return an iterator of the matching builds
*/
@Nonnull
abstract Iterator<Run> getRuns(Job job);
/**
* Sets this scan mode as the default for this user on future page visits.
*/
@SuppressWarnings("unused") //Called by the view
public void setAsDefault() {
StaplerRequest request = Stapler.getCurrentRequest();
if (request == null) {
throw new IllegalStateException("setAsDefault() can only be called in request scope");
}
request.getSession(true).setAttribute(BFA_SOD_BUILD_TYPE, getUrlName());
}
/**
* Finds the {@link ScanOnDemandBaseAction} in the ancestor path.
*
* Throws {@link IllegalStateException} if we are not in the request scope and
* {@link Stapler#getCurrentRequest()} returns null.
*
* @return the ancestor action
*
*/
@Nonnull
public ScanOnDemandBaseAction getParent() {
StaplerRequest request = Stapler.getCurrentRequest();
if (request == null) {
throw new IllegalStateException("getParent can only be called from request scope.");
}
Ancestor ancestor = request.findAncestor(ScanOnDemandBaseAction.class);
if (ancestor == null) {
throw new IllegalStateException("Not within the path of ScanOnDemandBaseAction");
}
return (ScanOnDemandBaseAction)ancestor.getObject();
}
/**
* Submit method for running build scan.
*
* @param action the action we have as an ancestor
* @param request StaplerRequest
* @param response StaplerResponse
* @throws ServletException if something unfortunate happens.
* @throws IOException if something unfortunate happens.
* @throws InterruptedException if something unfortunate happens.
*/
@SuppressWarnings("unused") //Called by the view
public void doPerformScan(@AncestorInPath ScanOnDemandBaseAction action,
StaplerRequest request, StaplerResponse response)
throws ServletException, IOException, InterruptedException {
action.checkPermission();
Iterator<Run> runIterator = getRuns(action.getProject());
while (runIterator.hasNext()) {
Run run = runIterator.next();
FailureCauseBuildAction fcba = run.getAction(FailureCauseBuildAction.class);
if (fcba != null) {
run.getActions().remove(fcba); //TODO Replace instead
}
FailureCauseMatrixBuildAction fcmba = run.getAction(FailureCauseMatrixBuildAction.class);
if (run instanceof MatrixBuild
&& fcmba != null) {
run.getActions().remove(fcmba); //TODO Replace instead
action.removeRunActions((MatrixBuild)run);
}
ScanOnDemandTask task = new ScanOnDemandTask(run);
ScanOnDemandQueue.queue(task);
}
response.sendRedirect2(Functions.joinPath("/", request.getContextPath(), getParent().getProject().getUrl()));
}
/**
* Provides the lookup list of all registered {@link ScanMode}s.
*
* @return the list of available modes.
*/
public static List<ScanMode> all() {
return ExtensionList.lookup(ScanMode.class);
}
}
/**
* ScanMode that scans only previously non scanned builds.
*/
@Extension
@Restricted(NoExternalUse.class)
public static class NonScanned extends ScanMode {
/**
* The {@link #getUrlName()} of this ScanMode.
*/
static final String URL = "nonscanned";
@Nonnull
@Override
public String getUrlName() {
return URL;
}
@Nonnull
@Override
public String getDisplayName() {
return Messages.ScanOnDemandBaseAction_NonScanned_DisplayName();
}
@Nonnull
@Override
Iterator<Run> getRuns(Job job) {
return new Iterators.FilterIterator<Run>(job.getBuilds().iterator()) {
@Override
protected boolean filter(Run run) {
final Result result = run.getResult();
return result != null
&& PluginImpl.needToAnalyze(result)
&& run.getActions(FailureCauseBuildAction.class).isEmpty()
&& run.getActions(FailureCauseMatrixBuildAction.class).isEmpty();
}
};
}
}
/**
* ScanMode that re-scans all builds regardless if they have been scanned before or not.
*/
@Extension
@Restricted(NoExternalUse.class)
public static class AllBuilds extends ScanMode {
@Nonnull
@Override
public String getUrlName() {
return "all";
}
@Nonnull
@Override
public String getDisplayName() {
return Messages.ScanOnDemandBaseAction_AllBuilds_DisplayName();
}
@Nonnull
@Override
Iterator<Run> getRuns(Job job) {
return new Iterators.FilterIterator<Run>(job.getBuilds().iterator()) {
@Override
protected boolean filter(Run run) {
final Result result = run.getResult();
return result != null
&& PluginImpl.needToAnalyze(result);
}
};
}
}
}