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

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

mercurial