Thu, 08 Oct 2020 20:16:47 +0200
adds versions overview
includes major refactoring of side menu generation
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.NoSuchElementException;
46 import java.util.Optional;
47 import java.util.stream.Collectors;
48 import java.util.stream.Stream;
50 import static de.uapcore.lightpit.Functions.fqn;
52 @WebServlet(
53 name = "ProjectsModule",
54 urlPatterns = "/projects/*"
55 )
56 public final class ProjectsModule extends AbstractLightPITServlet {
58 private static final Logger LOG = LoggerFactory.getLogger(ProjectsModule.class);
60 private static final String SESSION_ATTR_SELECTED_PROJECT = fqn(ProjectsModule.class, "selected_project");
61 private static final String SESSION_ATTR_SELECTED_VERSION = fqn(ProjectsModule.class, "selected_version");
62 private static final String PARAMETER_SELECTED_PROJECT = "pid";
63 private static final String PARAMETER_SELECTED_VERSION = "vid";
65 @Override
66 protected String getResourceBundleName() {
67 return "localization.projects";
68 }
70 private int syncParamWithSession(HttpServletRequest req, String param, String attr) {
71 final var session = req.getSession();
72 final var idParam = getParameter(req, Integer.class, param);
73 final int id;
74 if (idParam.isPresent()) {
75 id = idParam.get();
76 session.setAttribute(attr, id);
77 } else {
78 id = Optional.ofNullable(session.getAttribute(attr)).map(x->(Integer)x).orElse(-1);
79 }
80 return id;
81 }
83 private void populate(ProjectView viewModel, HttpServletRequest req, DataAccessObjects dao) throws SQLException {
84 final var projectDao = dao.getProjectDao();
85 final var versionDao = dao.getVersionDao();
87 projectDao.list().stream().map(ProjectInfo::new).forEach(viewModel.getProjectList()::add);
89 // Select Project
90 final int pid = syncParamWithSession(req, PARAMETER_SELECTED_PROJECT, SESSION_ATTR_SELECTED_PROJECT);
91 if (pid >= 0) {
92 final var project = projectDao.find(pid);
93 if (project == null) {
94 req.setAttribute(SESSION_ATTR_SELECTED_PROJECT, -1);
95 } else {
96 final var info = new ProjectInfo(project);
97 info.setVersions(versionDao.list(project));
98 info.setIssueSummary(projectDao.getIssueSummary(project));
99 viewModel.setProjectInfo(info);
100 }
101 }
103 // Select Version
104 final int vid = syncParamWithSession(req, PARAMETER_SELECTED_VERSION, SESSION_ATTR_SELECTED_VERSION);
105 if (vid >= 0) {
106 viewModel.setVersionFilter(versionDao.find(vid));
107 }
108 }
110 private ResponseType forwardView(HttpServletRequest req, ProjectView viewModel, String name) {
111 setViewModel(req, viewModel);
112 setContentPage(req, name);
113 setStylesheet(req, "projects");
114 setNavigationMenu(req, "project-navmenu");
115 return ResponseType.HTML;
116 }
118 @RequestMapping(method = HttpMethod.GET)
119 public ResponseType index(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
120 final var viewModel = new ProjectView();
121 populate(viewModel, req, dao);
123 final var projectDao = dao.getProjectDao();
124 final var versionDao = dao.getVersionDao();
126 for (var info : viewModel.getProjectList()) {
127 info.setVersions(versionDao.list(info.getProject()));
128 info.setIssueSummary(projectDao.getIssueSummary(info.getProject()));
129 }
131 return forwardView(req, viewModel, "projects");
132 }
134 private void configure(ProjectEditView viewModel, Project project, DataAccessObjects dao) throws SQLException {
135 viewModel.setProject(project);
136 viewModel.setUsers(dao.getUserDao().list());
137 }
139 @RequestMapping(requestPath = "edit", method = HttpMethod.GET)
140 public ResponseType edit(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
141 final var viewModel = new ProjectEditView();
142 populate(viewModel, req, dao);
144 final var project = Optional.ofNullable(viewModel.getProjectInfo())
145 .map(ProjectInfo::getProject)
146 .orElse(new Project(-1));
147 configure(viewModel, project, dao);
149 return forwardView(req, viewModel, "project-form");
150 }
152 @RequestMapping(requestPath = "commit", method = HttpMethod.POST)
153 public ResponseType commit(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
155 Project project = new Project(-1);
156 try {
157 project = new Project(getParameter(req, Integer.class, "pid").orElseThrow());
158 project.setName(getParameter(req, String.class, "name").orElseThrow());
159 getParameter(req, String.class, "description").ifPresent(project::setDescription);
160 getParameter(req, String.class, "repoUrl").ifPresent(project::setRepoUrl);
161 getParameter(req, Integer.class, "owner").map(
162 ownerId -> ownerId >= 0 ? new User(ownerId) : null
163 ).ifPresent(project::setOwner);
165 dao.getProjectDao().saveOrUpdate(project);
167 setRedirectLocation(req, "./projects/versions?pid="+project.getId());
168 setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
169 LOG.debug("Successfully updated project {}", project.getName());
171 return ResponseType.HTML;
172 } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) {
173 LOG.warn("Form validation failure: {}", ex.getMessage());
174 LOG.debug("Details:", ex);
175 final var viewModel = new ProjectEditView();
176 populate(viewModel, req, dao);
177 configure(viewModel, project, dao);
178 // TODO: error text
179 return forwardView(req, viewModel, "project-form");
180 }
181 }
183 @RequestMapping(requestPath = "view", method = HttpMethod.GET)
184 public ResponseType view(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws SQLException, IOException {
185 final var viewModel = new ProjectDetailsView();
186 populate(viewModel, req, dao);
188 if (viewModel.getProjectInfo() == null) {
189 resp.sendError(HttpServletResponse.SC_NOT_FOUND, "No project selected.");
190 return ResponseType.NONE;
191 }
193 final var issueDao = dao.getIssueDao();
195 final var version = viewModel.getVersionFilter();
197 final var detailView = viewModel.getProjectDetails();
198 final var issues = issueDao.list(version);
199 for (var issue : issues) issueDao.joinVersionInformation(issue);
200 detailView.updateDetails(issues, version);
202 return forwardView(req, viewModel, "project-details");
203 }
205 @RequestMapping(requestPath = "versions", method = HttpMethod.GET)
206 public ResponseType versions(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException, SQLException {
207 final var viewModel = new VersionsView();
208 populate(viewModel, req, dao);
209 viewModel.setVersionFilter(null);
211 final var projectInfo = viewModel.getProjectInfo();
212 if (projectInfo == null) {
213 resp.sendError(HttpServletResponse.SC_NOT_FOUND, "No project selected.");
214 return ResponseType.NONE;
215 }
217 final var issueDao = dao.getIssueDao();
218 final var issues = issueDao.list(projectInfo.getProject());
219 for (var issue : issues) issueDao.joinVersionInformation(issue);
220 viewModel.update(projectInfo.getVersions(), issues);
222 return forwardView(req, viewModel, "versions");
223 }
225 @RequestMapping(requestPath = "versions/edit", method = HttpMethod.GET)
226 public ResponseType editVersion(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
227 final var viewModel = new VersionEditView();
228 populate(viewModel, req, dao);
230 if (viewModel.getVersionFilter() == null) {
231 viewModel.setVersion(new Version(-1));
232 } else {
233 viewModel.setVersion(viewModel.getVersionFilter());
234 }
236 return forwardView(req, viewModel, "version-form");
237 }
239 @RequestMapping(requestPath = "versions/commit", method = HttpMethod.POST)
240 public ResponseType commitVersion(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws SQLException {
242 var version = new Version(-1);
243 try {
244 version = new Version(getParameter(req, Integer.class, "id").orElseThrow());
245 version.setProject(new Project(getParameter(req, Integer.class, "pid").orElseThrow()));
246 version.setName(getParameter(req, String.class, "name").orElseThrow());
247 getParameter(req, Integer.class, "ordinal").ifPresent(version::setOrdinal);
248 version.setStatus(VersionStatus.valueOf(getParameter(req, String.class, "status").orElseThrow()));
249 dao.getVersionDao().saveOrUpdate(version);
251 // specifying the pid parameter will purposely reset the session selected version!
252 setRedirectLocation(req, "./projects/versions?pid=" + version.getProject().getId());
253 setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
254 } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) {
255 LOG.warn("Form validation failure: {}", ex.getMessage());
256 LOG.debug("Details:", ex);
257 final var viewModel = new VersionEditView();
258 populate(viewModel, req, dao);
259 viewModel.setVersion(version);
260 // TODO: set Error Text
261 return forwardView(req, viewModel, "version-form");
262 }
264 return ResponseType.HTML;
265 }
267 private void configure(IssueEditView viewModel, Issue issue, DataAccessObjects dao) throws SQLException {
268 issue.setProject(viewModel.getProjectInfo().getProject());
269 viewModel.setIssue(issue);
270 viewModel.configureVersionSelectors(viewModel.getProjectInfo().getVersions());
271 viewModel.setUsers(dao.getUserDao().list());
272 }
274 @RequestMapping(requestPath = "issues/edit", method = HttpMethod.GET)
275 public ResponseType editIssue(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws SQLException {
276 final var viewModel = new IssueEditView();
278 final var issueParam = getParameter(req, Integer.class, "issue");
279 if (issueParam.isPresent()) {
280 final var issueDao = dao.getIssueDao();
281 final var issue = issueDao.find(issueParam.get());
282 issueDao.joinVersionInformation(issue);
283 req.getSession().setAttribute(SESSION_ATTR_SELECTED_PROJECT, issue.getProject().getId());
284 populate(viewModel, req, dao);
285 configure(viewModel, issue, dao);
286 } else {
287 populate(viewModel, req, dao);
288 configure(viewModel, new Issue(-1), dao);
289 }
291 return forwardView(req, viewModel, "issue-form");
292 }
294 @RequestMapping(requestPath = "issues/commit", method = HttpMethod.POST)
295 public ResponseType commitIssue(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws SQLException {
296 Issue issue = new Issue(-1);
297 try {
298 issue = new Issue(getParameter(req, Integer.class, "id").orElseThrow());
299 issue.setProject(new Project(getParameter(req, Integer.class, "pid").orElseThrow()));
300 getParameter(req, String.class, "category").map(IssueCategory::valueOf).ifPresent(issue::setCategory);
301 getParameter(req, String.class, "status").map(IssueStatus::valueOf).ifPresent(issue::setStatus);
302 issue.setSubject(getParameter(req, String.class, "subject").orElseThrow());
303 getParameter(req, Integer.class, "assignee").map(
304 userid -> userid >= 0 ? new User(userid) : null
305 ).ifPresent(issue::setAssignee);
306 getParameter(req, String.class, "description").ifPresent(issue::setDescription);
307 getParameter(req, Date.class, "eta").ifPresent(issue::setEta);
309 getParameter(req, Integer[].class, "affected")
310 .map(Stream::of)
311 .map(stream ->
312 stream.map(Version::new).collect(Collectors.toList())
313 ).ifPresent(issue::setAffectedVersions);
314 getParameter(req, Integer[].class, "resolved")
315 .map(Stream::of)
316 .map(stream ->
317 stream.map(Version::new).collect(Collectors.toList())
318 ).ifPresent(issue::setResolvedVersions);
320 dao.getIssueDao().saveOrUpdate(issue);
322 // specifying the issue parameter keeps the edited issue as menu item
323 setRedirectLocation(req, "./projects/view?pid=" + issue.getProject().getId());
324 setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
325 } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) {
326 // TODO: set request attribute with error text
327 LOG.warn("Form validation failure: {}", ex.getMessage());
328 LOG.debug("Details:", ex);
329 final var viewModel = new IssueEditView();
330 configure(viewModel, issue, dao);
331 // TODO: set Error Text
332 return forwardView(req, viewModel, "issue-form");
333 }
335 return ResponseType.HTML;
336 }
337 }