org.centralperf.controller.RunController.java Source code

Java tutorial

Introduction

Here is the source code for org.centralperf.controller.RunController.java

Source

/*
 * Copyright (C) 2014  The Central Perf authors
 *
 * This program 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/>.
 */

package org.centralperf.controller;

import java.io.IOException;
import java.util.Date;

import javax.annotation.Resource;
import javax.validation.Valid;

import org.centralperf.controller.exception.ControllerValidationException;
import org.centralperf.model.RunDetailGraphTypesEnum;
import org.centralperf.model.dao.Run;
import org.centralperf.model.dao.ScriptVariable;
import org.centralperf.model.dao.ScriptVersion;
import org.centralperf.repository.ProjectRepository;
import org.centralperf.repository.RunRepository;
import org.centralperf.repository.ScriptRepository;
import org.centralperf.repository.ScriptVersionRepository;
import org.centralperf.sampler.api.SamplerRunJob;
import org.centralperf.service.RunService;
import org.centralperf.service.ScriptLauncherService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

/**
 * The Run controller manage user interactions with the runs
 *  
 * @since 1.0
 * 
 */
@Controller
@SessionAttributes
public class RunController extends BaseController {

    @Resource
    private RunRepository runRepository;

    @Resource
    private ScriptRepository scriptRepository;

    @Resource
    private ScriptVersionRepository scriptVersionRepository;

    @Resource
    private ScriptLauncherService scriptLauncherService;

    @Resource
    private RunService runService;

    @Resource
    private ProjectRepository projectRepository;

    @Value("#{appProperties['report.cache.delay.seconds']}")
    private Long cacheRefreshDelay;

    private static final Logger log = LoggerFactory.getLogger(RunController.class);

    /**
     * Display the form to create a new Run, for a specific project
     * @param projectId   ID of the project for this run (from URI)
     * @param model   Model prepared for the new project form view
     * @return The path to the new run form view
     */
    @RequestMapping(value = "/project/{projectId}/run/new", method = RequestMethod.GET)
    public String addRunForm(@PathVariable("projectId") Long projectId, Model model) {
        model.addAttribute("newRun", new Run());
        model.addAttribute("project", projectRepository.findOne(projectId));
        return "macros/run/new-run-form.macro";
    }

    /**
    * Display the form to create a new Run, no project specified
    * @param model   Model prepared for the new run form view
     * @return
     */
    @RequestMapping(value = "/run/new", method = RequestMethod.GET)
    public String addRunFormGlobal(Model model) {
        model.addAttribute("newRun", new Run());
        model.addAttribute("projects", projectRepository.findAll());
        return "macros/run/new-run-form.macro";
    }

    /**
     * Create a new Run
     * @param projectId ID of the project (from URI)
     * @param run   The Run to create
     * @param result   BindingResult to check if binding raised some errors
     * @param model   Model prepared for the new project form view in case of errors
     * @return Redirection to the run detail page once the run has been created 
     */
    @RequestMapping(value = "/project/{projectId}/run/new", method = RequestMethod.POST)
    public String addRun(@PathVariable("projectId") Long projectId, @ModelAttribute("run") @Valid Run run,
            BindingResult result, Model model, RedirectAttributes redirectAttrs) {
        if (result.hasErrors()) {
            String errorMessage = "Error creating Run : ";
            for (FieldError error : result.getFieldErrors()) {
                errorMessage += "Field " + error.getField() + ",  " + error.getDefaultMessage() + " ( "
                        + error.getRejectedValue() + ")";
            }
            redirectAttrs.addFlashAttribute("error", errorMessage);
            return "redirect:/project/" + projectId + "/detail";
        }
        ScriptVersion scriptVersion = scriptVersionRepository.findOne(run.getScriptVersion().getId());
        run.setScriptVersion(scriptVersion);
        runRepository.save(run);
        return "redirect:/project/" + projectId + "/run/" + run.getId() + "/detail";
    }

    /**
     * Delete a run
     * @param projectId   ID of the project (from URI)
     * @param runId   ID of the run to delete  (from URI)
     * @return Redirection the project detail page
     */
    @RequestMapping(value = "/project/{projectId}/run/{runId}/delete", method = RequestMethod.GET)
    public String deleteRun(@PathVariable("runId") Long runId, @PathVariable("projectId") Long projectId) {
        runRepository.delete(runId);
        return "redirect:/project/" + projectId + "/detail";
    }

    /**
     * Display all runs
     * @param model   Model prepared for "all runs" view
     * @return The "all runs" view name
     */
    @RequestMapping("/run")
    public String showRuns(Model model) {
        log.debug("Displaying runs");
        Sort runSort = new Sort(Sort.Direction.DESC, "startDate");
        model.addAttribute("runs", runRepository.findAll(runSort));
        Sort scriptSort = new Sort(Sort.DEFAULT_DIRECTION, "label");
        model.addAttribute("scripts", scriptRepository.findAll(scriptSort));
        model.addAttribute("newRun", new Run());
        return "runs";
    }

    /**
     * Launch a Run
     * @param projectId   ID of the project (from URI)
     * @param runId   ID of the run (from URI)
     * @return   Redirection to the Run detail page
     */
    @RequestMapping(value = "/project/{projectId}/run/{runId}/launch", method = RequestMethod.GET)
    public String launchRun(@PathVariable("projectId") Long projectId, @PathVariable("runId") Long runId) {
        Run run = runRepository.findOne(runId);
        // If the run has already been launched, then create a new run and
        if (run.isLaunched()) {
            run = runService.copyRun(runId);
        }
        scriptLauncherService.launchRun(run);
        return "redirect:/project/" + projectId + "/run/" + run.getId() + "/detail";
    }

    /**
     * Stop (abort) a running run
     * @param projectId   ID of the project (from URI)
     * @param runId   ID of the run (from URI)
     * @return   Redirection to the Run detail page once the run has been stopped
     */
    @RequestMapping(value = "/project/{projectId}/run/{runId}/stop", method = RequestMethod.GET)
    public String stopRun(@PathVariable("projectId") Long projectId, @PathVariable("runId") Long runId) {
        Run run = runRepository.findOne(runId);
        // If the run has already been launched, then create a new run and
        if (run.isLaunched()) {
            scriptLauncherService.stopRun(run);
            String temp = (run.getComment() == null) ? "INTERRUPTED BY USER !!!"
                    : "INTERRUPTED BY USER !!!\r\n" + run.getComment();
            run.setComment(temp);
            runRepository.save(run);
        }
        return "redirect:/project/" + projectId + "/run/" + run.getId() + "/detail";
    }

    /**
     * Copy a run
     * @param projectId   ID of the project (from URI)
     * @param rundId   ID of the run to copy (from URI)
     * @return   Redirection to the "copied" run detail page
     */
    @RequestMapping(value = "/project/{projectId}/run/{runId}/copy", method = RequestMethod.GET)
    public String copyRun(@PathVariable("projectId") Long projectId, @PathVariable("runId") Long rundId) {
        Run newRun = runService.copyRun(rundId);
        return "redirect:/project/" + projectId + "/run/" + newRun.getId() + "/detail";
    }

    /**
     * Display run detail
     * @param projectId   ID of the project  (from URI)
     * @param runId   ID of the run to display  (from URI)
     * @param model   Model prepared for the run detail view
     * @return   Name of the run detail view
     */
    @RequestMapping(value = "/project/{projectId}/run/{runId}/detail", method = RequestMethod.GET)
    public String showRunDetail(@PathVariable("projectId") Long projectId, @PathVariable("runId") Long runId,
            Model model) {
        return showRunDetail(projectId, runId, RunDetailGraphTypesEnum.SUMMARY.getPageName(), model);
    }

    /**
     * Display run detail and arrive directly to a specific chart page
     * @param projectId   ID of the project  (from URI)
     * @param runId   ID of the run to display  (from URI)
     * @param page   Name of the chart type (see {@link RunDetailGraphTypesEnum#pageName})
     * @param model Model prepared for the run detail view
     * @return Name of the run detail view
     */
    @RequestMapping(value = "/project/{projectId}/run/{runId}/detail/{page}", method = RequestMethod.GET)
    public String showRunDetail(@PathVariable("projectId") Long projectId, @PathVariable("runId") Long runId,
            @PathVariable("page") String page, Model model) {
        log.debug("Run details for run [" + runId + "]");
        populateModelWithRunInfo(runId, model);
        model.addAttribute("page", page);
        model.addAttribute("refreshDelay", cacheRefreshDelay * 1000);
        return "runDetail";
    }

    /**
     * Update Run script variables set
     * @param projectId   ID of the project  (from URI)
     * @param runId   ID of the run to update  (from URI)
     * @param variableName   Name of the variable to update
     * @param variableValue   New value for the variable
     * @return true if the variable has been updated
     */
    @RequestMapping(value = "/project/{projectId}/run/{runId}/variables/update", method = RequestMethod.POST)
    @ResponseBody
    public boolean updateRunVariables(@PathVariable("projectId") Long projectId, @PathVariable("runId") Long runId,
            @RequestParam("name") String variableName, @RequestParam("value") String variableValue) {
        log.debug("Update variable " + variableName + " with value " + variableValue + " for run [" + runId + "]");
        ScriptVariable variable = new ScriptVariable();
        variable.setName(variableName);
        variable.setValue(variableValue);
        runService.updateRunVariable(runId, variable);
        return true;
    }

    /**
     * Returns the local start time of a run.
     * Local client make request and give it's timestamp.
     * Server compute delta between it's timestamp and client timestamp, and return run start time based on local time
     * @param projectId ID of the project  (From URI)
     * @param runId ID of the run
     * @param ts local timestamp (from client browser)
     * @return Run start time based on local time (compute delta from local and server time if there are not sync)
     */
    @RequestMapping(value = "/project/{projectId}/run/{runId}/startat/{ts}", method = RequestMethod.GET)
    @ResponseBody
    public Long getLocalStartTime(@PathVariable("projectId") Long projectId, @PathVariable("runId") Long runId,
            @PathVariable("ts") Long ts, Model model) {
        Run run = runRepository.findOne(runId);
        long duration = 0;
        long lastTime = (new Date()).getTime();
        if (run != null && run.isLaunched()) {
            if (!run.isRunning()) {
                lastTime = run.getEndDate().getTime();
            }
            duration = lastTime - run.getStartDate().getTime();
        }
        return ts - duration;
    }

    /**
     * Create a run and import result from JTL (CSV) file
     * @param projectId ID of the project (From URI)
     * @return Redirection to the run detail page
     */
    @RequestMapping(value = "/project/{projectId}/run/import", method = RequestMethod.POST)
    public String importRun(@PathVariable("projectId") Long projectId, @ModelAttribute("run") @Valid Run run,
            @RequestParam("jtlFile") MultipartFile file, BindingResult result, Model model) {
        if (result.hasErrors()) {
            model.addAttribute("newRun", run);
            return "redirect:/project/" + projectId + "/run";
        }

        // Get the jtl File
        try {
            String jtlContent = new String(file.getBytes());
            runService.importRun(run, jtlContent);
        } catch (IOException e) {
        }

        return "redirect:/project/" + projectId + "/run/" + run.getId() + "/detail";
    }

    /**
     * Set results from an uploaded JTL file for an existing run
     * @param projectId   ID of the project (From URI)
     * @param runId   ID of the run (from URI)
     * @param file   JTL file
     * @return   Redirection to run detail page
     */
    @RequestMapping(value = "/project/{projectId}/run/{runId}/results", method = RequestMethod.POST)
    public String uploadResults(@PathVariable("projectId") Long projectId, @ModelAttribute("runId") Long runId,
            @RequestParam("jtlFile") MultipartFile file) {

        Run run = runRepository.findOne(runId);

        // Get the jtl File
        try {
            String jtlContent = new String(file.getBytes());
            runService.insertResultsFromCSV(run, jtlContent);
        } catch (IOException e) {
        }
        return "redirect:/project/" + projectId + "/run/" + run.getId() + "/detail";
    }

    /**
     * Update run informations (label, comment)
     * @param runId   ID of the run (from URI)
     * @param label   New label (not updated if null)
     * @param comment   New comment (not updated if null)
     * @return
     * @throws ControllerValidationException 
     */
    @RequestMapping(value = "/run/{runId}", method = RequestMethod.POST)
    @ResponseBody
    public String updateRun(@PathVariable("runId") Long runId,
            @RequestParam(value = "label", required = false) String label,
            @RequestParam(value = "comment", required = false) String comment)
            throws ControllerValidationException {
        Run run = runRepository.findOne(runId);
        String valueToReturn = label;
        if (label != null) {
            run.setLabel(label);
        } else if (comment != null) {
            run.setComment(comment);
            valueToReturn = comment;
        }

        // Validate the data
        validateBean(run);

        runRepository.save(run);
        return valueToReturn;
    }

    /**
     * Set model info depending on run state
     * @param runId ID of the run (from URI)
     * @param model Model to update
     */
    private void populateModelWithRunInfo(Long runId, Model model) {
        Run run = runRepository.findOne(runId);
        SamplerRunJob job = scriptLauncherService.getJob(run.getId());
        model.addAttribute("run", run);
        model.addAttribute("job", job);
    }
}