Sat, 29 Aug 2020 11:51:12 +0200
fixes wrong redirect url after committing an issue
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.List;
47 import java.util.NoSuchElementException;
48 import java.util.Optional;
49 import java.util.stream.Collectors;
50 import java.util.stream.Stream;
52 import static de.uapcore.lightpit.Functions.fqn;
54 @WebServlet(
55 name = "ProjectsModule",
56 urlPatterns = "/projects/*"
57 )
58 public final class ProjectsModule extends AbstractLightPITServlet {
60 private static final Logger LOG = LoggerFactory.getLogger(ProjectsModule.class);
62 private static final String SESSION_ATTR_SELECTED_PROJECT = fqn(ProjectsModule.class, "selected_project");
63 private static final String SESSION_ATTR_SELECTED_VERSION = fqn(ProjectsModule.class, "selected_version");
64 private static final String PARAMETER_SELECTED_PROJECT = "pid";
65 private static final String PARAMETER_SELECTED_VERSION = "vid";
67 @Override
68 protected String getResourceBundleName() {
69 return "localization.projects";
70 }
72 private String queryParams(Project p, Version v) {
73 return String.format("pid=%d&vid=%d",
74 p == null ? -1 : p.getId(),
75 v == null ? -1 : v.getId()
76 );
77 }
79 /**
80 * Creates the navigation menu.
81 *
82 * @param req the servlet request
83 * @param viewModel the current view model
84 */
85 private void setNavigationMenu(HttpServletRequest req, ProjectView viewModel) {
86 final Project selectedProject = Optional.ofNullable(viewModel.getProjectInfo()).map(ProjectInfo::getProject).orElse(null);
88 final var navigation = new ArrayList<MenuEntry>();
90 for (ProjectInfo plistInfo : viewModel.getProjectList()) {
91 final var proj = plistInfo.getProject();
92 final var projEntry = new MenuEntry(
93 proj.getName(),
94 "projects/view?" + queryParams(proj, null)
95 );
96 navigation.add(projEntry);
97 if (proj.equals(selectedProject)) {
98 final var projInfo = viewModel.getProjectInfo();
99 projEntry.setActive(true);
101 // ****************
102 // Versions Section
103 // ****************
104 {
105 final var entry = new MenuEntry(1,
106 new ResourceKey(getResourceBundleName(), "menu.versions"),
107 "projects/view?" + queryParams(proj, null)
108 );
109 navigation.add(entry);
110 }
112 final var level2 = new ArrayList<MenuEntry>();
113 {
114 final var entry = new MenuEntry(
115 new ResourceKey(getResourceBundleName(), "filter.all"),
116 "projects/view?" + queryParams(proj, null)
117 );
118 if (viewModel.getVersionFilter() == null) entry.setActive(true);
119 level2.add(entry);
120 }
122 for (Version version : projInfo.getVersions()) {
123 final var entry = new MenuEntry(
124 version.getName(),
125 "projects/view?" + queryParams(proj, version)
126 );
127 if (version.equals(viewModel.getVersionFilter())) entry.setActive(true);
128 level2.add(entry);
129 }
131 level2.forEach(e -> e.setLevel(2));
132 navigation.addAll(level2);
133 }
134 }
136 setNavigationMenu(req, navigation);
137 }
139 private int syncParamWithSession(HttpServletRequest req, String param, String attr) {
140 final var session = req.getSession();
141 final var idParam = getParameter(req, Integer.class, param);
142 final int id;
143 if (idParam.isPresent()) {
144 id = idParam.get();
145 session.setAttribute(attr, id);
146 } else {
147 id = Optional.ofNullable(session.getAttribute(attr)).map(x->(Integer)x).orElse(-1);
148 }
149 return id;
150 }
152 private void populate(ProjectView viewModel, HttpServletRequest req, DataAccessObjects dao) throws SQLException {
153 final var projectDao = dao.getProjectDao();
154 final var versionDao = dao.getVersionDao();
156 projectDao.list().stream().map(ProjectInfo::new).forEach(viewModel.getProjectList()::add);
158 // Select Project
159 final int pid = syncParamWithSession(req, PARAMETER_SELECTED_PROJECT, SESSION_ATTR_SELECTED_PROJECT);
160 if (pid >= 0) {
161 final var project = projectDao.find(pid);
162 final var info = new ProjectInfo(project);
163 info.setVersions(versionDao.list(project));
164 info.setIssueSummary(projectDao.getIssueSummary(project));
165 viewModel.setProjectInfo(info);
166 }
168 // Select Version
169 final int vid = syncParamWithSession(req, PARAMETER_SELECTED_VERSION, SESSION_ATTR_SELECTED_VERSION);
170 if (vid >= 0) {
171 viewModel.setVersionFilter(versionDao.find(vid));
172 }
173 }
175 private ResponseType forwardView(HttpServletRequest req, ProjectView viewModel, String name) {
176 setViewModel(req, viewModel);
177 setContentPage(req, name);
178 setStylesheet(req, "projects");
179 setNavigationMenu(req, viewModel);
180 return ResponseType.HTML;
181 }
183 @RequestMapping(method = HttpMethod.GET)
184 public ResponseType index(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
185 final var viewModel = new ProjectView();
186 populate(viewModel, req, dao);
188 final var projectDao = dao.getProjectDao();
189 final var versionDao = dao.getVersionDao();
191 for (var info : viewModel.getProjectList()) {
192 info.setVersions(versionDao.list(info.getProject()));
193 info.setIssueSummary(projectDao.getIssueSummary(info.getProject()));
194 }
196 return forwardView(req, viewModel, "projects");
197 }
199 private void configure(ProjectEditView viewModel, Project project, DataAccessObjects dao) throws SQLException {
200 viewModel.setProject(project);
201 viewModel.setUsers(dao.getUserDao().list());
202 }
204 @RequestMapping(requestPath = "edit", method = HttpMethod.GET)
205 public ResponseType edit(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
206 final var viewModel = new ProjectEditView();
207 populate(viewModel, req, dao);
209 final var project = Optional.ofNullable(viewModel.getProjectInfo())
210 .map(ProjectInfo::getProject)
211 .orElse(new Project(-1));
212 configure(viewModel, project, dao);
214 return forwardView(req, viewModel, "project-form");
215 }
217 @RequestMapping(requestPath = "commit", method = HttpMethod.POST)
218 public ResponseType commit(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
220 Project project = new Project(-1);
221 try {
222 project = new Project(getParameter(req, Integer.class, "pid").orElseThrow());
223 project.setName(getParameter(req, String.class, "name").orElseThrow());
224 getParameter(req, String.class, "description").ifPresent(project::setDescription);
225 getParameter(req, String.class, "repoUrl").ifPresent(project::setRepoUrl);
226 getParameter(req, Integer.class, "owner").map(
227 ownerId -> ownerId >= 0 ? new User(ownerId) : null
228 ).ifPresent(project::setOwner);
230 dao.getProjectDao().saveOrUpdate(project);
232 setRedirectLocation(req, "./projects/view?pid="+project.getId());
233 setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
234 LOG.debug("Successfully updated project {}", project.getName());
236 return ResponseType.HTML;
237 } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) {
238 LOG.warn("Form validation failure: {}", ex.getMessage());
239 LOG.debug("Details:", ex);
240 final var viewModel = new ProjectEditView();
241 populate(viewModel, req, dao);
242 configure(viewModel, project, dao);
243 // TODO: error text
244 return forwardView(req, viewModel, "project-form");
245 }
246 }
248 @RequestMapping(requestPath = "view", method = HttpMethod.GET)
249 public ResponseType view(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws SQLException, IOException {
250 final var viewModel = new ProjectDetailsView();
251 populate(viewModel, req, dao);
253 if (viewModel.getProjectInfo() == null) {
254 resp.sendError(HttpServletResponse.SC_NOT_FOUND, "No project selected.");
255 return ResponseType.NONE;
256 }
258 final var issueDao = dao.getIssueDao();
260 final var project = viewModel.getProjectInfo().getProject();
262 final var detailView = viewModel.getProjectDetails();
263 final var issues = issueDao.list(project);
264 for (var issue : issues) issueDao.joinVersionInformation(issue);
265 detailView.setIssues(issues);
266 if (viewModel.getVersionFilter() != null) {
267 detailView.updateVersionInfo(List.of(viewModel.getVersionFilter()));
268 } else {
269 detailView.updateVersionInfo(viewModel.getProjectInfo().getVersions());
270 }
272 return forwardView(req, viewModel, "project-details");
273 }
275 @RequestMapping(requestPath = "versions/edit", method = HttpMethod.GET)
276 public ResponseType editVersion(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
277 final var viewModel = new VersionEditView();
278 populate(viewModel, req, dao);
280 if (viewModel.getVersionFilter() == null) {
281 viewModel.setVersion(new Version(-1));
282 } else {
283 viewModel.setVersion(viewModel.getVersionFilter());
284 }
286 return forwardView(req, viewModel, "version-form");
287 }
289 @RequestMapping(requestPath = "versions/commit", method = HttpMethod.POST)
290 public ResponseType commitVersion(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws SQLException {
292 var version = new Version(-1);
293 try {
294 version = new Version(getParameter(req, Integer.class, "id").orElseThrow());
295 version.setProject(new Project(getParameter(req, Integer.class, "pid").orElseThrow()));
296 version.setName(getParameter(req, String.class, "name").orElseThrow());
297 getParameter(req, Integer.class, "ordinal").ifPresent(version::setOrdinal);
298 version.setStatus(VersionStatus.valueOf(getParameter(req, String.class, "status").orElseThrow()));
299 dao.getVersionDao().saveOrUpdate(version);
301 // specifying the pid parameter will purposely reset the session selected version!
302 setRedirectLocation(req, "./projects/view?pid=" + version.getProject().getId());
303 setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
304 } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) {
305 LOG.warn("Form validation failure: {}", ex.getMessage());
306 LOG.debug("Details:", ex);
307 final var viewModel = new VersionEditView();
308 populate(viewModel, req, dao);
309 viewModel.setVersion(version);
310 // TODO: set Error Text
311 return forwardView(req, viewModel, "version-form");
312 }
314 return ResponseType.HTML;
315 }
317 private void configure(IssueEditView viewModel, Issue issue, DataAccessObjects dao) throws SQLException {
318 issue.setProject(viewModel.getProjectInfo().getProject());
319 viewModel.setIssue(issue);
320 viewModel.configureVersionSelectors(viewModel.getProjectInfo().getVersions());
321 viewModel.setUsers(dao.getUserDao().list());
322 }
324 @RequestMapping(requestPath = "issues/edit", method = HttpMethod.GET)
325 public ResponseType editIssue(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws SQLException {
326 final var viewModel = new IssueEditView();
328 final var issueParam = getParameter(req, Integer.class, "issue");
329 if (issueParam.isPresent()) {
330 final var issue = dao.getIssueDao().find(issueParam.get());
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 }