Thu, 15 Oct 2020 20:02:30 +0200
changes request mapping to contain project and version ID as path parameters (this removes the session storage)
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 de.uapcore.lightpit.viewmodel.util.IssueSorter;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
40 import javax.servlet.annotation.WebServlet;
41 import javax.servlet.http.HttpServletRequest;
42 import javax.servlet.http.HttpServletResponse;
43 import java.io.IOException;
44 import java.sql.Date;
45 import java.sql.SQLException;
46 import java.util.NoSuchElementException;
47 import java.util.stream.Collectors;
48 import java.util.stream.Stream;
50 @WebServlet(
51 name = "ProjectsModule",
52 urlPatterns = "/projects/*"
53 )
54 public final class ProjectsModule extends AbstractLightPITServlet {
56 private static final Logger LOG = LoggerFactory.getLogger(ProjectsModule.class);
58 @Override
59 protected String getResourceBundleName() {
60 return "localization.projects";
61 }
63 private void populate(ProjectView viewModel, PathParameters pathParameters, DataAccessObjects dao) throws SQLException {
64 final var projectDao = dao.getProjectDao();
65 final var versionDao = dao.getVersionDao();
66 final var componentDao = dao.getComponentDao();
68 projectDao.list().stream().map(ProjectInfo::new).forEach(viewModel.getProjectList()::add);
70 if (pathParameters == null)
71 return;
73 // Select Project
74 final int pid = Functions.parseIntOrZero(pathParameters.get("project"));
75 if (pid > 0) {
76 final var project = projectDao.find(pid);
77 if (project != null) {
78 final var info = new ProjectInfo(project);
79 info.setVersions(versionDao.list(project));
80 info.setComponents(componentDao.list(project));
81 info.setIssueSummary(projectDao.getIssueSummary(project));
82 viewModel.setProjectInfo(info);
83 }
84 }
86 // Select Version
87 final int vid = Functions.parseIntOrZero(pathParameters.get("version"));
88 if (vid > 0) {
89 viewModel.setVersionFilter(versionDao.find(vid));
90 }
91 // TODO: don't treat unknown == unassigned - send 404 for unknown and introduce special word for unassigned
93 // Select Component
94 final int cid = Functions.parseIntOrZero(pathParameters.get("component"));
95 if (cid > 0) {
96 viewModel.setComponentFilter(componentDao.find(cid));
97 }
99 // TODO: distinguish all/unassigned for components
100 }
102 private ResponseType forwardView(HttpServletRequest req, ProjectView viewModel, String name) {
103 setViewModel(req, viewModel);
104 setContentPage(req, name);
105 setStylesheet(req, "projects");
106 setNavigationMenu(req, "project-navmenu");
107 return ResponseType.HTML;
108 }
110 @RequestMapping(method = HttpMethod.GET)
111 public ResponseType index(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
112 final var viewModel = new ProjectView();
113 populate(viewModel, null, dao);
115 final var projectDao = dao.getProjectDao();
116 final var versionDao = dao.getVersionDao();
118 for (var info : viewModel.getProjectList()) {
119 info.setVersions(versionDao.list(info.getProject()));
120 info.setIssueSummary(projectDao.getIssueSummary(info.getProject()));
121 }
123 return forwardView(req, viewModel, "projects");
124 }
126 private void configureProjectEditor(ProjectEditView viewModel, Project project, DataAccessObjects dao) throws SQLException {
127 viewModel.setProject(project);
128 viewModel.setUsers(dao.getUserDao().list());
129 }
131 @RequestMapping(requestPath = "$project/edit", method = HttpMethod.GET)
132 public ResponseType edit(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParams, DataAccessObjects dao) throws IOException, SQLException {
133 final var viewModel = new ProjectEditView();
134 populate(viewModel, pathParams, dao);
136 if (viewModel.getProjectInfo() == null) {
137 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
138 return ResponseType.NONE;
139 }
141 configureProjectEditor(viewModel, viewModel.getProjectInfo().getProject(), dao);
142 return forwardView(req, viewModel, "project-form");
143 }
145 @RequestMapping(requestPath = "create", method = HttpMethod.GET)
146 public ResponseType create(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
147 final var viewModel = new ProjectEditView();
148 populate(viewModel, null, dao);
149 configureProjectEditor(viewModel, new Project(-1), dao);
150 return forwardView(req, viewModel, "project-form");
151 }
153 @RequestMapping(requestPath = "commit", method = HttpMethod.POST)
154 public ResponseType commit(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException {
156 try {
157 final var 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/");
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 resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
174 // TODO: implement - fix issue #21
175 return ResponseType.NONE;
176 }
177 }
179 @RequestMapping(requestPath = "$project/versions/$version", method = HttpMethod.GET)
180 public ResponseType view(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParams, DataAccessObjects dao) throws SQLException, IOException {
181 final var viewModel = new ProjectDetailsView();
182 populate(viewModel, pathParams, dao);
183 final var version = viewModel.getVersionFilter();
185 if (viewModel.getProjectInfo() == null || version == null) {
186 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
187 return ResponseType.NONE;
188 }
190 final var issueDao = dao.getIssueDao();
192 final var detailView = viewModel.getProjectDetails();
193 final var issues = issueDao.list(version);
194 for (var issue : issues) issueDao.joinVersionInformation(issue);
195 issues.sort(new IssueSorter(
196 new IssueSorter.Criteria(IssueSorter.Field.PHASE, true),
197 new IssueSorter.Criteria(IssueSorter.Field.ETA, true),
198 new IssueSorter.Criteria(IssueSorter.Field.UPDATED, false)
199 ));
200 detailView.updateDetails(issues, version);
202 return forwardView(req, viewModel, "project-details");
203 }
205 @RequestMapping(requestPath = "$project/versions/", method = HttpMethod.GET)
206 public ResponseType versions(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObjects dao) throws IOException, SQLException {
207 final var viewModel = new VersionsView();
208 populate(viewModel, pathParameters, dao);
210 final var projectInfo = viewModel.getProjectInfo();
211 if (projectInfo == null) {
212 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
213 return ResponseType.NONE;
214 }
216 final var issueDao = dao.getIssueDao();
217 final var issues = issueDao.list(projectInfo.getProject());
218 for (var issue : issues) issueDao.joinVersionInformation(issue);
219 viewModel.update(projectInfo.getVersions(), issues);
221 return forwardView(req, viewModel, "versions");
222 }
224 @RequestMapping(requestPath = "$project/versions/$version/edit", method = HttpMethod.GET)
225 public ResponseType editVersion(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObjects dao) throws IOException, SQLException {
226 final var viewModel = new VersionEditView();
227 populate(viewModel, pathParameters, dao);
229 if (viewModel.getProjectInfo() == null || viewModel.getVersionFilter() == null) {
230 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
231 return ResponseType.NONE;
232 }
234 viewModel.setVersion(viewModel.getVersionFilter());
236 return forwardView(req, viewModel, "version-form");
237 }
239 @RequestMapping(requestPath = "$project/create-version", method = HttpMethod.GET)
240 public ResponseType createVersion(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObjects dao) throws IOException, SQLException {
241 final var viewModel = new VersionEditView();
242 populate(viewModel, pathParameters, dao);
244 if (viewModel.getProjectInfo() == null) {
245 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
246 return ResponseType.NONE;
247 }
249 viewModel.setVersion(viewModel.getVersionFilter());
251 return forwardView(req, viewModel, "version-form");
252 }
254 @RequestMapping(requestPath = "commit-version", method = HttpMethod.POST)
255 public ResponseType commitVersion(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException {
257 try {
258 final var project = new Project(getParameter(req, Integer.class, "pid").orElseThrow());
259 final var version = new Version(getParameter(req, Integer.class, "id").orElseThrow());
260 version.setName(getParameter(req, String.class, "name").orElseThrow());
261 getParameter(req, Integer.class, "ordinal").ifPresent(version::setOrdinal);
262 version.setStatus(VersionStatus.valueOf(getParameter(req, String.class, "status").orElseThrow()));
263 dao.getVersionDao().saveOrUpdate(version, project);
265 // TODO: improve building the redirect location
266 setRedirectLocation(req, "./projects/" + project.getId() + "/versions/");
267 setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
268 } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) {
269 resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
270 // TODO: implement - fix issue #21
271 return ResponseType.NONE;
272 }
274 return ResponseType.HTML;
275 }
277 private void configureProjectEditor(IssueEditView viewModel, Issue issue, DataAccessObjects dao) throws SQLException {
278 issue.setProject(viewModel.getProjectInfo().getProject());
279 viewModel.setIssue(issue);
280 viewModel.configureVersionSelectors(viewModel.getProjectInfo().getVersions());
281 viewModel.setUsers(dao.getUserDao().list());
282 if (issue.getId() >= 0) {
283 viewModel.setComments(dao.getIssueDao().listComments(issue));
284 }
285 }
287 @RequestMapping(requestPath = "$project/issues/$issue/edit", method = HttpMethod.GET)
288 public ResponseType editIssue(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObjects dao) throws IOException, SQLException {
289 final var viewModel = new IssueEditView();
290 populate(viewModel, pathParameters, dao);
292 final var projectInfo = viewModel.getProjectInfo();
293 if (projectInfo == null) {
294 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
295 return ResponseType.NONE;
296 }
298 final var issueDao = dao.getIssueDao();
299 final var issue = issueDao.find(Functions.parseIntOrZero(pathParameters.get("issue")));
300 if (issue == null) {
301 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
302 return ResponseType.NONE;
303 }
305 issueDao.joinVersionInformation(issue);
306 configureProjectEditor(viewModel, issue, dao);
308 return forwardView(req, viewModel, "issue-form");
309 }
311 @RequestMapping(requestPath = "$project/create-issue", method = HttpMethod.GET)
312 public ResponseType createIssue(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObjects dao) throws IOException, SQLException {
313 final var viewModel = new IssueEditView();
314 populate(viewModel, pathParameters, dao);
316 final var projectInfo = viewModel.getProjectInfo();
317 if (projectInfo == null) {
318 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
319 return ResponseType.NONE;
320 }
322 final var issue = new Issue(-1);
323 issue.setProject(projectInfo.getProject());
324 configureProjectEditor(viewModel, issue, dao);
326 return forwardView(req, viewModel, "issue-form");
327 }
329 @RequestMapping(requestPath = "commit-issue", method = HttpMethod.POST)
330 public ResponseType commitIssue(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException {
331 try {
332 final var issue = new Issue(getParameter(req, Integer.class, "id").orElseThrow());
333 issue.setProject(new Project(getParameter(req, Integer.class, "pid").orElseThrow()));
334 getParameter(req, String.class, "category").map(IssueCategory::valueOf).ifPresent(issue::setCategory);
335 getParameter(req, String.class, "status").map(IssueStatus::valueOf).ifPresent(issue::setStatus);
336 issue.setSubject(getParameter(req, String.class, "subject").orElseThrow());
337 getParameter(req, Integer.class, "assignee").map(
338 userid -> userid >= 0 ? new User(userid) : null
339 ).ifPresent(issue::setAssignee);
340 getParameter(req, String.class, "description").ifPresent(issue::setDescription);
341 getParameter(req, Date.class, "eta").ifPresent(issue::setEta);
343 getParameter(req, Integer[].class, "affected")
344 .map(Stream::of)
345 .map(stream ->
346 stream.map(Version::new).collect(Collectors.toList())
347 ).ifPresent(issue::setAffectedVersions);
348 getParameter(req, Integer[].class, "resolved")
349 .map(Stream::of)
350 .map(stream ->
351 stream.map(Version::new).collect(Collectors.toList())
352 ).ifPresent(issue::setResolvedVersions);
354 dao.getIssueDao().saveOrUpdate(issue, issue.getProject());
356 // TODO: fix issue #14
357 setRedirectLocation(req, "./projects/" + issue.getProject().getId() + "/versions/");
358 setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
360 return ResponseType.HTML;
361 } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) {
362 resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
363 // TODO: implement - fix issue #21
364 return ResponseType.NONE;
365 }
366 }
368 @RequestMapping(requestPath = "commit-issue-comment", method = HttpMethod.POST)
369 public ResponseType commentIssue(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws SQLException, IOException {
370 final var issueIdParam = getParameter(req, Integer.class, "issueid");
371 if (issueIdParam.isEmpty()) {
372 resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Detected manipulated form.");
373 return ResponseType.NONE;
374 }
375 final var issue = new Issue(issueIdParam.get());
376 try {
377 final var issueComment = new IssueComment(getParameter(req, Integer.class, "commentid").orElse(-1), issue);
378 issueComment.setComment(getParameter(req, String.class, "comment").orElse(""));
380 if (issueComment.getComment().isBlank()) {
381 throw new IllegalArgumentException("comment.null");
382 }
384 LOG.debug("User {} is commenting on issue #{}", req.getRemoteUser(), issue.getId());
385 if (req.getRemoteUser() != null) {
386 dao.getUserDao().findByUsername(req.getRemoteUser()).ifPresent(issueComment::setAuthor);
387 }
389 dao.getIssueDao().saveComment(issueComment);
391 // TODO: fix redirect location (e.g. after fixing #24)
392 setRedirectLocation(req, "./projects/" + issue.getProject().getId()+"/issues/"+issue.getId()+"/edit");
393 setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
395 return ResponseType.HTML;
396 } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) {
397 resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
398 // TODO: implement - fix issue #21
399 return ResponseType.NONE;
400 }
401 }
402 }