hudson.plugins.release.ReleaseWrapper.java Source code

Java tutorial

Introduction

Here is the source code for hudson.plugins.release.ReleaseWrapper.java

Source

/*
 * The MIT License
 *
 * Copyright (c) 2009-2011, Peter Hayes, Manufacture Francaise des Pneumatiques Michelin,
 * Romain Seguy
 *
 * 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 hudson.plugins.release;

import hudson.EnvVars;
import hudson.Extension;
import hudson.Launcher;
import hudson.Util;
import hudson.maven.MavenModuleSet;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.BuildBadgeAction;
import hudson.model.BuildListener;
import hudson.model.Cause;
import hudson.model.Descriptor;
import hudson.model.FreeStyleProject;
import hudson.model.Hudson;
import hudson.model.Item;
import hudson.model.Job;
import hudson.model.ParameterDefinition;
import hudson.model.ParameterValue;
import hudson.model.ParametersAction;
import hudson.model.ParametersDefinitionProperty;
import hudson.model.PermalinkProjectAction;
import hudson.model.PermalinkProjectAction.Permalink;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.StringParameterValue;
import hudson.plugins.release.promotion.ReleasePromotionCondition;
import hudson.tasks.BuildStep;
import hudson.tasks.BuildWrapper;
import hudson.tasks.BuildWrapperDescriptor;
import hudson.tasks.Builder;
import hudson.util.VariableResolver;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.servlet.ServletException;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.commons.lang.ArrayUtils;

import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;

/**
 * Wraps a build with pre and post build steps.  These steps can take
 * any action as part of the special release build.
 *
 * @author Peter Hayes
 * @since 1.0
 */
public class ReleaseWrapper extends BuildWrapper {
    private static final String DEFAULT_RELEASE_VERSION_TEMPLATE = "Release #$RELEASE_VERSION";

    private String releaseVersionTemplate;
    private boolean doNotKeepLog;
    private boolean overrideBuildParameters;
    private List<ParameterDefinition> parameterDefinitions = new ArrayList<ParameterDefinition>();
    private List<Builder> preBuildSteps = new ArrayList<Builder>();
    private List<Builder> postBuildSteps = new ArrayList<Builder>();
    private List<Builder> postSuccessfulBuildSteps = new ArrayList<Builder>();
    private List<Builder> postFailedBuildSteps = new ArrayList<Builder>();

    /**
     * List of {@link Permalink}s for release builds.
     */
    public static final List<Permalink> RELEASE = new CopyOnWriteArrayList<Permalink>();

    static {
        RELEASE.add(new Permalink() {
            public String getDisplayName() {
                return Messages.ReleaseWrapper_LastReleaseBuild();
            }

            public String getId() {
                return "lastReleaseBuild";
            }

            public Run<?, ?> resolve(Job<?, ?> job) {
                for (Run<?, ?> build : job.getBuilds()) {
                    if (build.getAction(ReleaseBuildBadgeAction.class) != null) {
                        return build;
                    }
                }
                return null;
            }
        });
        RELEASE.add(new Permalink() {
            public String getDisplayName() {
                return Messages.ReleaseWrapper_LastSuccessfulReleaseBuild();
            }

            public String getId() {
                return "lastSuccessfulReleaseBuild";
            }

            public Run<?, ?> resolve(Job<?, ?> job) {
                for (Run<?, ?> build : job.getBuilds()) {
                    if (build.getResult() == Result.SUCCESS
                            && build.getAction(ReleaseBuildBadgeAction.class) != null) {
                        return build;
                    }
                }
                return null;
            }
        });
    }

    /**
     * @stapler-constructor
     */
    public ReleaseWrapper() {
    }

    public String getReleaseVersionTemplate() {
        return releaseVersionTemplate;
    }

    public void setReleaseVersionTemplate(String releaseVersionTemplate) {
        this.releaseVersionTemplate = releaseVersionTemplate;
    }

    public boolean isDoNotKeepLog() {
        return doNotKeepLog;
    }

    public void setDoNotKeepLog(boolean doNotKeepLog) {
        this.doNotKeepLog = doNotKeepLog;
    }

    public boolean isOverrideBuildParameters() {
        return overrideBuildParameters;
    }

    public void setOverrideBuildParameters(boolean overrideBuildParameters) {
        this.overrideBuildParameters = overrideBuildParameters;
    }

    public List<ParameterDefinition> getParameterDefinitions() {
        return parameterDefinitions;
    }

    public void setParameterDefinitions(List<ParameterDefinition> parameterDefinitions) {
        this.parameterDefinitions = parameterDefinitions;
    }

    /**
     * @return Returns the preBuildSteps.
     */
    public List<Builder> getPreBuildSteps() {
        return preBuildSteps;
    }

    /**
     * @param preBuildSteps The preBuildSteps to set.
     */
    public void setPreBuildSteps(List<Builder> preBuildSteps) {
        this.preBuildSteps = preBuildSteps;
    }

    /**
     * @return Returns the postBuildSteps.
     */
    public List<Builder> getPostBuildSteps() {
        return postBuildSteps;
    }

    /**
     * @param postBuildSteps The postBuildSteps to set.
     */
    public void setPostBuildSteps(List<Builder> postSuccessBuildSteps) {
        this.postBuildSteps = postSuccessBuildSteps;
    }

    public List<Builder> getPostSuccessfulBuildSteps() {
        return postSuccessfulBuildSteps;
    }

    public void setPostSuccessfulBuildSteps(List<Builder> postSuccessfulBuildSteps) {
        this.postSuccessfulBuildSteps = postSuccessfulBuildSteps;
    }

    public List<Builder> getPostFailedBuildSteps() {
        return postFailedBuildSteps;
    }

    public void setPostFailedBuildSteps(List<Builder> postFailedBuildSteps) {
        this.postFailedBuildSteps = postFailedBuildSteps;
    }

    @Override
    public Collection<? extends Action> getProjectActions(AbstractProject job) {
        return Collections.singletonList(new ReleaseAction(job));
    }

    @Override
    public Environment setUp(AbstractBuild build, final Launcher launcher, BuildListener listener)
            throws IOException, InterruptedException {

        final ReleaseBuildBadgeAction releaseBuildBadge = build.getAction(ReleaseBuildBadgeAction.class);

        if (releaseBuildBadge == null) {
            return new Environment() {
            };
        }

        // Set the release version now by resolving build parameters against build and release version template
        ParametersAction parametersAction = build.getAction(ParametersAction.class);
        if (parametersAction != null) {
            // set up variable resolver from parameters action
            VariableResolver<String> resolver = createVariableResolver(parametersAction, build);

            // resolve template against resolver
            String releaseVersion = Util.replaceMacro(
                    releaseVersionTemplate != null && !"".equals(releaseVersionTemplate) ? releaseVersionTemplate
                            : DEFAULT_RELEASE_VERSION_TEMPLATE,
                    resolver);

            // replace environment variables with actual values
            EnvVars env = build.getEnvironment(listener);
            releaseVersion = env.expand(releaseVersion);

            // if release version is same as original, then blank it out
            if (DEFAULT_RELEASE_VERSION_TEMPLATE.equals(releaseVersion)) {
                releaseVersion = null;
            }

            releaseBuildBadge.releaseVersion = releaseVersion;
        }

        if (!executeBuildSteps(preBuildSteps, build, launcher, listener)) {
            throw new IOException(Messages.ReleaseWrapper_CouldNotExecutePreBuildSteps());
        }

        // return environment
        return new Environment() {

            @Override
            public boolean tearDown(AbstractBuild build, BuildListener listener)
                    throws IOException, InterruptedException {
                boolean shouldContinue = false;

                try {
                    Result result = build.getResult();
                    if (result == null || result.isBetterOrEqualTo(Result.UNSTABLE)) {
                        // save build
                        if (!doNotKeepLog) {
                            build.keepLog();
                        }

                        // set description if we can derive version
                        if (releaseBuildBadge.getReleaseVersion() != null) {

                            // set build description to indicate release
                            build.setDescription(releaseBuildBadge.getReleaseVersion());
                        }

                        shouldContinue = executeBuildSteps(postSuccessfulBuildSteps, build, launcher, listener);
                    } else {
                        shouldContinue = executeBuildSteps(postFailedBuildSteps, build, launcher, listener);
                    }
                } finally {
                    if (shouldContinue) {
                        shouldContinue = executeBuildSteps(postBuildSteps, build, launcher, listener);
                    }
                }

                return shouldContinue;
            }
        };
    }

    /*
     * Copied method from ParametersAction to reverse order of resolvers
     * per HUDSON-5094
     */
    private VariableResolver<String> createVariableResolver(ParametersAction parametersAction,
            AbstractBuild<?, ?> build) {
        VariableResolver[] resolvers = new VariableResolver[parametersAction.getParameters().size() + 1];
        int i = 0;
        for (ParameterValue p : parametersAction.getParameters())
            resolvers[i++] = p.createVariableResolver(build);

        resolvers[i] = build.getBuildVariableResolver();

        ArrayUtils.reverse(resolvers);

        return new VariableResolver.Union<String>(resolvers);
    }

    private boolean executeBuildSteps(List<Builder> buildSteps, AbstractBuild build, Launcher launcher,
            BuildListener listener) throws InterruptedException, IOException {
        boolean shouldContinue = true;

        // execute prebuild steps, stop processing if indicated
        if (buildSteps != null) {
            for (BuildStep buildStep : buildSteps) {

                if (!shouldContinue) {
                    break;
                }

                shouldContinue = buildStep.prebuild(build, listener);
            }

            // execute build step, stop processing if indicated
            for (BuildStep buildStep : buildSteps) {

                if (!shouldContinue) {
                    break;
                }

                shouldContinue = buildStep.perform(build, launcher, listener);
            }
        }

        return shouldContinue;
    }

    public static boolean hasReleasePermission(AbstractProject job) {
        return job.hasPermission(Item.BUILD);
    }

    public static void checkReleasePermission(AbstractProject job) {
        job.checkPermission(Item.BUILD);
    }

    @Extension
    public static final class DescriptorImpl extends BuildWrapperDescriptor {

        static {
            // check if promoted plugins is installed and if so, register
            // promotion condition
            if (Hudson.getInstance().getPlugin("promoted-builds") != null) {
                ReleasePromotionCondition.registerExtension();
            }
        }

        @Override
        public String getDisplayName() {
            return Messages.ReleaseWrapper_ConfigureReleaseBuild();
        }

        @Override
        public BuildWrapper newInstance(StaplerRequest req, JSONObject formData) throws FormException {
            ReleaseWrapper instance = new ReleaseWrapper();
            instance.releaseVersionTemplate = formData.getString("releaseVersionTemplate");
            instance.doNotKeepLog = formData.getBoolean("doNotKeepLog");
            instance.overrideBuildParameters = formData.getBoolean("overrideBuildParameters");
            instance.parameterDefinitions = Descriptor.newInstancesFromHeteroList(req, formData, "parameters",
                    ParameterDefinition.all());
            instance.preBuildSteps = Descriptor.newInstancesFromHeteroList(req, formData, "preBuildSteps",
                    Builder.all());
            instance.postBuildSteps = Descriptor.newInstancesFromHeteroList(req, formData, "postBuildSteps",
                    Builder.all());
            instance.postSuccessfulBuildSteps = Descriptor.newInstancesFromHeteroList(req, formData,
                    "postSuccessfulBuildSteps", Builder.all());
            instance.postFailedBuildSteps = Descriptor.newInstancesFromHeteroList(req, formData,
                    "postFailedBuildSteps", Builder.all());
            return instance;
        }

        @Override
        public boolean isApplicable(AbstractProject<?, ?> item) {
            return FreeStyleProject.class.isInstance(item) || MavenModuleSet.class.isInstance(item);
        }
    }

    public class ReleaseAction implements Action, PermalinkProjectAction {
        private AbstractProject project;
        private String releaseVersion;
        private String developmentVersion;

        public ReleaseAction(AbstractProject project) {
            this.project = project;
        }

        public List<ParameterDefinition> getParameterDefinitions() {
            return parameterDefinitions;
        }

        public List<ParameterDefinition> getBuildParameterDefinitions() {
            ParametersDefinitionProperty paramsDefProp = (ParametersDefinitionProperty) project
                    .getProperty(ParametersDefinitionProperty.class);
            if (paramsDefProp != null) {
                return paramsDefProp.getParameterDefinitions();
            }
            return null;
        }

        /**
         * {@inheritDoc}
         */
        public String getDisplayName() {
            return "Release";
        }

        /**
         * {@inheritDoc}
         */
        public String getIconFileName() {
            return ReleaseWrapper.hasReleasePermission(project) ? "package.gif" : null;
        }

        /**
         * {@inheritDoc}
         */
        public String getUrlName() {
            return "release";
        }

        /**
         * @return Returns the project.
         */
        public AbstractProject getProject() {
            return project;
        }

        /**
         * @return A map of previous build numbers and release version identifiers
         */
        public List<AbstractBuild> getPreviousReleaseBuilds() {
            List<AbstractBuild> previousReleaseBuilds = new ArrayList<AbstractBuild>();

            for (Iterator iter = project.getBuilds().iterator(); iter.hasNext();) {
                AbstractBuild build = (AbstractBuild) iter.next();

                ReleaseBuildBadgeAction badge = build.getAction(ReleaseBuildBadgeAction.class);

                if (badge != null) {
                    previousReleaseBuilds.add(build);
                }
            }

            return previousReleaseBuilds;
        }

        public String getReleaseVersionForBuild(AbstractBuild build) {
            ReleaseBuildBadgeAction badge = build.getAction(ReleaseBuildBadgeAction.class);

            return badge.getReleaseVersion();
        }

        public List<ParameterValue> getParametersForBuild(AbstractBuild build) {
            ParametersAction parameters = build.getAction(ParametersAction.class);

            if (parameters != null) {
                return parameters.getParameters();
            }

            return Collections.emptyList();
        }

        public String getReleaseVersion() {
            return releaseVersion;
        }

        public void setReleaseVersion(String releaseVersion) {
            this.releaseVersion = releaseVersion;
        }

        public String getDevelopmentVersion() {
            return developmentVersion;
        }

        public void setDevelopmentVersion(String developmentVersion) {
            this.developmentVersion = developmentVersion;
        }

        /**
         * Gets the {@link ParameterDefinition} of the given name, including
         * the ones from the build parameters, if any.
         */
        public ParameterDefinition getParameterDefinition(String name) {
            ParametersDefinitionProperty buildParamsDefProp = (ParametersDefinitionProperty) project
                    .getProperty(ParametersDefinitionProperty.class);
            List<ParameterDefinition> buildParameterDefinitions = null;
            if (buildParamsDefProp != null) {
                buildParameterDefinitions = buildParamsDefProp.getParameterDefinitions();
            }

            if (!overrideBuildParameters && parameterDefinitions == null || overrideBuildParameters
                    && parameterDefinitions == null && buildParameterDefinitions == null) {
                return null;
            }

            for (ParameterDefinition pd : parameterDefinitions) {
                if (pd.getName().equals(name)) {
                    return pd;
                }
            }

            if (overrideBuildParameters) {
                for (ParameterDefinition pd : buildParameterDefinitions) {
                    if (pd.getName().equals(name)) {
                        return pd;
                    }
                }
            }

            return null;
        }

        /*
         * TODO Would be nice if this method was accessible from AbstractProject
         */
        private List<ParameterValue> getDefaultParametersValues() {
            ParametersDefinitionProperty paramDefProp = (ParametersDefinitionProperty) project
                    .getProperty(ParametersDefinitionProperty.class);
            ArrayList<ParameterValue> defValues = new ArrayList<ParameterValue>();

            /*
             * This check is made ONLY if someone will call this method even if isParametrized() is false.
             */
            if (paramDefProp == null)
                return defValues;

            /* Scan for all parameter with an associated default values */
            for (ParameterDefinition paramDefinition : paramDefProp.getParameterDefinitions()) {
                ParameterValue defaultValue = paramDefinition.getDefaultParameterValue();

                if (defaultValue != null)
                    defValues.add(defaultValue);
            }

            return defValues;
        }

        public boolean isOverrideBuildParameters() {
            return overrideBuildParameters;
        }

        public void doSubmit(StaplerRequest req, StaplerResponse resp) throws IOException, ServletException {
            // verify permission
            ReleaseWrapper.checkReleasePermission(project);

            // bind development / release version
            req.bindParameters(this);

            // create parameter list
            List<ParameterValue> paramValues;
            if (isOverrideBuildParameters()) {
                // if overrideBuildParameters is set, then build params are submitted
                // within the list of release params -- no need to gather default values
                paramValues = new ArrayList<ParameterValue>();
            } else {
                paramValues = getDefaultParametersValues();
            }

            if (getParameterDefinitions() != null && !getParameterDefinitions().isEmpty() || overrideBuildParameters
                    && getBuildParameterDefinitions() != null && !getBuildParameterDefinitions().isEmpty()) {
                JSONObject formData = req.getSubmittedForm();

                JSONArray a = JSONArray.fromObject(formData.get("parameter"));

                for (Object o : a) {
                    JSONObject jo = (JSONObject) o;
                    String name = jo.getString("name");

                    ParameterDefinition d = getParameterDefinition(name);
                    if (d == null)
                        throw new IllegalArgumentException("No such parameter definition: " + name);

                    ParameterValue value = d.createValue(req, jo);

                    paramValues.add(d.createValue(req, jo));
                }
            } else {
                // add version if specified
                if (releaseVersion != null && !"".equals(releaseVersion)) {
                    paramValues.add(new StringParameterValue("RELEASE_VERSION", releaseVersion));
                }

                if (developmentVersion != null && !"".equals(developmentVersion)) {
                    paramValues.add(new StringParameterValue("DEVELOPMENT_VERSION", developmentVersion));
                }
            }

            // schedule release build
            if (!project.scheduleBuild(0, new Cause.UserCause(), new ReleaseBuildBadgeAction(),
                    new ParametersAction(paramValues))) {
                // TODO redirect to error page?
            }

            // redirect to status page
            resp.sendRedirect(project.getAbsoluteUrl());
        }

        public List<Permalink> getPermalinks() {
            return RELEASE;
        }

    }

    public static class ReleaseBuildBadgeAction implements BuildBadgeAction {
        private String releaseVersion;

        public ReleaseBuildBadgeAction() {
        }

        public String getReleaseVersion() {
            return releaseVersion;
        }

        public String getIconFileName() {
            return null;
        }

        public String getDisplayName() {
            return null;
        }

        public String getUrlName() {
            return null;
        }
    }
}