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

Sat, 29 Aug 2020 17:32:59 +0200

author
Mike Becker <universe@uap-core.de>
date
Sat, 29 Aug 2020 17:32:59 +0200
changeset 107
b5f740a87af4
parent 105
250c5cbb8276
child 109
2e0669e814ff
permissions
-rw-r--r--

fixes misbehavior when a non-existing project ID is selected

     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 org.slf4j.Logger;
    37 import org.slf4j.LoggerFactory;
    39 import javax.servlet.annotation.WebServlet;
    40 import javax.servlet.http.HttpServletRequest;
    41 import javax.servlet.http.HttpServletResponse;
    42 import java.io.IOException;
    43 import java.sql.Date;
    44 import java.sql.SQLException;
    45 import java.util.ArrayList;
    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 String queryParams(Project p, Version v) {
    72         return String.format("pid=%d&vid=%d",
    73                 p == null ? -1 : p.getId(),
    74                 v == null ? -1 : v.getId()
    75         );
    76     }
    78     /**
    79      * Creates the navigation menu.
    80      *
    81      * @param req the servlet request
    82      * @param viewModel the current view model
    83      */
    84     private void setNavigationMenu(HttpServletRequest req, ProjectView viewModel) {
    85         final Project selectedProject = Optional.ofNullable(viewModel.getProjectInfo()).map(ProjectInfo::getProject).orElse(null);
    87         final var navigation = new ArrayList<MenuEntry>();
    89         for (ProjectInfo plistInfo : viewModel.getProjectList()) {
    90             final var proj = plistInfo.getProject();
    91             final var projEntry = new MenuEntry(
    92                     proj.getName(),
    93                     "projects/view?" + queryParams(proj, null)
    94             );
    95             navigation.add(projEntry);
    96             if (proj.equals(selectedProject)) {
    97                 final var projInfo = viewModel.getProjectInfo();
    98                 projEntry.setActive(true);
   100                 // ****************
   101                 // Versions Section
   102                 // ****************
   103                 {
   104                     final var entry = new MenuEntry(1,
   105                             new ResourceKey(getResourceBundleName(), "menu.versions"),
   106                             "projects/view?" + queryParams(proj, null)
   107                     );
   108                     navigation.add(entry);
   109                 }
   111                 final var level2 = new ArrayList<MenuEntry>();
   112                 {
   113                     final var entry = new MenuEntry(
   114                             new ResourceKey(getResourceBundleName(), "filter.none"),
   115                             "projects/view?" + queryParams(proj, null)
   116                     );
   117                     if (viewModel.getVersionFilter() == null) entry.setActive(true);
   118                     level2.add(entry);
   119                 }
   121                 for (Version version : projInfo.getVersions()) {
   122                     final var entry = new MenuEntry(
   123                             version.getName(),
   124                             "projects/view?" + queryParams(proj, version)
   125                     );
   126                     if (version.equals(viewModel.getVersionFilter())) entry.setActive(true);
   127                     level2.add(entry);
   128                 }
   130                 level2.forEach(e -> e.setLevel(2));
   131                 navigation.addAll(level2);
   132             }
   133         }
   135         setNavigationMenu(req, navigation);
   136     }
   138     private int syncParamWithSession(HttpServletRequest req, String param, String attr) {
   139         final var session = req.getSession();
   140         final var idParam = getParameter(req, Integer.class, param);
   141         final int id;
   142         if (idParam.isPresent()) {
   143             id = idParam.get();
   144             session.setAttribute(attr, id);
   145         } else {
   146             id = Optional.ofNullable(session.getAttribute(attr)).map(x->(Integer)x).orElse(-1);
   147         }
   148         return id;
   149     }
   151     private void populate(ProjectView viewModel, HttpServletRequest req, DataAccessObjects dao) throws SQLException {
   152         final var projectDao = dao.getProjectDao();
   153         final var versionDao = dao.getVersionDao();
   155         projectDao.list().stream().map(ProjectInfo::new).forEach(viewModel.getProjectList()::add);
   157         // Select Project
   158         final int pid = syncParamWithSession(req, PARAMETER_SELECTED_PROJECT, SESSION_ATTR_SELECTED_PROJECT);
   159         if (pid >= 0) {
   160             final var project = projectDao.find(pid);
   161             if (project == null) {
   162                 req.setAttribute(SESSION_ATTR_SELECTED_PROJECT, -1);
   163             } else {
   164                 final var info = new ProjectInfo(project);
   165                 info.setVersions(versionDao.list(project));
   166                 info.setIssueSummary(projectDao.getIssueSummary(project));
   167                 viewModel.setProjectInfo(info);
   168             }
   169         }
   171         // Select Version
   172         final int vid = syncParamWithSession(req, PARAMETER_SELECTED_VERSION, SESSION_ATTR_SELECTED_VERSION);
   173         if (vid >= 0) {
   174             viewModel.setVersionFilter(versionDao.find(vid));
   175         }
   176     }
   178     private ResponseType forwardView(HttpServletRequest req, ProjectView viewModel, String name) {
   179         setViewModel(req, viewModel);
   180         setContentPage(req, name);
   181         setStylesheet(req, "projects");
   182         setNavigationMenu(req, viewModel);
   183         return ResponseType.HTML;
   184     }
   186     @RequestMapping(method = HttpMethod.GET)
   187     public ResponseType index(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
   188         final var viewModel = new ProjectView();
   189         populate(viewModel, req, dao);
   191         final var projectDao = dao.getProjectDao();
   192         final var versionDao = dao.getVersionDao();
   194         for (var info : viewModel.getProjectList()) {
   195             info.setVersions(versionDao.list(info.getProject()));
   196             info.setIssueSummary(projectDao.getIssueSummary(info.getProject()));
   197         }
   199         return forwardView(req, viewModel, "projects");
   200     }
   202     private void configure(ProjectEditView viewModel, Project project, DataAccessObjects dao) throws SQLException {
   203         viewModel.setProject(project);
   204         viewModel.setUsers(dao.getUserDao().list());
   205     }
   207     @RequestMapping(requestPath = "edit", method = HttpMethod.GET)
   208     public ResponseType edit(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
   209         final var viewModel = new ProjectEditView();
   210         populate(viewModel, req, dao);
   212         final var project = Optional.ofNullable(viewModel.getProjectInfo())
   213                 .map(ProjectInfo::getProject)
   214                 .orElse(new Project(-1));
   215         configure(viewModel, project, dao);
   217         return forwardView(req, viewModel, "project-form");
   218     }
   220     @RequestMapping(requestPath = "commit", method = HttpMethod.POST)
   221     public ResponseType commit(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
   223         Project project = new Project(-1);
   224         try {
   225             project = new Project(getParameter(req, Integer.class, "pid").orElseThrow());
   226             project.setName(getParameter(req, String.class, "name").orElseThrow());
   227             getParameter(req, String.class, "description").ifPresent(project::setDescription);
   228             getParameter(req, String.class, "repoUrl").ifPresent(project::setRepoUrl);
   229             getParameter(req, Integer.class, "owner").map(
   230                     ownerId -> ownerId >= 0 ? new User(ownerId) : null
   231             ).ifPresent(project::setOwner);
   233             dao.getProjectDao().saveOrUpdate(project);
   235             setRedirectLocation(req, "./projects/view?pid="+project.getId());
   236             setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
   237             LOG.debug("Successfully updated project {}", project.getName());
   239             return ResponseType.HTML;
   240         } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) {
   241             LOG.warn("Form validation failure: {}", ex.getMessage());
   242             LOG.debug("Details:", ex);
   243             final var viewModel = new ProjectEditView();
   244             populate(viewModel, req, dao);
   245             configure(viewModel, project, dao);
   246             // TODO: error text
   247             return forwardView(req, viewModel, "project-form");
   248         }
   249     }
   251     @RequestMapping(requestPath = "view", method = HttpMethod.GET)
   252     public ResponseType view(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws SQLException, IOException {
   253         final var viewModel = new ProjectDetailsView();
   254         populate(viewModel, req, dao);
   256         if (viewModel.getProjectInfo() == null) {
   257             resp.sendError(HttpServletResponse.SC_NOT_FOUND, "No project selected.");
   258             return ResponseType.NONE;
   259         }
   261         final var issueDao = dao.getIssueDao();
   263         final var version = viewModel.getVersionFilter();
   265         final var detailView = viewModel.getProjectDetails();
   266         final var issues = issueDao.list(version);
   267         for (var issue : issues) issueDao.joinVersionInformation(issue);
   268         detailView.updateDetails(issues, version);
   270         return forwardView(req, viewModel, "project-details");
   271     }
   273     @RequestMapping(requestPath = "versions/edit", method = HttpMethod.GET)
   274     public ResponseType editVersion(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
   275         final var viewModel = new VersionEditView();
   276         populate(viewModel, req, dao);
   278         if (viewModel.getVersionFilter() == null) {
   279             viewModel.setVersion(new Version(-1));
   280         } else {
   281             viewModel.setVersion(viewModel.getVersionFilter());
   282         }
   284         return forwardView(req, viewModel, "version-form");
   285     }
   287     @RequestMapping(requestPath = "versions/commit", method = HttpMethod.POST)
   288     public ResponseType commitVersion(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws SQLException {
   290         var version = new Version(-1);
   291         try {
   292             version = new Version(getParameter(req, Integer.class, "id").orElseThrow());
   293             version.setProject(new Project(getParameter(req, Integer.class, "pid").orElseThrow()));
   294             version.setName(getParameter(req, String.class, "name").orElseThrow());
   295             getParameter(req, Integer.class, "ordinal").ifPresent(version::setOrdinal);
   296             version.setStatus(VersionStatus.valueOf(getParameter(req, String.class, "status").orElseThrow()));
   297             dao.getVersionDao().saveOrUpdate(version);
   299             // specifying the pid parameter will purposely reset the session selected version!
   300             setRedirectLocation(req, "./projects/view?pid=" + version.getProject().getId());
   301             setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
   302         } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) {
   303             LOG.warn("Form validation failure: {}", ex.getMessage());
   304             LOG.debug("Details:", ex);
   305             final var viewModel = new VersionEditView();
   306             populate(viewModel, req, dao);
   307             viewModel.setVersion(version);
   308             // TODO: set Error Text
   309             return forwardView(req, viewModel, "version-form");
   310         }
   312         return ResponseType.HTML;
   313     }
   315     private void configure(IssueEditView viewModel, Issue issue, DataAccessObjects dao) throws SQLException {
   316         issue.setProject(viewModel.getProjectInfo().getProject());
   317         viewModel.setIssue(issue);
   318         viewModel.configureVersionSelectors(viewModel.getProjectInfo().getVersions());
   319         viewModel.setUsers(dao.getUserDao().list());
   320     }
   322     @RequestMapping(requestPath = "issues/edit", method = HttpMethod.GET)
   323     public ResponseType editIssue(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws SQLException {
   324         final var viewModel = new IssueEditView();
   326         final var issueParam = getParameter(req, Integer.class, "issue");
   327         if (issueParam.isPresent()) {
   328             final var issueDao = dao.getIssueDao();
   329             final var issue = issueDao.find(issueParam.get());
   330             issueDao.joinVersionInformation(issue);
   331             req.getSession().setAttribute(SESSION_ATTR_SELECTED_PROJECT, issue.getProject().getId());
   332             populate(viewModel, req, dao);
   333             configure(viewModel, issue, dao);
   334         } else {
   335             populate(viewModel, req, dao);
   336             configure(viewModel, new Issue(-1), dao);
   337         }
   339         return forwardView(req, viewModel, "issue-form");
   340     }
   342     @RequestMapping(requestPath = "issues/commit", method = HttpMethod.POST)
   343     public ResponseType commitIssue(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws SQLException {
   344         Issue issue = new Issue(-1);
   345         try {
   346             issue = new Issue(getParameter(req, Integer.class, "id").orElseThrow());
   347             issue.setProject(new Project(getParameter(req, Integer.class, "pid").orElseThrow()));
   348             getParameter(req, String.class, "category").map(IssueCategory::valueOf).ifPresent(issue::setCategory);
   349             getParameter(req, String.class, "status").map(IssueStatus::valueOf).ifPresent(issue::setStatus);
   350             issue.setSubject(getParameter(req, String.class, "subject").orElseThrow());
   351             getParameter(req, Integer.class, "assignee").map(
   352                     userid -> userid >= 0 ? new User(userid) : null
   353             ).ifPresent(issue::setAssignee);
   354             getParameter(req, String.class, "description").ifPresent(issue::setDescription);
   355             getParameter(req, Date.class, "eta").ifPresent(issue::setEta);
   357             getParameter(req, Integer[].class, "affected")
   358                     .map(Stream::of)
   359                     .map(stream ->
   360                             stream.map(Version::new).collect(Collectors.toList())
   361                     ).ifPresent(issue::setAffectedVersions);
   362             getParameter(req, Integer[].class, "resolved")
   363                     .map(Stream::of)
   364                     .map(stream ->
   365                             stream.map(Version::new).collect(Collectors.toList())
   366                     ).ifPresent(issue::setResolvedVersions);
   368             dao.getIssueDao().saveOrUpdate(issue);
   370             // specifying the issue parameter keeps the edited issue as menu item
   371             setRedirectLocation(req, "./projects/view?pid=" + issue.getProject().getId());
   372             setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
   373         } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) {
   374             // TODO: set request attribute with error text
   375             LOG.warn("Form validation failure: {}", ex.getMessage());
   376             LOG.debug("Details:", ex);
   377             final var viewModel = new IssueEditView();
   378             configure(viewModel, issue, dao);
   379             // TODO: set Error Text
   380             return forwardView(req, viewModel, "issue-form");
   381         }
   383         return ResponseType.HTML;
   384     }
   385 }

mercurial