jenkins.plugins.tanaguru.TanaguruRunnerBuilder.java Source code

Java tutorial

Introduction

Here is the source code for jenkins.plugins.tanaguru.TanaguruRunnerBuilder.java

Source

/*
 *  Tanaguru Runner - Trigger Automated webpage assessmentfrom Jenkins 
 *  Copyright (C) 2008-2015  Tanaguru.org
 * 
 *  This file is part of Tanaguru.
 * 
 *  Tanaguru is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 * 
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 *  Contact us by mail: open-s AT open-s DOT com
 */
package jenkins.plugins.tanaguru;

import com.sebuilder.interpreter.IO;
import com.sebuilder.interpreter.factory.ScriptFactory;
import hudson.Launcher;
import hudson.Extension;
import hudson.FilePath;
import hudson.util.FormValidation;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.Result;
import hudson.tasks.Builder;
import hudson.tasks.BuildStepDescriptor;
import hudson.util.ListBoxModel;
import hudson.util.ListBoxModel.Option;
import java.io.File;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.QueryParameter;

import javax.servlet.ServletException;
import java.io.IOException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;

/**
 * <p>
 * When the user configures the project and enables this builder,
 * {@link DescriptorImpl#newInstance(StaplerRequest)} is invoked and a new
 * {@link TanaguruRunnerBuilder} is created. The created instance is persisted
 * to the project configuration XML by using XStream, so this allows you to use
 * instance fields (like {@link #scenarioName}) to remember the configuration.
 *
 * <p>
 * When a build is performed, the
 * {@link #perform(AbstractBuild, Launcher, BuildListener)} method will be
 * invoked.
 *
 * @author Jrme Kowalczyk
 */
public class TanaguruRunnerBuilder extends Builder {

    private static final String PLOT_PLUGIN_YVALUE = "YVALUE=";
    private static final String TG_SCRIPT_NAME = "bin/tanaguru.sh";
    private static final String SQL_PROCEDURE_NAME = "/sql/create_contract_with_scenario_and_create_act.sql";
    private static final String SQL_PROCEDURE_SCRIPT_NAME = "create-contract-with-scenario-and-create-act.sql";
    private static final String INSERT_ACT_NAME = "/sql/insert_act.sh";
    private static final String INSERT_ACT_SCRIPT_NAME = "insert-act.sh";
    private static final String TMP_FOLDER_NAME = "tmp/";
    private static final String DEFAULT_XMX_VALUE = "256";

    private final String scenario;
    private final String scenarioName;
    private final String refAndLevel;
    private final String minMarkThresholdForStable;
    private final String maxFailedOccurencesForStable;
    private final String xmxValue;

    // Fields in config.jelly must match the parameter names in the "DataBoundConstructor"
    @DataBoundConstructor
    public TanaguruRunnerBuilder(String scenarioName, String scenario, String refAndLevel,
            String minMarkThresholdForStable, String maxFailedOccurencesForStable, String xmxValue) {
        this.scenario = scenario;
        this.refAndLevel = refAndLevel;
        this.scenarioName = scenarioName;
        this.minMarkThresholdForStable = minMarkThresholdForStable;
        this.maxFailedOccurencesForStable = maxFailedOccurencesForStable;
        this.xmxValue = xmxValue;
    }

    /**
     * We'll use this from the <tt>config.jelly</tt>.
     *
     * @return
     */
    public String getScenario() {
        return scenario;
    }

    /**
     * We'll use this from the <tt>config.jelly</tt>.
     *
     * @return
     */
    public String getRefAndLevel() {
        return refAndLevel;
    }

    /**
     * We'll use this from the <tt>config.jelly</tt>.
     *
     * @return
     */
    public String getScenarioName() {
        return scenarioName;
    }

    /**
     * We'll use this from the <tt>config.jelly</tt>.
     *
     * @return
     */
    public String getMaxFailedOccurencesForStable() {
        return String.valueOf(maxFailedOccurencesForStable);
    }

    /**
     * We'll use this from the <tt>config.jelly</tt>.
     *
     * @return
     */
    public String getMinMarkThresholdForStable() {
        return String.valueOf(minMarkThresholdForStable);
    }

    /**
     * We'll use this from the <tt>config.jelly</tt>.
     *
     * @return
     */
    public String getXmxValue() {
        return String.valueOf(xmxValue);
    }

    @Override
    public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener)
            throws IOException, InterruptedException {

        // This is where you 'build' the project.
        File contextDir = new File(getDescriptor().getTanaguruCliPath());
        if (!contextDir.exists()) {
            listener.getLogger().println("Le chemin vers le contexte d'excution est incorrect");
            return false;
        }
        File scriptFile = new File(getDescriptor().getTanaguruCliPath() + "/" + TG_SCRIPT_NAME);
        if (!scriptFile.canExecute()) {
            listener.getLogger().println("Le script n'est pas excutable");
            return false;
        }

        String spaceEscapedScenarioName = scenarioName.replace(' ', '_');

        TanaguruRunner tanaguruRunner = new TanaguruRunner(TG_SCRIPT_NAME, spaceEscapedScenarioName, scenario,
                build.getNumber(), refAndLevel.split(";")[0], refAndLevel.split(";")[1], contextDir,
                getDescriptor().getFirefoxPath(), getDescriptor().getDisplayPort(),
                StringUtils.isBlank(xmxValue) ? DEFAULT_XMX_VALUE : xmxValue, listener,
                getDescriptor().getIsDebug());

        tanaguruRunner.callTanaguruService();

        writeResultToWorkspace(tanaguruRunner, build.getWorkspace());

        linkToTanaguruWebapp(tanaguruRunner, spaceEscapedScenarioName, scenario, contextDir,
                build.getProject().getDisplayName());

        setBuildStatus(build, tanaguruRunner);

        return true;
    }

    /**
     * 
     * @param tanaguruRunner
     * @param scenario
     * @param workspace
     */
    private void linkToTanaguruWebapp(TanaguruRunner tanaguruRunner, String scenarioName, String scenario,
            File contextDir, String projectName) throws IOException, InterruptedException {

        File insertProcedureFile = TanaguruRunnerBuilder.createTempFile(contextDir, SQL_PROCEDURE_SCRIPT_NAME,
                IOUtils.toString(getClass().getResourceAsStream(SQL_PROCEDURE_NAME)));

        String script = IOUtils.toString(getClass().getResourceAsStream(INSERT_ACT_NAME))
                .replace("$host", TanaguruInstallation.get().getDatabaseHost())
                .replace("$user", TanaguruInstallation.get().getDatabaseLogin())
                .replace("$port", TanaguruInstallation.get().getDatabasePort())
                .replace("$passwd", TanaguruInstallation.get().getDatabasePassword())
                .replace("$db", TanaguruInstallation.get().getDatabaseName())
                .replace("$procedureFileName", TMP_FOLDER_NAME + SQL_PROCEDURE_SCRIPT_NAME);

        File insertActFile = TanaguruRunnerBuilder.createTempFile(contextDir, INSERT_ACT_SCRIPT_NAME, script);

        ProcessBuilder pb = new ProcessBuilder(TMP_FOLDER_NAME + INSERT_ACT_SCRIPT_NAME,
                TanaguruInstallation.get().getTanaguruLogin(), projectName.replaceAll("'", "'\"'\"'"),
                scenarioName.replaceAll("'", "'\"'\"'"),
                TanaguruRunnerBuilder.forceVersion1ToScenario(scenario.replaceAll("'", "'\"'\"'")),
                tanaguruRunner.auditId);

        pb.directory(contextDir);
        pb.redirectErrorStream(true);

        Process p = pb.start();
        p.waitFor();

        FileUtils.deleteQuietly(insertActFile);
        FileUtils.deleteQuietly(insertProcedureFile);
    }

    /**
     * Export atomic results from Tanaguru Analysis to enable graphic creation
     * through plot plugin.
     * 
     * @param tanaguruRunner
     * @param workspace 
     */
    private void writeResultToWorkspace(TanaguruRunner tanaguruRunner, FilePath workspace)
            throws IOException, InterruptedException {

        File workspacedir = new File(workspace.toURI());

        writeValueToFile(tanaguruRunner.mark, "mark", workspacedir);
        writeValueToFile(tanaguruRunner.nbPassed, "passed", workspacedir);
        writeValueToFile(tanaguruRunner.nbFailed, "failed", workspacedir);
        writeValueToFile(tanaguruRunner.nbFailedOccurences, "failedOccurences", workspacedir);
        writeValueToFile(tanaguruRunner.nbNmi, "nmi", workspacedir);
        writeValueToFile(tanaguruRunner.nbNa, "na", workspacedir);
        writeValueToFile(tanaguruRunner.nbNt, "nt", workspacedir);
    }

    /**
     * 
     * @param value
     * @param valueType
     * @param workspacedir
     * @return
     * @throws IOException
     * @throws InterruptedException 
     */
    private void writeValueToFile(String value, String valueType, File workspacedir)
            throws IOException, InterruptedException {
        File ntFile = new File(workspacedir.getAbsolutePath() + "/tanaguru-" + valueType + ".properties");
        FileUtils.write(ntFile, PLOT_PLUGIN_YVALUE + value);
    }

    private void setBuildStatus(AbstractBuild build, TanaguruRunner tanaguruRunner) {
        if ((StringUtils.isBlank(minMarkThresholdForStable) || Integer.valueOf(minMarkThresholdForStable) < 0)
                && (StringUtils.isBlank(maxFailedOccurencesForStable)
                        || Integer.valueOf(maxFailedOccurencesForStable) < 0)) {
            build.setResult(Result.SUCCESS);
            return;
        }
        if (Integer.valueOf(minMarkThresholdForStable) > 0
                && Float.valueOf(tanaguruRunner.mark) < Integer.valueOf(minMarkThresholdForStable)) {
            build.setResult(Result.UNSTABLE);
            return;
        }
        if (Integer.valueOf(maxFailedOccurencesForStable) > 0 && Integer
                .valueOf(tanaguruRunner.nbFailedOccurences) > Integer.valueOf(maxFailedOccurencesForStable)) {
            build.setResult(Result.UNSTABLE);
            return;
        }
        build.setResult(Result.SUCCESS);
    }

    /**
     * Create a temporary file within a temporary folder, created in the
     * contextDir if not exists (first time)
     * @param contextDir
     * @param fileName
     * @param fileContent
     * @return
     * @throws IOException 
     */
    public static File createTempFile(File contextDir, String fileName, String fileContent) throws IOException {
        File contextDirTemp = new File(contextDir.getAbsolutePath() + "/tmp");
        if (!contextDirTemp.exists()) {
            if (contextDirTemp.mkdir()) {
                contextDirTemp.setExecutable(true);
                contextDirTemp.setWritable(true);
            }
        }
        File tempFile = new File(contextDirTemp.getAbsolutePath() + "/" + fileName);
        FileUtils.writeStringToFile(tempFile, fileContent);
        if (tempFile.exists()) {
            tempFile.setExecutable(true);
            tempFile.setWritable(true);
        }
        return tempFile;
    }

    /**
     * Change on-the-fly version of se-builder scenario from '2' to '1' to
     * ensure its compatibility 
     * @param scenario
     * @return the updated scenario
     */
    public static String forceVersion1ToScenario(String scenario) {
        return scenario.replace("\"formatVersion\": 2", "\"formatVersion\":1").replace("\"formatVersion\":2",
                "\"formatVersion\":1");
    }

    @Override
    public DescriptorImpl getDescriptor() {
        return (DescriptorImpl) super.getDescriptor();
    }

    @Override
    public Action getProjectAction(AbstractProject<?, ?> project) {
        return new ProjectTanaguruAction(project);
    }

    /**
     * Descriptor for {@link TanaguruRunnerBuilder}. Used as a singleton. The
     * class is marked as public so that it can be accessed from views.
     */
    @Extension // This indicates to Jenkins that this is an implementation of an extension point.
    public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {

        private String pathToCli = "";
        private String displayPort = "";
        private String firefoxPath = "";
        private boolean isDebug = false;
        private TanaguruInstallation tanaguruInstallation;

        /**
         * In order to load the persisted global configuration, you have to call
         * load() in the constructor.
         */
        public DescriptorImpl() {
            load();
        }

        /**
         * Performs on-the-fly validation of the form field 'scenario'.
         *
         * @param value This parameter receives the value that the user has
         * typed.
         * @return Indicates the outcome of the validation. This is sent to the
         * browser.
         * <p>
         * Note that returning {@link FormValidation#error(String)} does not
         * prevent the form from being saved. It just means that a message will
         * be displayed to the user.
         * @throws javax.servlet.ServletException
         */
        public FormValidation doCheckScenario(@QueryParameter String value) throws ServletException {
            if (value.length() == 0) {
                return FormValidation.error("Please fill-in a not empty scenario");
            }

            try {
                IO.read(TanaguruRunnerBuilder.forceVersion1ToScenario(value));
            } catch (IOException ex) {
                return FormValidation.error("Please fill-in a valid scenario");
            } catch (ScriptFactory.SuiteException ex) {
                return FormValidation.error("Please fill-in a valid scenario");
            } catch (org.json.JSONException ex) {
                return FormValidation.error("Please fill-in a valid scenario");
            }
            return FormValidation.ok();
        }

        /**
         * Performs on-the-fly validation of the form field 'scenarioName'.
         *
         * @param value This parameter receives the value that the user has
         * typed.
         * @return Indicates the outcome of the validation. This is sent to the
         * browser.
         * <p>
         * Note that returning {@link FormValidation#error(String)} does not
         * prevent the form from being saved. It just means that a message will
         * be displayed to the user.
         * @throws java.io.IOException
         * @throws javax.servlet.ServletException
         */
        public FormValidation doCheckScenarioName(@QueryParameter String value)
                throws IOException, ServletException {
            if (value.length() == 0) {
                return FormValidation.error("Please fill-in a name to the scenario");
            }
            return FormValidation.ok();
        }

        /**
         * Performs on-the-fly validation of the form field 'xmx Value'.
         *
         * @param value This parameter receives the value that the user has
         * typed.
         * @return Indicates the outcome of the validation. This is sent to the
         * browser.
         * <p>
         * Note that returning {@link FormValidation#error(String)} does not
         * prevent the form from being saved. It just means that a message will
         * be displayed to the user.
         * @throws javax.servlet.ServletException
         * @throws IOException 
         */
        public FormValidation doCheckXmxValue(@QueryParameter String value) throws IOException, ServletException {

            if (value.length() == 0) {
                return FormValidation.ok();
            }
            try {
                int xmxValue = Float.valueOf(value).intValue();
                if (xmxValue <= 64) {
                    return FormValidation.error("Please fill-in a Xmx value superior to default Xms value (64)");
                }
            } catch (NumberFormatException nfe) {
                return FormValidation.error("Please fill-in a valid Xmx value");
            }
            return FormValidation.ok();
        }

        /**
         * Performs on-the-fly validation of the form field 'Min Mark Threshold 
         * For Stable'.
         *
         * @param value This parameter receives the value that the user has
         * typed.
         * @return Indicates the outcome of the validation. This is sent to the
         * browser.
         * <p>
         * Note that returning {@link FormValidation#error(String)} does not
         * prevent the form from being saved. It just means that a message will
         * be displayed to the user.
         * @throws javax.servlet.ServletException
         * @throws IOException
         */
        public FormValidation doCheckMinMarkThresholdForStable(@QueryParameter String value)
                throws IOException, ServletException {

            if (value.length() == 0) {
                return FormValidation.ok();
            }
            try {
                int mark = Float.valueOf(value).intValue();
                if (mark > 100) {
                    return FormValidation.error("Please fill-in a valid minimal mark threshold");
                }
            } catch (NumberFormatException nfe) {
                return FormValidation.error("Please fill-in a valid minimal mark threshold");
            }
            return FormValidation.ok();
        }

        /**
         * Performs on-the-fly validation of the form field 'Max Failed 
         * Occurences For Stable'.
         *
         * @param value This parameter receives the value that the user has
         * typed.
         * @return Indicates the outcome of the validation. This is sent to the
         * browser.
         * <p>
         * Note that returning {@link FormValidation#error(String)} does not
         * prevent the form from being saved. It just means that a message will
         * be displayed to the user.
         * @throws javax.servlet.ServletException
         * @throws IOException
         */
        public FormValidation doCheckMaxFailedOccurencesForStable(@QueryParameter String value)
                throws IOException, ServletException {

            if (value.length() == 0) {
                return FormValidation.ok();
            }
            try {
                int mark = Integer.valueOf(value);
                if (mark > 65535) {
                    return FormValidation
                            .error("Please fill-in a valid maximal number of failed occurences threshold");
                }
            } catch (NumberFormatException nfe) {
                return FormValidation.error("Please fill-in a valid maximal number of failed occurences threshold");
            }
            return FormValidation.ok();
        }

        /**
         * Fill-in values of the form field 'referential and level'.
         *
         * @param selection This parameter receives the value that the user has
         * typed.
         * @return the filled-in ListBoxModel 
         */
        public ListBoxModel doFillRefAndLevelItems(@QueryParameter String selection) {
            return new ListBoxModel(new Option("Accessiweb2.2 : Bronze", "Aw22;Bz"),
                    new Option("Accessiweb2.2 : Argent", "Aw22;Ar"), new Option("Accessiweb2.2 : Or", "Aw22;Or"),
                    new Option("Rgaa2.2 : A", "Rgaa22;Bz"), new Option("Rgaa2.2 : AA", "Rgaa22;Ar"),
                    new Option("Rgaa2.2 : AAA", "Rgaa22;Or"), new Option("Rgaa3 : A", "Rgaa30;Bz"),
                    new Option("Rgaa3 : AA", "Rgaa30;Ar"), new Option("Rgaa3 : AAA", "Rgaa30;Or"));
        }

        @Override
        public boolean isApplicable(Class<? extends AbstractProject> aClass) {
            // Indicates that this builder can be used with all kinds of project types 
            return true;
        }

        /**
         * @return the human readable name used in the configuration screen.
         */
        @Override
        public String getDisplayName() {
            return "Tanaguru Runner";
        }

        @Override
        public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {

            pathToCli = formData.getString("tanaguruCliPath");
            displayPort = formData.getString("displayPort");
            firefoxPath = formData.getString("firefoxPath");
            tanaguruInstallation = new TanaguruInstallation(formData.getString("webappUrl"),
                    formData.getString("databaseHost"), formData.getString("databasePort"),
                    formData.getString("databaseName"), formData.getString("databaseLogin"),
                    formData.getString("databasePassword"), formData.getString("tanaguruLogin"));
            isDebug = formData.getBoolean("isDebug");

            save();
            return super.configure(req, formData);
        }

        /**
         * @return the path to the tanaguru cli script.
         */
        public String getTanaguruCliPath() {
            return pathToCli;
        }

        /**
         * @return the path to the tanaguru cli script.
         */
        public String getDisplayPort() {
            return displayPort;
        }

        /**
         * @return the path to the firefox instance.
         */
        public String getFirefoxPath() {
            return firefoxPath;
        }

        /**
         * @return whether the debug mode is activated
         */
        public boolean getIsDebug() {
            return isDebug;
        }

        /**
         * @return all configured {@link jenkins.plugins.tanaguru.TanaguruInstallation}
         */
        public TanaguruInstallation getInstallation() {
            return tanaguruInstallation;
        }
    }
}