/* * The MIT License * * Copyright 2010 Sony Ericsson Mobile Communications. All rights reservered. * Copyright 2012 Sony Mobile Communications AB. All rights reservered. * * 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.rebuild; import hudson.Extension; import hudson.model.Action; import javax.servlet.ServletException; import java.io.IOException; import java.util.ArrayList; import java.util.List; import hudson.matrix.MatrixRun; import hudson.model.BooleanParameterValue; import hudson.model.Cause; import hudson.model.CauseAction; import hudson.model.Hudson; import hudson.model.Item; import hudson.model.Job; import hudson.model.ParameterValue; import hudson.model.ParametersAction; import hudson.model.ParametersDefinitionProperty; import hudson.model.Queue; import hudson.model.Run; import hudson.model.SimpleParameterDefinition; import hudson.model.ParameterDefinition; import hudson.model.PasswordParameterValue; import hudson.model.RunParameterValue; import hudson.model.StringParameterValue; import net.sf.json.JSONArray; import net.sf.json.JSONObject; import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import com.sonyericsson.rebuild.RebuildParameterPage; import com.sonyericsson.rebuild.RebuildParameterProvider; /** * Rebuild RootAction implementation class. This class will basically reschedule * the build with existing parameters. * * @author Shemeer S; */ public class RebuildAction implements Action { private static final String SVN_TAG_PARAM_CLASS = "hudson.scm.listtagsparameter.ListSubversionTagsParameterValue"; /* * All the below transient variables are declared only for backward * compatibility of the rebuild plugin. */ private transient String rebuildurl = "rebuild"; private transient String parameters = "rebuildParam"; private transient String p = "parameter"; private transient Run<?, ?> build; private transient ParametersDefinitionProperty pdp; private static final String PARAMETERIZED_URL = "parameterized"; /** * Rebuild Descriptor. */ @Extension public static final RebuildDescriptor DESCRIPTOR = new RebuildDescriptor(); /** * RebuildAction constructor. */ public RebuildAction() { } /** * Getter method for pdp. * * @return pdp. */ public ParametersDefinitionProperty getPdp() { return pdp; } /** * Getter method for build. * * @return build. */ public Run<?, ?> getBuild() { return build; } /** * Getter method for p. * * @return p. */ public String getP() { return p; } /** * Getter method for parameters. * * @return parameters. */ public String getParameters() { return parameters; } /** * Getter method for rebuildurl. * * @return rebuildurl. */ public String getRebuildurl() { return rebuildurl; } /** * True if the password fields should be pre-filled. * * @return True if the password fields should be pre-filled. */ public boolean isRememberPasswordEnabled() { return DESCRIPTOR.getRebuildConfiguration().isRememberPasswordEnabled(); } /** * Method will return current project. * * @return currentProject. */ public Job getProject() { if (build != null) { return build.getParent(); } Job currentProject = null; StaplerRequest request = Stapler.getCurrentRequest(); if (request != null) { currentProject = request.findAncestorObject(Job.class); } return currentProject; } @Override public String getIconFileName() { if (isRebuildAvailable()) { return "clock.gif"; } else { return null; } } @Override public String getDisplayName() { if (isRebuildAvailable()) { return "Rebuild"; } else { return null; } } @Override public String getUrlName() { if (isRebuildAvailable()) { return "rebuild"; } else { return null; } } /** * Handles the rebuild request and redirects to parameterized * and non parameterized build when needed. * * @param request StaplerRequest the request. * @param response StaplerResponse the response handler. * @throws IOException in case of Stapler issues * @throws ServletException if something unfortunate happens. * @throws InterruptedException if something unfortunate happens. */ public void doIndex(StaplerRequest request, StaplerResponse response) throws IOException, ServletException, InterruptedException { Run currentBuild = request.findAncestorObject(Run.class); if (currentBuild != null) { ParametersAction paramAction = currentBuild.getAction(ParametersAction.class); if (paramAction != null) { RebuildSettings settings = (RebuildSettings)getProject().getProperty(RebuildSettings.class); if (settings != null && settings.getAutoRebuild()) { parameterizedRebuild(currentBuild, response); } else { response.sendRedirect(PARAMETERIZED_URL); } } else { nonParameterizedRebuild(currentBuild, response); } } } /** * Handles the rebuild request with parameter. * * @param currentBuild the build. * @param response StaplerResponse the response handler. * @throws IOException in case of Stapler issues */ public void parameterizedRebuild(Run currentBuild, StaplerResponse response) throws IOException { Job project = getProject(); if (project == null) { return; } project.checkPermission(Item.BUILD); if (isRebuildAvailable()) { List<Action> actions = copyBuildCausesAndAddUserCause(currentBuild); ParametersAction action = currentBuild.getAction(ParametersAction.class); actions.add(action); Hudson.getInstance().getQueue().schedule((Queue.Task) build.getParent(), 0, actions); response.sendRedirect("../../"); } } /** * Call this method while rebuilding * non parameterized build. . * * @param currentBuild current build. * @param response current response object. * @throws ServletException if something unfortunate happens. * @throws IOException if something unfortunate happens. * @throws InterruptedException if something unfortunate happens. */ public void nonParameterizedRebuild(Run currentBuild, StaplerResponse response) throws ServletException, IOException, InterruptedException { getProject().checkPermission(Item.BUILD); List<Action> actions = constructRebuildCause(build, null); Hudson.getInstance().getQueue().schedule((Queue.Task) currentBuild.getParent(), 0, actions); response.sendRedirect("../../"); } /** * Saves the form to the configuration and disk. * * @param req StaplerRequest * @param rsp StaplerResponse * @throws ServletException if something unfortunate happens. * @throws IOException if something unfortunate happens. * @throws InterruptedException if something unfortunate happens. */ public void doConfigSubmit(StaplerRequest req, StaplerResponse rsp) throws ServletException, IOException, InterruptedException { Job project = getProject(); if (project == null) { return; } project.checkPermission(Item.BUILD); if (isRebuildAvailable()) { if (!req.getMethod().equals("POST")) { // show the parameter entry form. req.getView(this, "index.jelly").forward(req, rsp); return; } build = req.findAncestorObject(Run.class); ParametersDefinitionProperty paramDefProp = build.getParent().getProperty( ParametersDefinitionProperty.class); List<ParameterValue> values = new ArrayList<ParameterValue>(); ParametersAction paramAction = build.getAction(ParametersAction.class); JSONObject formData = req.getSubmittedForm(); if (!formData.isEmpty()) { JSONArray a = JSONArray.fromObject(formData.get("parameter")); for (Object o : a) { JSONObject jo = (JSONObject)o; String name = jo.getString("name"); ParameterValue parameterValue = getParameterValue(paramDefProp, name, paramAction, req, jo); if (parameterValue != null) { values.add(parameterValue); } } } List<Action> actions = constructRebuildCause(build, new ParametersAction(values)); Hudson.getInstance().getQueue().schedule((Queue.Task) build.getParent(), 0, actions); rsp.sendRedirect("../../"); } } /** * Extracts the build causes and adds or replaces the {@link hudson.model.Cause.UserIdCause}. The result is a * list of all build causes from the original build (might be an empty list), plus a * {@link hudson.model.Cause.UserIdCause} for the user who started the rebuild. * * @param fromBuild the build to copy the causes from. * @return list with all original causes and a {@link hudson.model.Cause.UserIdCause}. */ private List<Action> copyBuildCausesAndAddUserCause(Run<?, ?> fromBuild) { List currentBuildCauses = fromBuild.getCauses(); List<Action> actions = new ArrayList<Action>(currentBuildCauses.size()); boolean hasUserCause = false; for (Object buildCause : currentBuildCauses) { if (buildCause instanceof Cause.UserIdCause) { hasUserCause = true; actions.add(new CauseAction(new Cause.UserIdCause())); } else { actions.add(new CauseAction((Cause)buildCause)); } } if (!hasUserCause) { actions.add(new CauseAction(new Cause.UserIdCause())); } return actions; } /** * Method for checking whether current build is sub job(MatrixRun) of Matrix * build. * * @return boolean */ public boolean isMatrixRun() { StaplerRequest request = Stapler.getCurrentRequest(); if (request != null) { build = request.findAncestorObject(Run.class); if (build != null && build instanceof MatrixRun) { return true; } } return false; } /** * Method for checking,whether the rebuild functionality would be available * for build. * * @return boolean */ public boolean isRebuildAvailable() { Job project = getProject(); return project != null && project.hasPermission(Item.BUILD) && project.isBuildable() && project instanceof Queue.Task && !isMatrixRun() && !isRebuildDisbaled(); } private boolean isRebuildDisbaled() { RebuildSettings settings = (RebuildSettings)getProject().getProperty(RebuildSettings.class); if (settings != null && settings.getRebuildDisabled()) { return true; } return false; } /** * Method for getting the ParameterValue instance from ParameterDefinition * or ParamterAction. * * @param paramDefProp ParametersDefinitionProperty * @param parameterName Name of the Parameter. * @param paramAction ParametersAction * @param req StaplerRequest * @param jo JSONObject * @return ParameterValue instance of subclass of ParameterValue */ public ParameterValue getParameterValue(ParametersDefinitionProperty paramDefProp, String parameterName, ParametersAction paramAction, StaplerRequest req, JSONObject jo) { ParameterDefinition paramDef; // this is normal case when user try to rebuild a parameterized job. if (paramDefProp != null) { paramDef = paramDefProp.getParameterDefinition(parameterName); if (paramDef != null) { // The copy artifact plugin throws an exception when using createValue(req, jo) // If the parameter comes from the copy artifact plugin, then use the single argument createValue if (jo.toString().contains("BuildSelector") || jo.toString().contains("WorkspaceSelector")) { SimpleParameterDefinition parameterDefinition = (SimpleParameterDefinition)paramDefProp.getParameterDefinition(parameterName); return parameterDefinition.createValue(jo.getString("value")); } return paramDef.createValue(req, jo); } } /* * when user try to rebuild a build that was invoked by * parameterized trigger plugin in that case ParameterDefinition * is null for that parametername that is paased by parameterize * trigger plugin,so for handling that scenario, we need to * create an instance of that specific ParameterValue with * passed parameter value by form. * * In contrast to all other parameterActions, ListSubversionTagsParameterValue uses "tag" instead of "value" */ if (jo.containsKey("value")) { return cloneParameter(paramAction.getParameter(parameterName), jo.getString("value")); } else { return cloneParameter(paramAction.getParameter(parameterName), jo.getString("tag")); } } /** * Method for replacing the old parametervalue with new parameter value * * @param oldValue ParameterValue * @param newValue The value that is submitted by user using form. * @return ParameterValue */ private ParameterValue cloneParameter(ParameterValue oldValue, String newValue) { if (oldValue instanceof StringParameterValue) { return new StringParameterValue(oldValue.getName(), newValue, oldValue.getDescription()); } else if (oldValue instanceof BooleanParameterValue) { return new BooleanParameterValue(oldValue.getName(), Boolean.valueOf(newValue), oldValue.getDescription()); } else if (oldValue instanceof RunParameterValue) { return new RunParameterValue(oldValue.getName(), newValue, oldValue.getDescription()); } else if (oldValue instanceof PasswordParameterValue) { return new PasswordParameterValue(oldValue.getName(), newValue, oldValue.getDescription()); } else if (oldValue.getClass().getName().equals(SVN_TAG_PARAM_CLASS)) { /** * getClass().getName() to avoid dependency on svn plugin. */ return new StringParameterValue(oldValue.getName(), newValue, oldValue.getDescription()); } throw new IllegalArgumentException("Unrecognized parameter type: " + oldValue.getClass()); } /** * Method for constructing Rebuild cause. * * @param up AbsstractBuild * @param paramAction ParametersAction. * @return actions List<Action> */ private List<Action> constructRebuildCause(Run up, ParametersAction paramAction) { List<Action> actions = copyBuildCausesAndAddUserCause(up); actions.add(new CauseAction(new RebuildCause(up))); if (paramAction != null) { actions.add(paramAction); } return actions; } /** * @param value the parameter value to show to rebuild. * @return page for the parameter value, or null if no suitable option found. */ public RebuildParameterPage getRebuildParameterPage(ParameterValue value) { for (RebuildParameterProvider provider: RebuildParameterProvider.all()) { RebuildParameterPage page = provider.getRebuildPage(value); if (page != null) { return page; } } // Check if we have a branched Jelly in the plugin. if (getClass().getResource(String.format("/%s/%s.jelly", getClass().getCanonicalName().replace('.', '/'), value.getClass().getSimpleName())) != null) { // No provider available, use an existing view provided by rebuild plugin. return new RebuildParameterPage( getClass(), String.format("%s.jelly", value.getClass().getSimpleName()) ); } // Else we return that we haven't found anything. // So Jelly fallback could occur. return null; } }