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

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

mercurial