1.1 --- a/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java Fri May 22 17:26:27 2020 +0200 1.2 +++ b/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java Fri May 22 21:23:57 2020 +0200 1.3 @@ -38,12 +38,14 @@ 1.4 import javax.servlet.annotation.WebServlet; 1.5 import javax.servlet.http.HttpServletRequest; 1.6 import javax.servlet.http.HttpServletResponse; 1.7 +import javax.servlet.http.HttpSession; 1.8 import java.io.IOException; 1.9 +import java.sql.Date; 1.10 import java.sql.SQLException; 1.11 import java.util.ArrayList; 1.12 import java.util.List; 1.13 import java.util.NoSuchElementException; 1.14 -import java.util.Optional; 1.15 +import java.util.Objects; 1.16 1.17 import static de.uapcore.lightpit.Functions.fqn; 1.18 1.19 @@ -61,32 +63,85 @@ 1.20 private static final Logger LOG = LoggerFactory.getLogger(ProjectsModule.class); 1.21 1.22 public static final String SESSION_ATTR_SELECTED_PROJECT = fqn(ProjectsModule.class, "selected-project"); 1.23 + public static final String SESSION_ATTR_SELECTED_ISSUE = fqn(ProjectsModule.class, "selected-issue"); 1.24 + public static final String SESSION_ATTR_SELECTED_VERSION = fqn(ProjectsModule.class, "selected-version"); 1.25 1.26 - private Project getSelectedProject(HttpServletRequest req, DataAccessObjects dao) throws SQLException { 1.27 - final var projectDao = dao.getProjectDao(); 1.28 - final var session = req.getSession(); 1.29 - final var projectSelection = getParameter(req, Integer.class, "pid"); 1.30 - final Project selectedProject; 1.31 - if (projectSelection.isPresent()) { 1.32 - selectedProject = projectDao.find(projectSelection.get()); 1.33 - } else { 1.34 - final var sessionProject = (Project) session.getAttribute(SESSION_ATTR_SELECTED_PROJECT); 1.35 - selectedProject = sessionProject == null ? null : projectDao.find(sessionProject.getId()); 1.36 + private class SessionSelection { 1.37 + final HttpSession session; 1.38 + Project project; 1.39 + Version version; 1.40 + Issue issue; 1.41 + 1.42 + SessionSelection(HttpServletRequest req, Project project) { 1.43 + this.session = req.getSession(); 1.44 + this.project = project; 1.45 + version = null; 1.46 + issue = null; 1.47 + updateAttributes(); 1.48 } 1.49 - session.setAttribute(SESSION_ATTR_SELECTED_PROJECT, selectedProject); 1.50 - return selectedProject; 1.51 + 1.52 + SessionSelection(HttpServletRequest req, DataAccessObjects dao) throws SQLException { 1.53 + this.session = req.getSession(); 1.54 + final var issueDao = dao.getIssueDao(); 1.55 + final var projectDao = dao.getProjectDao(); 1.56 + final var issueSelection = getParameter(req, Integer.class, "issue"); 1.57 + if (issueSelection.isPresent()) { 1.58 + issue = issueDao.find(issueSelection.get()); 1.59 + } else { 1.60 + final var issue = (Issue) session.getAttribute(SESSION_ATTR_SELECTED_ISSUE); 1.61 + this.issue = issue == null ? null : issueDao.find(issue.getId()); 1.62 + } 1.63 + if (issue != null) { 1.64 + version = null; // show the issue globally 1.65 + project = projectDao.find(issue.getProject().getId()); 1.66 + } 1.67 + 1.68 + final var projectSelection = getParameter(req, Integer.class, "pid"); 1.69 + if (projectSelection.isPresent()) { 1.70 + final var selectedProject = projectDao.find(projectSelection.get()); 1.71 + if (!Objects.equals(selectedProject, project)) { 1.72 + // reset version and issue if project changed 1.73 + version = null; 1.74 + issue = null; 1.75 + } 1.76 + project = selectedProject; 1.77 + } else { 1.78 + final var sessionProject = (Project) session.getAttribute(SESSION_ATTR_SELECTED_PROJECT); 1.79 + project = sessionProject == null ? null : projectDao.find(sessionProject.getId()); 1.80 + } 1.81 + updateAttributes(); 1.82 + } 1.83 + 1.84 + void selectVersion(Version version) { 1.85 + if (!version.getProject().equals(project)) throw new AssertionError("Nice, you implemented a bug!"); 1.86 + this.version = version; 1.87 + this.issue = null; 1.88 + updateAttributes(); 1.89 + } 1.90 + 1.91 + void selectIssue(Issue issue) { 1.92 + if (!issue.getProject().equals(project)) throw new AssertionError("Nice, you implemented a bug!"); 1.93 + this.issue = issue; 1.94 + this.version = null; 1.95 + updateAttributes(); 1.96 + } 1.97 + 1.98 + void updateAttributes() { 1.99 + session.setAttribute(SESSION_ATTR_SELECTED_PROJECT, project); 1.100 + session.setAttribute(SESSION_ATTR_SELECTED_VERSION, version); 1.101 + session.setAttribute(SESSION_ATTR_SELECTED_ISSUE, issue); 1.102 + } 1.103 } 1.104 1.105 1.106 /** 1.107 * Creates the breadcrumb menu. 1.108 * 1.109 - * @param level the current active level 1.110 - * @param selectedProject the selected project, if any, or null 1.111 + * @param level the current active level (0: root, 1: project, 2: version, 3: issue) 1.112 + * @param sessionSelection the currently selected objects 1.113 * @return a dynamic breadcrumb menu trying to display as many levels as possible 1.114 */ 1.115 - private List<MenuEntry> getBreadcrumbs(int level, 1.116 - Project selectedProject) { 1.117 + private List<MenuEntry> getBreadcrumbs(int level, SessionSelection sessionSelection) { 1.118 MenuEntry entry; 1.119 1.120 final var breadcrumbs = new ArrayList<MenuEntry>(); 1.121 @@ -95,52 +150,77 @@ 1.122 breadcrumbs.add(entry); 1.123 if (level == 0) entry.setActive(true); 1.124 1.125 - if (selectedProject == null) 1.126 - return breadcrumbs; 1.127 + if (sessionSelection.project != null) { 1.128 + if (sessionSelection.project.getId() < 0) { 1.129 + entry = new MenuEntry(new ResourceKey("localization.projects", "button.create"), 1.130 + "projects/edit", 1); 1.131 + } else { 1.132 + entry = new MenuEntry(sessionSelection.project.getName(), 1.133 + "projects/view?pid=" + sessionSelection.project.getId(), 1); 1.134 + } 1.135 + if (level == 1) entry.setActive(true); 1.136 + breadcrumbs.add(entry); 1.137 + } 1.138 1.139 - entry = new MenuEntry(selectedProject.getName(), 1.140 - "projects/view?pid=" + selectedProject.getId(), 1); 1.141 - if (level == 1) entry.setActive(true); 1.142 + if (sessionSelection.version != null) { 1.143 + if (sessionSelection.version.getId() < 0) { 1.144 + entry = new MenuEntry(new ResourceKey("localization.projects", "button.version.create"), 1.145 + "projects/versions/edit", 2); 1.146 + } else { 1.147 + entry = new MenuEntry(sessionSelection.version.getName(), 1.148 + // TODO: change link to issue overview for that version 1.149 + "projects/versions/edit?id=" + sessionSelection.version.getId(), 2); 1.150 + } 1.151 + if (level == 2) entry.setActive(true); 1.152 + breadcrumbs.add(entry); 1.153 + } 1.154 1.155 - breadcrumbs.add(entry); 1.156 + if (sessionSelection.issue != null) { 1.157 + entry = new MenuEntry(new ResourceKey("localization.projects", "menu.issues"), 1.158 + // TODO: change link to a separate issue view (maybe depending on the selected version) 1.159 + "projects/view?pid=" + sessionSelection.issue.getProject().getId(), 3); 1.160 + breadcrumbs.add(entry); 1.161 + if (sessionSelection.issue.getId() < 0) { 1.162 + entry = new MenuEntry(new ResourceKey("localization.projects", "button.issue.create"), 1.163 + "projects/issues/edit", 2); 1.164 + } else { 1.165 + entry = new MenuEntry("#" + sessionSelection.issue.getId(), 1.166 + // TODO: maybe change link to a view rather than directly opening the editor 1.167 + "projects/issues/edit?id=" + sessionSelection.issue.getId(), 4); 1.168 + } 1.169 + if (level == 3) entry.setActive(true); 1.170 + breadcrumbs.add(entry); 1.171 + } 1.172 + 1.173 return breadcrumbs; 1.174 } 1.175 1.176 @RequestMapping(method = HttpMethod.GET) 1.177 public ResponseType index(HttpServletRequest req, DataAccessObjects dao) throws SQLException { 1.178 - 1.179 + final var sessionSelection = new SessionSelection(req, dao); 1.180 final var projectList = dao.getProjectDao().list(); 1.181 req.setAttribute("projects", projectList); 1.182 setContentPage(req, "projects"); 1.183 setStylesheet(req, "projects"); 1.184 1.185 - final var selectedProject = getSelectedProject(req, dao); 1.186 - setBreadcrumbs(req, getBreadcrumbs(0, selectedProject)); 1.187 + setBreadcrumbs(req, getBreadcrumbs(0, sessionSelection)); 1.188 1.189 return ResponseType.HTML; 1.190 } 1.191 1.192 - private void configureEditForm(HttpServletRequest req, DataAccessObjects dao, Optional<Project> project) throws SQLException { 1.193 - if (project.isPresent()) { 1.194 - req.setAttribute("project", project.get()); 1.195 - setBreadcrumbs(req, getBreadcrumbs(1, project.get())); 1.196 - } else { 1.197 - req.setAttribute("project", new Project(-1)); 1.198 - setBreadcrumbs(req, getBreadcrumbs(0, null)); 1.199 - } 1.200 - 1.201 + private void configureEditForm(HttpServletRequest req, DataAccessObjects dao, SessionSelection selection) throws SQLException { 1.202 + req.setAttribute("project", selection.project); 1.203 req.setAttribute("users", dao.getUserDao().list()); 1.204 setContentPage(req, "project-form"); 1.205 + setBreadcrumbs(req, getBreadcrumbs(1, selection)); 1.206 } 1.207 1.208 @RequestMapping(requestPath = "edit", method = HttpMethod.GET) 1.209 public ResponseType edit(HttpServletRequest req, DataAccessObjects dao) throws SQLException { 1.210 + final var selection = new SessionSelection(req, findByParameter(req, Integer.class, "id", 1.211 + dao.getProjectDao()::find).orElse(new Project(-1))); 1.212 1.213 - Optional<Project> project = findByParameter(req, Integer.class, "id", dao.getProjectDao()::find); 1.214 - configureEditForm(req, dao, project); 1.215 - if (project.isPresent()) { 1.216 - req.getSession().setAttribute(SESSION_ATTR_SELECTED_PROJECT, project.get()); 1.217 - } 1.218 + configureEditForm(req, dao, selection); 1.219 1.220 return ResponseType.HTML; 1.221 } 1.222 @@ -148,7 +228,7 @@ 1.223 @RequestMapping(requestPath = "commit", method = HttpMethod.POST) 1.224 public ResponseType commit(HttpServletRequest req, DataAccessObjects dao) throws SQLException { 1.225 1.226 - Project project = null; 1.227 + Project project = new Project(-1); 1.228 try { 1.229 project = new Project(getParameter(req, Integer.class, "id").orElseThrow()); 1.230 project.setName(getParameter(req, String.class, "name").orElseThrow()); 1.231 @@ -163,11 +243,11 @@ 1.232 setRedirectLocation(req, "./projects/"); 1.233 setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL); 1.234 LOG.debug("Successfully updated project {}", project.getName()); 1.235 - } catch (NoSuchElementException | NumberFormatException | SQLException ex) { 1.236 + } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) { 1.237 // TODO: set request attribute with error text 1.238 LOG.warn("Form validation failure: {}", ex.getMessage()); 1.239 LOG.debug("Details:", ex); 1.240 - configureEditForm(req, dao, Optional.ofNullable(project)); 1.241 + configureEditForm(req, dao, new SessionSelection(req, project)); 1.242 } 1.243 1.244 return ResponseType.HTML; 1.245 @@ -175,123 +255,133 @@ 1.246 1.247 @RequestMapping(requestPath = "view", method = HttpMethod.GET) 1.248 public ResponseType view(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException, SQLException { 1.249 - final var selectedProject = getSelectedProject(req, dao); 1.250 - if (selectedProject == null) { 1.251 - resp.sendError(HttpServletResponse.SC_FORBIDDEN); 1.252 - return ResponseType.NONE; 1.253 - } 1.254 + final var sessionSelection = new SessionSelection(req, dao); 1.255 1.256 - req.setAttribute("versions", dao.getVersionDao().list(selectedProject)); 1.257 - req.setAttribute("issues", dao.getIssueDao().list(selectedProject)); 1.258 + req.setAttribute("versions", dao.getVersionDao().list(sessionSelection.project)); 1.259 + req.setAttribute("issues", dao.getIssueDao().list(sessionSelection.project)); 1.260 1.261 - // TODO: add more levels depending on last visited location 1.262 - setBreadcrumbs(req, getBreadcrumbs(1, selectedProject)); 1.263 - 1.264 + setBreadcrumbs(req, getBreadcrumbs(1, sessionSelection)); 1.265 setContentPage(req, "project-details"); 1.266 1.267 return ResponseType.HTML; 1.268 } 1.269 1.270 - private void configureEditVersionForm(HttpServletRequest req, Optional<Version> version, Project selectedProject) { 1.271 - req.setAttribute("version", version.orElse(new Version(-1, selectedProject))); 1.272 + private void configureEditVersionForm(HttpServletRequest req, SessionSelection selection) { 1.273 + req.setAttribute("version", selection.version); 1.274 req.setAttribute("versionStatusEnum", VersionStatus.values()); 1.275 1.276 setContentPage(req, "version-form"); 1.277 + setBreadcrumbs(req, getBreadcrumbs(2, selection)); 1.278 } 1.279 1.280 @RequestMapping(requestPath = "versions/edit", method = HttpMethod.GET) 1.281 public ResponseType editVersion(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException, SQLException { 1.282 - final var selectedProject = getSelectedProject(req, dao); 1.283 - if (selectedProject == null) { 1.284 + final var sessionSelection = new SessionSelection(req, dao); 1.285 + if (sessionSelection.project == null) { 1.286 + // TODO: remove this bullshit and only retrieve the object from session if we are creating a fresh version 1.287 resp.sendError(HttpServletResponse.SC_FORBIDDEN); 1.288 return ResponseType.NONE; 1.289 } 1.290 1.291 - configureEditVersionForm(req, 1.292 - findByParameter(req, Integer.class, "id", dao.getVersionDao()::find), 1.293 - selectedProject); 1.294 + sessionSelection.selectVersion(findByParameter(req, Integer.class, "id", dao.getVersionDao()::find) 1.295 + .orElse(new Version(-1, sessionSelection.project))); 1.296 + configureEditVersionForm(req, sessionSelection); 1.297 1.298 return ResponseType.HTML; 1.299 } 1.300 1.301 @RequestMapping(requestPath = "versions/commit", method = HttpMethod.POST) 1.302 public ResponseType commitVersion(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException, SQLException { 1.303 - final var selectedProject = getSelectedProject(req, dao); 1.304 - if (selectedProject == null) { 1.305 + final var sessionSelection = new SessionSelection(req, dao); 1.306 + if (sessionSelection.project == null) { 1.307 + // TODO: remove this bullshit and retrieve project id from hidden field 1.308 resp.sendError(HttpServletResponse.SC_FORBIDDEN); 1.309 return ResponseType.NONE; 1.310 } 1.311 1.312 - Version version = null; 1.313 + var version = new Version(-1, sessionSelection.project); 1.314 try { 1.315 - version = new Version(getParameter(req, Integer.class, "id").orElseThrow(), selectedProject); 1.316 + version = new Version(getParameter(req, Integer.class, "id").orElseThrow(), sessionSelection.project); 1.317 version.setName(getParameter(req, String.class, "name").orElseThrow()); 1.318 getParameter(req, Integer.class, "ordinal").ifPresent(version::setOrdinal); 1.319 version.setStatus(VersionStatus.valueOf(getParameter(req, String.class, "status").orElseThrow())); 1.320 dao.getVersionDao().saveOrUpdate(version); 1.321 1.322 - setRedirectLocation(req, "./projects/versions/"); 1.323 + // specifying the pid parameter will purposely reset the session selected version! 1.324 + setRedirectLocation(req, "./projects/view?pid="+sessionSelection.project.getId()); 1.325 setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL); 1.326 - LOG.debug("Successfully updated version {} for project {}", version.getName(), selectedProject.getName()); 1.327 - } catch (NoSuchElementException | NumberFormatException | SQLException ex) { 1.328 + LOG.debug("Successfully updated version {} for project {}", version.getName(), sessionSelection.project.getName()); 1.329 + } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) { 1.330 // TODO: set request attribute with error text 1.331 LOG.warn("Form validation failure: {}", ex.getMessage()); 1.332 LOG.debug("Details:", ex); 1.333 - configureEditVersionForm(req, Optional.ofNullable(version), selectedProject); 1.334 + sessionSelection.selectVersion(version); 1.335 + configureEditVersionForm(req, sessionSelection); 1.336 } 1.337 1.338 return ResponseType.HTML; 1.339 } 1.340 1.341 - private void configureEditIssueForm(HttpServletRequest req, DataAccessObjects dao, Optional<Issue> issue, Project selectedProject) throws SQLException { 1.342 - 1.343 - req.setAttribute("issue", issue.orElse(new Issue(-1, selectedProject))); 1.344 + private void configureEditIssueForm(HttpServletRequest req, DataAccessObjects dao, SessionSelection selection) throws SQLException { 1.345 + req.setAttribute("issue", selection.issue); 1.346 req.setAttribute("issueStatusEnum", IssueStatus.values()); 1.347 req.setAttribute("issueCategoryEnum", IssueCategory.values()); 1.348 - req.setAttribute("versions", dao.getVersionDao().list(selectedProject)); 1.349 + req.setAttribute("versions", dao.getVersionDao().list(selection.project)); 1.350 + req.setAttribute("users", dao.getUserDao().list()); 1.351 1.352 setContentPage(req, "issue-form"); 1.353 + setBreadcrumbs(req, getBreadcrumbs(3, selection)); 1.354 } 1.355 1.356 @RequestMapping(requestPath = "issues/edit", method = HttpMethod.GET) 1.357 public ResponseType editIssue(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException, SQLException { 1.358 - final var selectedProject = getSelectedProject(req, dao); 1.359 - if (selectedProject == null) { 1.360 + final var sessionSelection = new SessionSelection(req, dao); 1.361 + if (sessionSelection.project == null) { 1.362 + // TODO: remove this bullshit and only retrieve the object from session if we are creating a fresh issue 1.363 resp.sendError(HttpServletResponse.SC_FORBIDDEN); 1.364 return ResponseType.NONE; 1.365 } 1.366 1.367 - configureEditIssueForm(req, dao, 1.368 - findByParameter(req, Integer.class, "id", dao.getIssueDao()::find), 1.369 - selectedProject); 1.370 + sessionSelection.selectIssue(findByParameter(req, Integer.class, "id", 1.371 + dao.getIssueDao()::find).orElse(new Issue(-1, sessionSelection.project))); 1.372 + configureEditIssueForm(req, dao, sessionSelection); 1.373 1.374 return ResponseType.HTML; 1.375 } 1.376 1.377 @RequestMapping(requestPath = "issues/commit", method = HttpMethod.POST) 1.378 public ResponseType commitIssue(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException, SQLException { 1.379 - final var selectedProject = getSelectedProject(req, dao); 1.380 - if (selectedProject == null) { 1.381 + // TODO: remove this bullshit and store the project ID as hidden field 1.382 + final var sessionSelection = new SessionSelection(req, dao); 1.383 + if (sessionSelection.project == null) { 1.384 resp.sendError(HttpServletResponse.SC_FORBIDDEN); 1.385 return ResponseType.NONE; 1.386 } 1.387 1.388 - Issue issue = null; 1.389 + Issue issue = new Issue(-1, sessionSelection.project); 1.390 try { 1.391 - issue = new Issue(getParameter(req, Integer.class, "id").orElseThrow(), selectedProject); 1.392 - 1.393 - // TODO: implement 1.394 - 1.395 + issue = new Issue(getParameter(req, Integer.class, "id").orElseThrow(), sessionSelection.project); 1.396 + getParameter(req, String.class, "category").map(IssueCategory::valueOf).ifPresent(issue::setCategory); 1.397 + getParameter(req, String.class, "status").map(IssueStatus::valueOf).ifPresent(issue::setStatus); 1.398 + issue.setSubject(getParameter(req, String.class, "subject").orElseThrow()); 1.399 + getParameter(req, Integer.class, "assignee").map( 1.400 + userid -> userid >= 0 ? new User(userid) : null 1.401 + ).ifPresent(issue::setAssignee); 1.402 + getParameter(req, String.class, "description").ifPresent(issue::setDescription); 1.403 + getParameter(req, Date.class, "eta").ifPresent(issue::setEta); 1.404 dao.getIssueDao().saveOrUpdate(issue); 1.405 1.406 - setRedirectLocation(req, "./projects/issues/"); 1.407 + // TODO: redirect to issue overview 1.408 + // specifying the issue parameter keeps the edited issue as breadcrumb 1.409 + setRedirectLocation(req, "./projects/view?issue="+issue.getId()); 1.410 setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL); 1.411 - LOG.debug("Successfully updated issue {} for project {}", issue.getId(), selectedProject.getName()); 1.412 - } catch (NoSuchElementException | NumberFormatException | SQLException ex) { 1.413 + LOG.debug("Successfully updated issue {} for project {}", issue.getId(), sessionSelection.project.getName()); 1.414 + } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) { 1.415 // TODO: set request attribute with error text 1.416 LOG.warn("Form validation failure: {}", ex.getMessage()); 1.417 LOG.debug("Details:", ex); 1.418 - configureEditIssueForm(req, dao, Optional.ofNullable(issue), selectedProject); 1.419 + sessionSelection.selectIssue(issue); 1.420 + configureEditIssueForm(req, dao, sessionSelection); 1.421 } 1.422 1.423 return ResponseType.HTML;