de.unihannover.se.processSimulation.interactive.ServerMain.java Source code

Java tutorial

Introduction

Here is the source code for de.unihannover.se.processSimulation.interactive.ServerMain.java

Source

/**
This file is part of LUH PrePostReview Process Simulation.
    
LUH PrePostReview Process Simulation is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
LUH PrePostReview Process Simulation 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 General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with LUH PrePostReview Process Simulation. If not, see <http://www.gnu.org/licenses/>.
 */

package de.unihannover.se.processSimulation.interactive;

import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.data.category.DefaultCategoryDataset;

import com.google.common.io.Files;

import de.unihannover.se.processSimulation.common.ReviewMode;
import de.unihannover.se.processSimulation.dataGenerator.BulkParameterFactory;
import de.unihannover.se.processSimulation.dataGenerator.BulkParameterFactory.ParameterType;
import de.unihannover.se.processSimulation.dataGenerator.DataGenerator;
import de.unihannover.se.processSimulation.dataGenerator.ExperimentResult;
import de.unihannover.se.processSimulation.dataGenerator.ExperimentRun;
import de.unihannover.se.processSimulation.dataGenerator.ExperimentRun.ExperimentRunSummary;
import de.unihannover.se.processSimulation.dataGenerator.ExperimentRun.SingleRunCallback;
import de.unihannover.se.processSimulation.dataGenerator.ExperimentRunSettings;
import de.unihannover.se.processSimulation.dataGenerator.ExperimentRunSettings.ExperimentRunParameters;
import de.unihannover.se.processSimulation.dataGenerator.MedianWithConfidenceInterval;
import desmoj.core.simulator.CoroutineModel;
import desmoj.core.simulator.Experiment;

/**
 * Main class of the interactive web-based simulation view.
 */
public class ServerMain extends AbstractHandler {

    private static final String PARAMS_PROPERTIES = "params.properties";
    private static final String SETTINGS_PROPERTIES = "settings.properties";
    private final AtomicInteger requestIdCounter = new AtomicInteger();

    @Override
    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {

        baseRequest.setHandled(true);

        final Matcher detailsMainMatcher = Pattern.compile("/details/([0-9]+)/([0-9]+)/overview").matcher(target);
        final Matcher plotImageMatcher = Pattern.compile("/details/([0-9]+)/([0-9]+)/(.+)\\.png").matcher(target);
        final Matcher detailsFileMatcher = Pattern.compile("/details/([0-9]+)/([0-9]+)/(.+)").matcher(target);

        if (target.equals("/")) {
            final int currentRequestId = this.getNewRequestId();
            this.handleMainPage(request, response, currentRequestId, false);
        } else if (target.equals("/full")) {
            final int currentRequestId = this.getNewRequestId();
            this.handleMainPage(request, response, currentRequestId, true);
        } else if (detailsMainMatcher.matches()) {
            this.handleDetailsPage(request, response, Integer.parseInt(detailsMainMatcher.group(1)),
                    Integer.parseInt(detailsMainMatcher.group(2)));
        } else if (plotImageMatcher.matches()) {
            this.handlePlotImage(request, response, Integer.parseInt(plotImageMatcher.group(1)),
                    Integer.parseInt(plotImageMatcher.group(2)), plotImageMatcher.group(3));
        } else if (detailsFileMatcher.matches()) {
            this.handleDetailFileRequest(request, response, Integer.parseInt(detailsFileMatcher.group(1)),
                    Integer.parseInt(detailsFileMatcher.group(2)), detailsFileMatcher.group(3));
        }
    }

    private int getNewRequestId() {
        int requestId;
        do {
            requestId = this.requestIdCounter.getAndIncrement();
        } while (this.getRequestDir(requestId).exists());
        return requestId;
    }

    private void handleMainPage(HttpServletRequest request, HttpServletResponse response, int requestId,
            boolean allParams) throws IOException {
        response.setContentType("text/html;charset=utf-8");
        response.setStatus(HttpServletResponse.SC_OK);

        final PrintWriter w = response.getWriter();
        this.printHeader(w);
        w.println("<h1>Pre/Post commit review comparison - Process simulation</h1>");
        final BulkParameterFactory f = this.getParameters(w, request);
        final ExperimentRunSettings s = this.getExperimentSettings(w, request);
        this.saveParametersAndSettings(requestId, f, s);
        this.printInputParameters(w, f, s, allParams);
        if (this.shallSimulate(request)) {
            this.simulateAndPrintOutput(w, f, s, requestId);
        }
        this.printFooter(w);
    }

    private void saveParametersAndSettings(int requestId, BulkParameterFactory f, ExperimentRunSettings s)
            throws IOException {
        final File requestDir = this.getRequestDir(requestId);
        requestDir.mkdirs();

        this.storeProperties(this.parametersToProperties(f), requestDir, PARAMS_PROPERTIES,
                "Params for request id " + requestId);
        this.storeProperties(this.settingsToProperties(s), requestDir, SETTINGS_PROPERTIES,
                "Settings for request id " + requestId);
    }

    private void storeProperties(Properties p, File requestDir, String name, String comment) throws IOException {
        try (FileOutputStream out = new FileOutputStream(new File(requestDir, name))) {
            p.store(out, comment);
        }
    }

    private File getRequestDir(int requestId) {
        return new File("webguiWorkdir", Integer.toString(requestId));
    }

    private Properties parametersToProperties(BulkParameterFactory f) {
        final Properties ret = new Properties();
        for (final ParameterType type : ParameterType.values()) {
            ret.setProperty(type.name(), f.getParam(type).toString());
        }
        return ret;
    }

    private Properties settingsToProperties(ExperimentRunSettings s) {
        final Properties ret = new Properties();
        for (final ExperimentRunParameters type : ExperimentRunParameters.values()) {
            ret.setProperty(type.name(), Double.toString(s.get(type)));
        }
        return ret;
    }

    private void handleDetailsPage(HttpServletRequest request, HttpServletResponse response, int requestId,
            int runNbr) throws IOException {

        final BulkParameterFactory f = this.createRunDirectoryIfMissing(requestId, runNbr);

        response.setContentType("text/html;charset=utf-8");
        response.setStatus(HttpServletResponse.SC_OK);

        final PrintWriter w = response.getWriter();
        w.println("<h2>Details for run " + runNbr + "</h2>");
        w.println("<h3>Parameters</h3>");
        for (final ParameterType type : ParameterType.values()) {
            w.println(type + ": " + f.getParam(type) + "<br/>");
        }
        w.println("Seed: " + f.getSeed());

        w.println("<h3>Pre commit review</h3>");
        w.println(
                "<img src=\"ExperimentPRE_COMMIT_runplotBoard.png\" /><img src=\"ExperimentPRE_COMMIT_runplotResults.png\" /><br/>");
        w.println(
                "<iframe width=\"800\" height=\"400\" src=\"ExperimentPRE_COMMIT_run_report.html\">Report</iframe><br/>");
        w.println("<a href=\"ExperimentPRE_COMMIT_run_trace.html\">Trace</a><br/>");
        w.println("<a href=\"ExperimentPRE_COMMIT_run_error.html\">Error log</a><br/>");
        w.println("<a href=\"ExperimentPRE_COMMIT_run_debug.html\">Debug</a><br/>");

        w.println("<h3>Post commit review</h3>");
        w.println(
                "<img src=\"ExperimentPOST_COMMIT_runplotBoard.png\" /><img src=\"ExperimentPOST_COMMIT_runplotResults.png\" /><br/>");
        w.println(
                "<iframe width=\"800\" height=\"400\" src=\"ExperimentPOST_COMMIT_run_report.html\">Report</iframe><br/>");
        w.println("<a href=\"ExperimentPOST_COMMIT_run_trace.html\">Trace</a><br/>");
        w.println("<a href=\"ExperimentPOST_COMMIT_run_error.html\">Error log</a><br/>");
        w.println("<a href=\"ExperimentPOST_COMMIT_run_debug.html\">Debug</a><br/>");

        w.println("<h3>No review</h3>");
        w.println(
                "<img src=\"ExperimentNO_REVIEW_runplotBoard.png\" /><img src=\"ExperimentNO_REVIEW_runplotResults.png\" /><br/>");
        w.println(
                "<iframe width=\"800\" height=\"400\" src=\"ExperimentNO_REVIEW_run_report.html\">Report</iframe><br/>");
        w.println("<a href=\"ExperimentNO_REVIEW_run_trace.html\">Trace</a><br/>");
        w.println("<a href=\"ExperimentNO_REVIEW_run_error.html\">Error log</a><br/>");
        w.println("<a href=\"ExperimentNO_REVIEW_run_debug.html\">Debug</a><br/>");
    }

    private BulkParameterFactory createRunDirectoryIfMissing(int requestId, int runNbr) throws IOException {
        assert runNbr > 0;

        BulkParameterFactory f = this.loadParameters(requestId);
        final ExperimentRunSettings s = this.loadSettings(requestId);
        for (int i = 1; i < runNbr; i++) {
            f = f.copyWithChangedSeed();
        }

        final File runDirectory = this.getRunDir(requestId, runNbr);
        synchronized (this) {
            if (!runDirectory.exists()) {
                runDirectory.mkdir();

                final int daysForStartup = (int) s.get(ExperimentRunParameters.WORKING_DAYS_FOR_STARTUP);
                final int daysForMeasurement = (int) s.get(ExperimentRunParameters.WORKING_DAYS_FOR_MEASUREMENT);

                DataGenerator.runExperiment(f, ReviewMode.NO_REVIEW, runDirectory, "run", daysForStartup,
                        daysForMeasurement);
                DataGenerator.runExperiment(f, ReviewMode.PRE_COMMIT, runDirectory, "run", daysForStartup,
                        daysForMeasurement);
                DataGenerator.runExperiment(f, ReviewMode.POST_COMMIT, runDirectory, "run", daysForStartup,
                        daysForMeasurement);
            }
        }

        return f;
    }

    private File getRunDir(int requestId, int runNbr) {
        return new File(this.getRequestDir(requestId), Integer.toString(runNbr));
    }

    private BulkParameterFactory loadParameters(int requestId) throws IOException {
        final File requestDir = this.getRequestDir(requestId);
        final File paramsFile = new File(requestDir, PARAMS_PROPERTIES);
        try (FileInputStream in = new FileInputStream(paramsFile)) {
            final Properties properties = new Properties();
            properties.load(in);

            BulkParameterFactory ret = BulkParameterFactory.forCommercial();
            for (final String name : properties.stringPropertyNames()) {
                final ParameterType type = ParameterType.valueOf(name);
                ret = ret.copyWithChangedParam(type, type.parse(properties.getProperty(name)));
            }
            return ret;
        }
    }

    private ExperimentRunSettings loadSettings(int requestId) throws IOException {
        final File requestDir = this.getRequestDir(requestId);
        final File paramsFile = new File(requestDir, SETTINGS_PROPERTIES);
        try (FileInputStream in = new FileInputStream(paramsFile)) {
            final Properties properties = new Properties();
            properties.load(in);

            ExperimentRunSettings ret = ExperimentRunSettings.defaultSettings();
            for (final String name : properties.stringPropertyNames()) {
                final ExperimentRunParameters type = ExperimentRunParameters.valueOf(name);
                ret = ret.copyWithChangedParam(type, Double.parseDouble(properties.getProperty(name)));
            }
            return ret;
        }
    }

    private void handlePlotImage(HttpServletRequest request, HttpServletResponse response, int requestId,
            int runNbr, String plotName) throws IOException {

        try {
            final DefaultCategoryDataset dataset = this
                    .loadDatasetFromCsv(new File(this.getRunDir(requestId, runNbr), plotName + ".csv"));
            response.setContentType("image/png");
            final JFreeChart chart = ChartFactory.createLineChart("", "time", "count", dataset);
            final BufferedImage image = chart.createBufferedImage(800, 500);
            ImageIO.write(image, "PNG", response.getOutputStream());
        } catch (final Exception e) {
            e.printStackTrace();
            throw e;
        }
    }

    private DefaultCategoryDataset loadDatasetFromCsv(File csvFile) throws IOException {
        try (BufferedReader r = new BufferedReader(new FileReader(csvFile))) {
            final DefaultCategoryDataset dataset = new DefaultCategoryDataset();
            final String[] columnNames = r.readLine().split(";");
            String line;
            while ((line = r.readLine()) != null) {
                final String[] values = line.split(";");
                final Double time = Double.valueOf(values[0]);
                for (int i = 1; i < values.length; i++) {
                    dataset.addValue(Double.parseDouble(values[i]), columnNames[i], time);
                }
            }
            return dataset;
        }
    }

    private void handleDetailFileRequest(HttpServletRequest request, HttpServletResponse response, int requestId,
            int runNbr, String filename) throws IOException {
        final File file = new File(this.getRunDir(requestId, runNbr), filename);

        response.setContentType("text/html;charset=utf-8");
        if (!file.exists()) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            response.getWriter().println("File not found: " + filename);
        } else {
            response.setStatus(HttpServletResponse.SC_OK);
            Files.copy(file, response.getOutputStream());
        }
    }

    private boolean shallSimulate(HttpServletRequest request) {
        return request.getParameter(ExperimentRunParameters.MIN_RUNS.name()) != null;
    }

    private BulkParameterFactory getParameters(PrintWriter w, HttpServletRequest request) {
        BulkParameterFactory f = BulkParameterFactory.forCommercial();
        for (final ParameterType t : ParameterType.values()) {
            final String param = request.getParameter(t.name());
            if (param != null && !param.isEmpty()) {
                try {
                    f = f.copyWithChangedParam(t, t.parse(param));
                } catch (final RuntimeException e) {
                    this.printParseError(w, t.toString(), e);
                }
            }
        }
        return f;
    }

    private ExperimentRunSettings getExperimentSettings(PrintWriter w, HttpServletRequest request) {
        ExperimentRunSettings s = ExperimentRunSettings.defaultSettings();
        for (final ExperimentRunParameters t : ExperimentRunParameters.values()) {
            final String param = request.getParameter(t.name());
            if (param != null && !param.isEmpty()) {
                try {
                    s = s.copyWithChangedParam(t, Double.parseDouble(param));
                } catch (final RuntimeException e) {
                    this.printParseError(w, t.toString(), e);
                }
            }
        }
        return s;
    }

    private void printParseError(PrintWriter w, String t, final RuntimeException e) {
        w.println("<b>Fehler beim Parsen von Parameter " + t + "</b><br/>");
        w.println(e.toString());
        w.println("<br/>");
    }

    private void printHeader(final PrintWriter w) {
        w.println("<html><head><title>Pre/Post commit review comparison - Process simulation</title></head><body>");
    }

    private void printFooter(final PrintWriter w) {
        w.println("</body></html>");
    }

    private void printInputParameters(final PrintWriter w, BulkParameterFactory f, ExperimentRunSettings s,
            boolean allParams) {
        w.println("<form action=\"#\">");
        w.println("<h2>Input parameters</h2>");
        w.println("<table>");
        for (final ParameterType t : ParameterType.values()) {
            if (!allParams && t.getDescription().isEmpty()) {
                continue;
            }
            w.println("<tr><td style=\"vertical-align:top;text-align:right\">");
            w.println(t.toString());
            w.println("</td><td style=\"vertical-align:top\">");
            w.println(this.getInputFor(t, f));
            w.println("</td><td style=\"vertical-align:top\">");
            w.println(t.getDescription());
            w.println("</td></tr>");
        }
        w.println("</table>");
        w.println("<h2>Experiment settings</h2>");
        w.println("<table>");
        for (final ExperimentRunParameters t : ExperimentRunParameters.values()) {
            w.println("<tr><td style=\"vertical-align:top;text-align:right\">");
            w.println(t.toString());
            w.println("</td><td style=\"vertical-align:top\">");
            w.println(this.getInputFor(t, s));
            w.println("</td><td style=\"vertical-align:top\">");
            w.println(t.getDescription());
            w.println("</td></tr>");
        }
        w.println("</table>");
        w.println("<button type=\"submit\">Start simulation</button>");
        w.println("</form>");
    }

    private String getInputFor(ParameterType t, BulkParameterFactory f) {
        if (t.getType().isEnum()) {
            final StringBuilder ret = new StringBuilder();
            ret.append("<select name=\"").append(t.name()).append("\">");
            for (final Object o : t.getType().getEnumConstants()) {
                if (o.equals(f.getParam(t))) {
                    ret.append("<option selected=\"selected\">");
                } else {
                    ret.append("<option>");
                }
                ret.append(o.toString()).append("</option>");
            }
            ret.append("</select>");
            return ret.toString();
        } else if (t.getType().equals(Double.class)) {
            return "<input name=\"" + t.name() + "\" value=\"" + f.getParam(t)
                    + "\" type=\"number\" step=\"any\" />";
        } else if (t.getType().equals(Integer.class)) {
            return "<input name=\"" + t.name() + "\" value=\"" + f.getParam(t) + "\" type=\"number\" step=\"1\" />";
        } else {
            return "Invalid type: " + t.getType();
        }
    }

    private String getInputFor(ExperimentRunParameters t, ExperimentRunSettings s) {
        return "<input name=\"" + t.name() + "\" value=\"" + s.get(t) + "\" type=\"number\" step=\"any\" />";
    }

    private void simulateAndPrintOutput(PrintWriter w, BulkParameterFactory f, ExperimentRunSettings s,
            int requestId) {
        w.println("<h2>Simulation output</h2>");

        final StringBuilder detailsTable = new StringBuilder();
        final AtomicInteger count = new AtomicInteger(1);
        detailsTable.append("<table border=\"1\">");
        detailsTable.append(
                "<tr><th>#</th><th colspan=\"3\">Story points</th><th colspan=\"3\">Cycle time</th><th colspan=\"3\">Bugs found by customer</th></tr>\n");
        detailsTable.append(
                "<tr><th></th><th>no</th><th>pre</th><th>post</th><th>no</th><th>pre</th><th>post</th><th>no</th><th>pre</th><th>post</th></tr>\n");
        final SingleRunCallback detailsCallback = new SingleRunCallback() {
            @Override
            public void handleResult(ExperimentResult no, ExperimentResult pre, ExperimentResult post) {
                System.err.println("run " + count + " finished");
                detailsTable.append("<tr>");
                detailsTable.append("<td><a href=\"details/").append(requestId).append("/").append(count)
                        .append("/overview\" target=\"_blank\">").append(count).append("</a></td>");
                detailsTable.append("<td>").append(no == null ? "" : no.getFinishedStoryPoints()).append("</td>");
                detailsTable.append("<td>").append(pre.getFinishedStoryPoints()).append("</td>");
                detailsTable.append("<td>").append(post.getFinishedStoryPoints()).append("</td>");
                detailsTable.append("<td>").append(no == null ? "" : no.getStoryCycleTimeMean()).append("</td>");
                detailsTable.append("<td>").append(pre.getStoryCycleTimeMean()).append("</td>");
                detailsTable.append("<td>").append(post.getStoryCycleTimeMean()).append("</td>");
                detailsTable.append("<td>").append(no == null ? "" : no.getIssueCountFoundByCustomers())
                        .append("</td>");
                detailsTable.append("<td>").append(pre.getIssueCountFoundByCustomers()).append("</td>");
                detailsTable.append("<td>").append(post.getIssueCountFoundByCustomers()).append("</td>");
                detailsTable.append("</tr>");
                count.incrementAndGet();
            }
        };

        final ExperimentRun result;
        try {
            result = ExperimentRun.perform(s, DataGenerator::runExperiment, f, detailsCallback);
        } catch (final RuntimeException e) {
            w.println("An exception occured during simulation: " + e.getMessage());
            return;
        }

        final ExperimentRunSummary summary = result.getSummary();
        w.println("Summary result - Story points: " + summary.getStoryPointsResult() + "<br/>");
        w.println("Summary result - Bugs found by customer: " + summary.getIssuesResult() + "<br/>");
        w.println("Summary result - Cycle time: " + summary.getCycleTimeResult() + "<br/>");
        if (!result.isSummaryStatisticallySignificant()) {
            w.println("Summary result not statistically significant<br/>");
        }
        w.println("Median finished stories (best alternative): " + result.getFinishedStoryMedian().toHtml()
                + "<br/>");
        w.println("Median share of productive work: " + result.getShareProductiveWork().toHtmlPercent() + "<br/>");
        w.println("Median share no review/review story points: " + result.getFactorNoReview().toHtmlPercent()
                + "<br/>");
        w.println("Median difference pre/post story points: "
                + this.formatDiff(result.getFactorStoryPoints(), "pre", "post") + "; "
                + result.getMinMaxFactorStoryPoints() + "<br/>");
        w.println("Median difference pre/post issues found by customer/story point: "
                + this.formatDiff(result.getFactorIssues(), "post", "pre") + "; " + result.getMinMaxFactorIssues()
                + "<br/>");
        w.println("Median difference pre/post cycle time: "
                + this.formatDiff(result.getFactorCycleTime(), "post", "pre") + "; "
                + result.getMinMaxFactorCycleTime() + "<br/>");
        w.println("<br/>");

        detailsTable.append("<tr>");
        detailsTable.append("<td></td>");
        detailsTable.append("<td>").append(result.getFinishedStoryPointsMedian(ReviewMode.NO_REVIEW).toHtml())
                .append("</td>");
        detailsTable.append("<td>").append(result.getFinishedStoryPointsMedian(ReviewMode.PRE_COMMIT).toHtml())
                .append("</td>");
        detailsTable.append("<td>").append(result.getFinishedStoryPointsMedian(ReviewMode.POST_COMMIT).toHtml())
                .append("</td>");
        detailsTable.append("<td>").append(result.getStoryCycleTimeMeanMedian(ReviewMode.NO_REVIEW).toHtml())
                .append("</td>");
        detailsTable.append("<td>").append(result.getStoryCycleTimeMeanMedian(ReviewMode.PRE_COMMIT).toHtml())
                .append("</td>");
        detailsTable.append("<td>").append(result.getStoryCycleTimeMeanMedian(ReviewMode.POST_COMMIT).toHtml())
                .append("</td>");
        detailsTable.append("<td>").append(result.getIssueCountMedian(ReviewMode.NO_REVIEW).toHtml())
                .append("</td>");
        detailsTable.append("<td>").append(result.getIssueCountMedian(ReviewMode.PRE_COMMIT).toHtml())
                .append("</td>");
        detailsTable.append("<td>").append(result.getIssueCountMedian(ReviewMode.POST_COMMIT).toHtml())
                .append("</td>");
        detailsTable.append("</tr>");
        detailsTable.append("</table>");
        w.println(detailsTable);
    }

    private String formatDiff(MedianWithConfidenceInterval median, String betterWhenNegative,
            String betterWhenPositive) {
        return median.toHtmlPercent() + " [" + (median.getMedian() < 0 ? betterWhenNegative : betterWhenPositive)
                + " better]";
    }

    public static void main(String[] args) throws Exception {
        Experiment.setCoroutineModel(CoroutineModel.FIBERS);
        final Server server = new Server(args.length > 0 ? Integer.parseInt(args[0]) : 8080);
        server.setHandler(new ServerMain());

        server.start();
        server.join();
    }
}