src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java

Fri, 09 Oct 2020 19:07:05 +0200

author
Mike Becker <universe@uap-core.de>
date
Fri, 09 Oct 2020 19:07:05 +0200
changeset 124
ed2e7aef2a3e
parent 121
428dca747d6b
child 128
947d0f6a6a83
permissions
-rw-r--r--

adds issue comments

     1 /*
     2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3  *
     4  * Copyright 2018 Mike Becker. All rights reserved.
     5  *
     6  * Redistribution and use in source and binary forms, with or without
     7  * modification, are permitted provided that the following conditions are met:
     8  *
     9  *   1. Redistributions of source code must retain the above copyright
    10  *      notice, this list of conditions and the following disclaimer.
    11  *
    12  *   2. Redistributions in binary form must reproduce the above copyright
    13  *      notice, this list of conditions and the following disclaimer in the
    14  *      documentation and/or other materials provided with the distribution.
    15  *
    16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    17  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    19  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    20  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    26  * POSSIBILITY OF SUCH DAMAGE.
    27  *
    28  */
    29 package de.uapcore.lightpit.modules;
    32 import de.uapcore.lightpit.*;
    33 import de.uapcore.lightpit.dao.DataAccessObjects;
    34 import de.uapcore.lightpit.entities.*;
    35 import de.uapcore.lightpit.viewmodel.*;
    36 import de.uapcore.lightpit.viewmodel.util.IssueSorter;
    37 import org.slf4j.Logger;
    38 import org.slf4j.LoggerFactory;
    40 import javax.servlet.annotation.WebServlet;
    41 import javax.servlet.http.HttpServletRequest;
    42 import javax.servlet.http.HttpServletResponse;
    43 import java.io.IOException;
    44 import java.sql.Date;
    45 import java.sql.SQLException;
    46 import java.util.NoSuchElementException;
    47 import java.util.Optional;
    48 import java.util.stream.Collectors;
    49 import java.util.stream.Stream;
    51 import static de.uapcore.lightpit.Functions.fqn;
    53 @WebServlet(
    54         name = "ProjectsModule",
    55         urlPatterns = "/projects/*"
    56 )
    57 public final class ProjectsModule extends AbstractLightPITServlet {
    59     private static final Logger LOG = LoggerFactory.getLogger(ProjectsModule.class);
    61     private static final String SESSION_ATTR_SELECTED_PROJECT = fqn(ProjectsModule.class, "selected_project");
    62     private static final String SESSION_ATTR_SELECTED_VERSION = fqn(ProjectsModule.class, "selected_version");
    63     private static final String PARAMETER_SELECTED_PROJECT = "pid";
    64     private static final String PARAMETER_SELECTED_VERSION = "vid";
    66     @Override
    67     protected String getResourceBundleName() {
    68         return "localization.projects";
    69     }
    71     private int syncParamWithSession(HttpServletRequest req, String param, String attr) {
    72         final var session = req.getSession();
    73         final var idParam = getParameter(req, Integer.class, param);
    74         final int id;
    75         if (idParam.isPresent()) {
    76             id = idParam.get();
    77             session.setAttribute(attr, id);
    78         } else {
    79             id = Optional.ofNullable(session.getAttribute(attr)).map(x->(Integer)x).orElse(-1);
    80         }
    81         return id;
    82     }
    84     private void populate(ProjectView viewModel, HttpServletRequest req, DataAccessObjects dao) throws SQLException {
    85         final var projectDao = dao.getProjectDao();
    86         final var versionDao = dao.getVersionDao();
    88         projectDao.list().stream().map(ProjectInfo::new).forEach(viewModel.getProjectList()::add);
    90         // Select Project
    91         final int pid = syncParamWithSession(req, PARAMETER_SELECTED_PROJECT, SESSION_ATTR_SELECTED_PROJECT);
    92         if (pid >= 0) {
    93             final var project = projectDao.find(pid);
    94             if (project == null) {
    95                 req.setAttribute(SESSION_ATTR_SELECTED_PROJECT, -1);
    96             } else {
    97                 final var info = new ProjectInfo(project);
    98                 info.setVersions(versionDao.list(project));
    99                 info.setIssueSummary(projectDao.getIssueSummary(project));
   100                 viewModel.setProjectInfo(info);
   101             }
   102         }
   104         // Select Version
   105         final int vid = syncParamWithSession(req, PARAMETER_SELECTED_VERSION, SESSION_ATTR_SELECTED_VERSION);
   106         if (vid >= 0) {
   107             viewModel.setVersionFilter(versionDao.find(vid));
   108         }
   109     }
   111     private ResponseType forwardView(HttpServletRequest req, ProjectView viewModel, String name) {
   112         setViewModel(req, viewModel);
   113         setContentPage(req, name);
   114         setStylesheet(req, "projects");
   115         setNavigationMenu(req, "project-navmenu");
   116         return ResponseType.HTML;
   117     }
   119     @RequestMapping(method = HttpMethod.GET)
   120     public ResponseType index(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
   121         final var viewModel = new ProjectView();
   122         populate(viewModel, req, dao);
   124         final var projectDao = dao.getProjectDao();
   125         final var versionDao = dao.getVersionDao();
   127         for (var info : viewModel.getProjectList()) {
   128             info.setVersions(versionDao.list(info.getProject()));
   129             info.setIssueSummary(projectDao.getIssueSummary(info.getProject()));
   130         }
   132         return forwardView(req, viewModel, "projects");
   133     }
   135     private void configure(ProjectEditView viewModel, Project project, DataAccessObjects dao) throws SQLException {
   136         viewModel.setProject(project);
   137         viewModel.setUsers(dao.getUserDao().list());
   138     }
   140     @RequestMapping(requestPath = "edit", method = HttpMethod.GET)
   141     public ResponseType edit(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
   142         final var viewModel = new ProjectEditView();
   143         populate(viewModel, req, dao);
   145         final var project = Optional.ofNullable(viewModel.getProjectInfo())
   146                 .map(ProjectInfo::getProject)
   147                 .orElse(new Project(-1));
   148         configure(viewModel, project, dao);
   150         return forwardView(req, viewModel, "project-form");
   151     }
   153     @RequestMapping(requestPath = "commit", method = HttpMethod.POST)
   154     public ResponseType commit(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
   156         Project project = new Project(-1);
   157         try {
   158             project = new Project(getParameter(req, Integer.class, "pid").orElseThrow());
   159             project.setName(getParameter(req, String.class, "name").orElseThrow());
   160             getParameter(req, String.class, "description").ifPresent(project::setDescription);
   161             getParameter(req, String.class, "repoUrl").ifPresent(project::setRepoUrl);
   162             getParameter(req, Integer.class, "owner").map(
   163                     ownerId -> ownerId >= 0 ? new User(ownerId) : null
   164             ).ifPresent(project::setOwner);
   166             dao.getProjectDao().saveOrUpdate(project);
   168             setRedirectLocation(req, "./projects/");
   169             setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
   170             LOG.debug("Successfully updated project {}", project.getName());
   172             return ResponseType.HTML;
   173         } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) {
   174             LOG.warn("Form validation failure: {}", ex.getMessage());
   175             LOG.debug("Details:", ex);
   176             final var viewModel = new ProjectEditView();
   177             populate(viewModel, req, dao);
   178             configure(viewModel, project, dao);
   179             // TODO: error text
   180             return forwardView(req, viewModel, "project-form");
   181         }
   182     }
   184     @RequestMapping(requestPath = "view", method = HttpMethod.GET)
   185     public ResponseType view(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws SQLException, IOException {
   186         final var viewModel = new ProjectDetailsView();
   187         populate(viewModel, req, dao);
   189         if (viewModel.getProjectInfo() == null) {
   190             resp.sendError(HttpServletResponse.SC_NOT_FOUND, "No project selected.");
   191             return ResponseType.NONE;
   192         }
   194         final var issueDao = dao.getIssueDao();
   196         final var version = viewModel.getVersionFilter();
   198         final var detailView = viewModel.getProjectDetails();
   199         final var issues = issueDao.list(version);
   200         for (var issue : issues) issueDao.joinVersionInformation(issue);
   201         issues.sort(new IssueSorter(
   202                 new IssueSorter.Criteria(IssueSorter.Field.PHASE, true),
   203                 new IssueSorter.Criteria(IssueSorter.Field.ETA, true),
   204                 new IssueSorter.Criteria(IssueSorter.Field.UPDATED, false)
   205         ));
   206         detailView.updateDetails(issues, version);
   208         return forwardView(req, viewModel, "project-details");
   209     }
   211     @RequestMapping(requestPath = "versions", method = HttpMethod.GET)
   212     public ResponseType versions(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException, SQLException {
   213         final var viewModel = new VersionsView();
   214         populate(viewModel, req, dao);
   215         viewModel.setVersionFilter(null);
   217         final var projectInfo = viewModel.getProjectInfo();
   218         if (projectInfo == null) {
   219             resp.sendError(HttpServletResponse.SC_NOT_FOUND, "No project selected.");
   220             return ResponseType.NONE;
   221         }
   223         final var issueDao = dao.getIssueDao();
   224         final var issues = issueDao.list(projectInfo.getProject());
   225         for (var issue : issues) issueDao.joinVersionInformation(issue);
   226         viewModel.update(projectInfo.getVersions(), issues);
   228         return forwardView(req, viewModel, "versions");
   229     }
   231     @RequestMapping(requestPath = "versions/edit", method = HttpMethod.GET)
   232     public ResponseType editVersion(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException, SQLException {
   233         final var viewModel = new VersionEditView();
   234         populate(viewModel, req, dao);
   236         if (viewModel.getProjectInfo() == null) {
   237             resp.sendError(HttpServletResponse.SC_NOT_FOUND, "No project selected.");
   238             return ResponseType.NONE;
   239         }
   241         if (viewModel.getVersionFilter() == null) {
   242             final var version = new Version(-1);
   243             version.setProject(viewModel.getProjectInfo().getProject());
   244             viewModel.setVersion(version);
   245         } else {
   246             viewModel.setVersion(viewModel.getVersionFilter());
   247         }
   249         return forwardView(req, viewModel, "version-form");
   250     }
   252     @RequestMapping(requestPath = "versions/commit", method = HttpMethod.POST)
   253     public ResponseType commitVersion(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws SQLException {
   255         var version = new Version(-1);
   256         try {
   257             version = new Version(getParameter(req, Integer.class, "id").orElseThrow());
   258             version.setProject(new Project(getParameter(req, Integer.class, "pid").orElseThrow()));
   259             version.setName(getParameter(req, String.class, "name").orElseThrow());
   260             getParameter(req, Integer.class, "ordinal").ifPresent(version::setOrdinal);
   261             version.setStatus(VersionStatus.valueOf(getParameter(req, String.class, "status").orElseThrow()));
   262             dao.getVersionDao().saveOrUpdate(version);
   264             // specifying the pid parameter will purposely reset the session selected version!
   265             setRedirectLocation(req, "./projects/versions?pid=" + version.getProject().getId());
   266             setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
   267         } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) {
   268             LOG.warn("Form validation failure: {}", ex.getMessage());
   269             LOG.debug("Details:", ex);
   270             final var viewModel = new VersionEditView();
   271             populate(viewModel, req, dao);
   272             viewModel.setVersion(version);
   273             // TODO: set Error Text
   274             return forwardView(req, viewModel, "version-form");
   275         }
   277         return ResponseType.HTML;
   278     }
   280     private void configure(IssueEditView viewModel, Issue issue, DataAccessObjects dao) throws SQLException {
   281         issue.setProject(viewModel.getProjectInfo().getProject());
   282         viewModel.setIssue(issue);
   283         viewModel.configureVersionSelectors(viewModel.getProjectInfo().getVersions());
   284         viewModel.setUsers(dao.getUserDao().list());
   285         if (issue.getId() >= 0) {
   286             viewModel.setComments(dao.getIssueDao().listComments(issue));
   287         }
   288     }
   290     @RequestMapping(requestPath = "issues/edit", method = HttpMethod.GET)
   291     public ResponseType editIssue(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws SQLException {
   292         final var viewModel = new IssueEditView();
   293         populate(viewModel, req, dao);
   295         final var issueParam = getParameter(req, Integer.class, "issue");
   296         if (issueParam.isPresent()) {
   297             final var issueDao = dao.getIssueDao();
   298             final var issue = issueDao.find(issueParam.get());
   299             issueDao.joinVersionInformation(issue);
   300             req.getSession().setAttribute(SESSION_ATTR_SELECTED_PROJECT, issue.getProject().getId());
   301             configure(viewModel, issue, dao);
   302         } else {
   303             configure(viewModel, new Issue(-1), dao);
   304         }
   306         return forwardView(req, viewModel, "issue-form");
   307     }
   309     @RequestMapping(requestPath = "issues/commit", method = HttpMethod.POST)
   310     public ResponseType commitIssue(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
   311         Issue issue = new Issue(-1);
   312         try {
   313             issue = new Issue(getParameter(req, Integer.class, "id").orElseThrow());
   314             issue.setProject(new Project(getParameter(req, Integer.class, "pid").orElseThrow()));
   315             getParameter(req, String.class, "category").map(IssueCategory::valueOf).ifPresent(issue::setCategory);
   316             getParameter(req, String.class, "status").map(IssueStatus::valueOf).ifPresent(issue::setStatus);
   317             issue.setSubject(getParameter(req, String.class, "subject").orElseThrow());
   318             getParameter(req, Integer.class, "assignee").map(
   319                     userid -> userid >= 0 ? new User(userid) : null
   320             ).ifPresent(issue::setAssignee);
   321             getParameter(req, String.class, "description").ifPresent(issue::setDescription);
   322             getParameter(req, Date.class, "eta").ifPresent(issue::setEta);
   324             getParameter(req, Integer[].class, "affected")
   325                     .map(Stream::of)
   326                     .map(stream ->
   327                             stream.map(Version::new).collect(Collectors.toList())
   328                     ).ifPresent(issue::setAffectedVersions);
   329             getParameter(req, Integer[].class, "resolved")
   330                     .map(Stream::of)
   331                     .map(stream ->
   332                             stream.map(Version::new).collect(Collectors.toList())
   333                     ).ifPresent(issue::setResolvedVersions);
   335             dao.getIssueDao().saveOrUpdate(issue);
   337             // specifying the issue parameter keeps the edited issue as menu item
   338             setRedirectLocation(req, "./projects/view?pid=" + issue.getProject().getId());
   339             setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
   341             return ResponseType.HTML;
   342         } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) {
   343             // TODO: set request attribute with error text
   344             LOG.warn("Form validation failure: {}", ex.getMessage());
   345             LOG.debug("Details:", ex);
   346             final var viewModel = new IssueEditView();
   347             populate(viewModel, req, dao);
   348             configure(viewModel, issue, dao);
   349             // TODO: set Error Text
   350             return forwardView(req, viewModel, "issue-form");
   351         }
   352     }
   354     @RequestMapping(requestPath = "issues/comment", method = HttpMethod.POST)
   355     public ResponseType commentIssue(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws SQLException, IOException {
   356         final var issueIdParam = getParameter(req, Integer.class, "issueid");
   357         if (issueIdParam.isEmpty()) {
   358             resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Detected manipulated form.");
   359             return ResponseType.NONE;
   360         }
   361         final var issue = new Issue(issueIdParam.get());
   362         try {
   363             final var issueComment = new IssueComment(getParameter(req, Integer.class, "commentid").orElse(-1), issue);
   364             issueComment.setComment(getParameter(req, String.class, "comment").orElse(""));
   366             if (issueComment.getComment().isBlank()) {
   367                 throw new IllegalArgumentException("comment.null");
   368             }
   370             LOG.debug("User {} is commenting on issue #{}", req.getRemoteUser(), issue.getId());
   371             if (req.getRemoteUser() != null) {
   372                 dao.getUserDao().findByUsername(req.getRemoteUser()).ifPresent(issueComment::setAuthor);
   373             }
   375             dao.getIssueDao().saveComment(issueComment);
   377             // specifying the issue parameter keeps the edited issue as menu item
   378             setRedirectLocation(req, "./projects/issues/edit?issue=" + issue.getId());
   379             setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
   381             return ResponseType.HTML;
   382         } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) {
   383             // TODO: set request attribute with error text
   384             LOG.warn("Form validation failure: {}", ex.getMessage());
   385             LOG.debug("Details:", ex);
   386             final var viewModel = new IssueEditView();
   387             populate(viewModel, req, dao);
   388             configure(viewModel, issue, dao);
   389             // TODO: set Error Text
   390             return forwardView(req, viewModel, "issue-form");
   391         }
   392     }
   393 }

mercurial