diff -r 91d1fc2a3a14 -r 33b6843fdf8a src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java --- a/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java Fri May 22 17:26:27 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java Fri May 22 21:23:57 2020 +0200 @@ -38,12 +38,14 @@ import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; import java.io.IOException; +import java.sql.Date; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; -import java.util.Optional; +import java.util.Objects; import static de.uapcore.lightpit.Functions.fqn; @@ -61,32 +63,85 @@ private static final Logger LOG = LoggerFactory.getLogger(ProjectsModule.class); public static final String SESSION_ATTR_SELECTED_PROJECT = fqn(ProjectsModule.class, "selected-project"); + public static final String SESSION_ATTR_SELECTED_ISSUE = fqn(ProjectsModule.class, "selected-issue"); + public static final String SESSION_ATTR_SELECTED_VERSION = fqn(ProjectsModule.class, "selected-version"); - private Project getSelectedProject(HttpServletRequest req, DataAccessObjects dao) throws SQLException { - final var projectDao = dao.getProjectDao(); - final var session = req.getSession(); - final var projectSelection = getParameter(req, Integer.class, "pid"); - final Project selectedProject; - if (projectSelection.isPresent()) { - selectedProject = projectDao.find(projectSelection.get()); - } else { - final var sessionProject = (Project) session.getAttribute(SESSION_ATTR_SELECTED_PROJECT); - selectedProject = sessionProject == null ? null : projectDao.find(sessionProject.getId()); + private class SessionSelection { + final HttpSession session; + Project project; + Version version; + Issue issue; + + SessionSelection(HttpServletRequest req, Project project) { + this.session = req.getSession(); + this.project = project; + version = null; + issue = null; + updateAttributes(); } - session.setAttribute(SESSION_ATTR_SELECTED_PROJECT, selectedProject); - return selectedProject; + + SessionSelection(HttpServletRequest req, DataAccessObjects dao) throws SQLException { + this.session = req.getSession(); + final var issueDao = dao.getIssueDao(); + final var projectDao = dao.getProjectDao(); + final var issueSelection = getParameter(req, Integer.class, "issue"); + if (issueSelection.isPresent()) { + issue = issueDao.find(issueSelection.get()); + } else { + final var issue = (Issue) session.getAttribute(SESSION_ATTR_SELECTED_ISSUE); + this.issue = issue == null ? null : issueDao.find(issue.getId()); + } + if (issue != null) { + version = null; // show the issue globally + project = projectDao.find(issue.getProject().getId()); + } + + final var projectSelection = getParameter(req, Integer.class, "pid"); + if (projectSelection.isPresent()) { + final var selectedProject = projectDao.find(projectSelection.get()); + if (!Objects.equals(selectedProject, project)) { + // reset version and issue if project changed + version = null; + issue = null; + } + project = selectedProject; + } else { + final var sessionProject = (Project) session.getAttribute(SESSION_ATTR_SELECTED_PROJECT); + project = sessionProject == null ? null : projectDao.find(sessionProject.getId()); + } + updateAttributes(); + } + + void selectVersion(Version version) { + if (!version.getProject().equals(project)) throw new AssertionError("Nice, you implemented a bug!"); + this.version = version; + this.issue = null; + updateAttributes(); + } + + void selectIssue(Issue issue) { + if (!issue.getProject().equals(project)) throw new AssertionError("Nice, you implemented a bug!"); + this.issue = issue; + this.version = null; + updateAttributes(); + } + + void updateAttributes() { + session.setAttribute(SESSION_ATTR_SELECTED_PROJECT, project); + session.setAttribute(SESSION_ATTR_SELECTED_VERSION, version); + session.setAttribute(SESSION_ATTR_SELECTED_ISSUE, issue); + } } /** * Creates the breadcrumb menu. * - * @param level the current active level - * @param selectedProject the selected project, if any, or null + * @param level the current active level (0: root, 1: project, 2: version, 3: issue) + * @param sessionSelection the currently selected objects * @return a dynamic breadcrumb menu trying to display as many levels as possible */ - private List getBreadcrumbs(int level, - Project selectedProject) { + private List getBreadcrumbs(int level, SessionSelection sessionSelection) { MenuEntry entry; final var breadcrumbs = new ArrayList(); @@ -95,52 +150,77 @@ breadcrumbs.add(entry); if (level == 0) entry.setActive(true); - if (selectedProject == null) - return breadcrumbs; + if (sessionSelection.project != null) { + if (sessionSelection.project.getId() < 0) { + entry = new MenuEntry(new ResourceKey("localization.projects", "button.create"), + "projects/edit", 1); + } else { + entry = new MenuEntry(sessionSelection.project.getName(), + "projects/view?pid=" + sessionSelection.project.getId(), 1); + } + if (level == 1) entry.setActive(true); + breadcrumbs.add(entry); + } - entry = new MenuEntry(selectedProject.getName(), - "projects/view?pid=" + selectedProject.getId(), 1); - if (level == 1) entry.setActive(true); + if (sessionSelection.version != null) { + if (sessionSelection.version.getId() < 0) { + entry = new MenuEntry(new ResourceKey("localization.projects", "button.version.create"), + "projects/versions/edit", 2); + } else { + entry = new MenuEntry(sessionSelection.version.getName(), + // TODO: change link to issue overview for that version + "projects/versions/edit?id=" + sessionSelection.version.getId(), 2); + } + if (level == 2) entry.setActive(true); + breadcrumbs.add(entry); + } - breadcrumbs.add(entry); + if (sessionSelection.issue != null) { + entry = new MenuEntry(new ResourceKey("localization.projects", "menu.issues"), + // TODO: change link to a separate issue view (maybe depending on the selected version) + "projects/view?pid=" + sessionSelection.issue.getProject().getId(), 3); + breadcrumbs.add(entry); + if (sessionSelection.issue.getId() < 0) { + entry = new MenuEntry(new ResourceKey("localization.projects", "button.issue.create"), + "projects/issues/edit", 2); + } else { + entry = new MenuEntry("#" + sessionSelection.issue.getId(), + // TODO: maybe change link to a view rather than directly opening the editor + "projects/issues/edit?id=" + sessionSelection.issue.getId(), 4); + } + if (level == 3) entry.setActive(true); + breadcrumbs.add(entry); + } + return breadcrumbs; } @RequestMapping(method = HttpMethod.GET) public ResponseType index(HttpServletRequest req, DataAccessObjects dao) throws SQLException { - + final var sessionSelection = new SessionSelection(req, dao); final var projectList = dao.getProjectDao().list(); req.setAttribute("projects", projectList); setContentPage(req, "projects"); setStylesheet(req, "projects"); - final var selectedProject = getSelectedProject(req, dao); - setBreadcrumbs(req, getBreadcrumbs(0, selectedProject)); + setBreadcrumbs(req, getBreadcrumbs(0, sessionSelection)); return ResponseType.HTML; } - private void configureEditForm(HttpServletRequest req, DataAccessObjects dao, Optional project) throws SQLException { - if (project.isPresent()) { - req.setAttribute("project", project.get()); - setBreadcrumbs(req, getBreadcrumbs(1, project.get())); - } else { - req.setAttribute("project", new Project(-1)); - setBreadcrumbs(req, getBreadcrumbs(0, null)); - } - + private void configureEditForm(HttpServletRequest req, DataAccessObjects dao, SessionSelection selection) throws SQLException { + req.setAttribute("project", selection.project); req.setAttribute("users", dao.getUserDao().list()); setContentPage(req, "project-form"); + setBreadcrumbs(req, getBreadcrumbs(1, selection)); } @RequestMapping(requestPath = "edit", method = HttpMethod.GET) public ResponseType edit(HttpServletRequest req, DataAccessObjects dao) throws SQLException { + final var selection = new SessionSelection(req, findByParameter(req, Integer.class, "id", + dao.getProjectDao()::find).orElse(new Project(-1))); - Optional project = findByParameter(req, Integer.class, "id", dao.getProjectDao()::find); - configureEditForm(req, dao, project); - if (project.isPresent()) { - req.getSession().setAttribute(SESSION_ATTR_SELECTED_PROJECT, project.get()); - } + configureEditForm(req, dao, selection); return ResponseType.HTML; } @@ -148,7 +228,7 @@ @RequestMapping(requestPath = "commit", method = HttpMethod.POST) public ResponseType commit(HttpServletRequest req, DataAccessObjects dao) throws SQLException { - Project project = null; + Project project = new Project(-1); try { project = new Project(getParameter(req, Integer.class, "id").orElseThrow()); project.setName(getParameter(req, String.class, "name").orElseThrow()); @@ -163,11 +243,11 @@ setRedirectLocation(req, "./projects/"); setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL); LOG.debug("Successfully updated project {}", project.getName()); - } catch (NoSuchElementException | NumberFormatException | SQLException ex) { + } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) { // TODO: set request attribute with error text LOG.warn("Form validation failure: {}", ex.getMessage()); LOG.debug("Details:", ex); - configureEditForm(req, dao, Optional.ofNullable(project)); + configureEditForm(req, dao, new SessionSelection(req, project)); } return ResponseType.HTML; @@ -175,123 +255,133 @@ @RequestMapping(requestPath = "view", method = HttpMethod.GET) public ResponseType view(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException, SQLException { - final var selectedProject = getSelectedProject(req, dao); - if (selectedProject == null) { - resp.sendError(HttpServletResponse.SC_FORBIDDEN); - return ResponseType.NONE; - } + final var sessionSelection = new SessionSelection(req, dao); - req.setAttribute("versions", dao.getVersionDao().list(selectedProject)); - req.setAttribute("issues", dao.getIssueDao().list(selectedProject)); + req.setAttribute("versions", dao.getVersionDao().list(sessionSelection.project)); + req.setAttribute("issues", dao.getIssueDao().list(sessionSelection.project)); - // TODO: add more levels depending on last visited location - setBreadcrumbs(req, getBreadcrumbs(1, selectedProject)); - + setBreadcrumbs(req, getBreadcrumbs(1, sessionSelection)); setContentPage(req, "project-details"); return ResponseType.HTML; } - private void configureEditVersionForm(HttpServletRequest req, Optional version, Project selectedProject) { - req.setAttribute("version", version.orElse(new Version(-1, selectedProject))); + private void configureEditVersionForm(HttpServletRequest req, SessionSelection selection) { + req.setAttribute("version", selection.version); req.setAttribute("versionStatusEnum", VersionStatus.values()); setContentPage(req, "version-form"); + setBreadcrumbs(req, getBreadcrumbs(2, selection)); } @RequestMapping(requestPath = "versions/edit", method = HttpMethod.GET) public ResponseType editVersion(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException, SQLException { - final var selectedProject = getSelectedProject(req, dao); - if (selectedProject == null) { + final var sessionSelection = new SessionSelection(req, dao); + if (sessionSelection.project == null) { + // TODO: remove this bullshit and only retrieve the object from session if we are creating a fresh version resp.sendError(HttpServletResponse.SC_FORBIDDEN); return ResponseType.NONE; } - configureEditVersionForm(req, - findByParameter(req, Integer.class, "id", dao.getVersionDao()::find), - selectedProject); + sessionSelection.selectVersion(findByParameter(req, Integer.class, "id", dao.getVersionDao()::find) + .orElse(new Version(-1, sessionSelection.project))); + configureEditVersionForm(req, sessionSelection); return ResponseType.HTML; } @RequestMapping(requestPath = "versions/commit", method = HttpMethod.POST) public ResponseType commitVersion(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException, SQLException { - final var selectedProject = getSelectedProject(req, dao); - if (selectedProject == null) { + final var sessionSelection = new SessionSelection(req, dao); + if (sessionSelection.project == null) { + // TODO: remove this bullshit and retrieve project id from hidden field resp.sendError(HttpServletResponse.SC_FORBIDDEN); return ResponseType.NONE; } - Version version = null; + var version = new Version(-1, sessionSelection.project); try { - version = new Version(getParameter(req, Integer.class, "id").orElseThrow(), selectedProject); + version = new Version(getParameter(req, Integer.class, "id").orElseThrow(), sessionSelection.project); version.setName(getParameter(req, String.class, "name").orElseThrow()); getParameter(req, Integer.class, "ordinal").ifPresent(version::setOrdinal); version.setStatus(VersionStatus.valueOf(getParameter(req, String.class, "status").orElseThrow())); dao.getVersionDao().saveOrUpdate(version); - setRedirectLocation(req, "./projects/versions/"); + // specifying the pid parameter will purposely reset the session selected version! + setRedirectLocation(req, "./projects/view?pid="+sessionSelection.project.getId()); setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL); - LOG.debug("Successfully updated version {} for project {}", version.getName(), selectedProject.getName()); - } catch (NoSuchElementException | NumberFormatException | SQLException ex) { + LOG.debug("Successfully updated version {} for project {}", version.getName(), sessionSelection.project.getName()); + } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) { // TODO: set request attribute with error text LOG.warn("Form validation failure: {}", ex.getMessage()); LOG.debug("Details:", ex); - configureEditVersionForm(req, Optional.ofNullable(version), selectedProject); + sessionSelection.selectVersion(version); + configureEditVersionForm(req, sessionSelection); } return ResponseType.HTML; } - private void configureEditIssueForm(HttpServletRequest req, DataAccessObjects dao, Optional issue, Project selectedProject) throws SQLException { - - req.setAttribute("issue", issue.orElse(new Issue(-1, selectedProject))); + private void configureEditIssueForm(HttpServletRequest req, DataAccessObjects dao, SessionSelection selection) throws SQLException { + req.setAttribute("issue", selection.issue); req.setAttribute("issueStatusEnum", IssueStatus.values()); req.setAttribute("issueCategoryEnum", IssueCategory.values()); - req.setAttribute("versions", dao.getVersionDao().list(selectedProject)); + req.setAttribute("versions", dao.getVersionDao().list(selection.project)); + req.setAttribute("users", dao.getUserDao().list()); setContentPage(req, "issue-form"); + setBreadcrumbs(req, getBreadcrumbs(3, selection)); } @RequestMapping(requestPath = "issues/edit", method = HttpMethod.GET) public ResponseType editIssue(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException, SQLException { - final var selectedProject = getSelectedProject(req, dao); - if (selectedProject == null) { + final var sessionSelection = new SessionSelection(req, dao); + if (sessionSelection.project == null) { + // TODO: remove this bullshit and only retrieve the object from session if we are creating a fresh issue resp.sendError(HttpServletResponse.SC_FORBIDDEN); return ResponseType.NONE; } - configureEditIssueForm(req, dao, - findByParameter(req, Integer.class, "id", dao.getIssueDao()::find), - selectedProject); + sessionSelection.selectIssue(findByParameter(req, Integer.class, "id", + dao.getIssueDao()::find).orElse(new Issue(-1, sessionSelection.project))); + configureEditIssueForm(req, dao, sessionSelection); return ResponseType.HTML; } @RequestMapping(requestPath = "issues/commit", method = HttpMethod.POST) public ResponseType commitIssue(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException, SQLException { - final var selectedProject = getSelectedProject(req, dao); - if (selectedProject == null) { + // TODO: remove this bullshit and store the project ID as hidden field + final var sessionSelection = new SessionSelection(req, dao); + if (sessionSelection.project == null) { resp.sendError(HttpServletResponse.SC_FORBIDDEN); return ResponseType.NONE; } - Issue issue = null; + Issue issue = new Issue(-1, sessionSelection.project); try { - issue = new Issue(getParameter(req, Integer.class, "id").orElseThrow(), selectedProject); - - // TODO: implement - + issue = new Issue(getParameter(req, Integer.class, "id").orElseThrow(), sessionSelection.project); + getParameter(req, String.class, "category").map(IssueCategory::valueOf).ifPresent(issue::setCategory); + getParameter(req, String.class, "status").map(IssueStatus::valueOf).ifPresent(issue::setStatus); + issue.setSubject(getParameter(req, String.class, "subject").orElseThrow()); + getParameter(req, Integer.class, "assignee").map( + userid -> userid >= 0 ? new User(userid) : null + ).ifPresent(issue::setAssignee); + getParameter(req, String.class, "description").ifPresent(issue::setDescription); + getParameter(req, Date.class, "eta").ifPresent(issue::setEta); dao.getIssueDao().saveOrUpdate(issue); - setRedirectLocation(req, "./projects/issues/"); + // TODO: redirect to issue overview + // specifying the issue parameter keeps the edited issue as breadcrumb + setRedirectLocation(req, "./projects/view?issue="+issue.getId()); setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL); - LOG.debug("Successfully updated issue {} for project {}", issue.getId(), selectedProject.getName()); - } catch (NoSuchElementException | NumberFormatException | SQLException ex) { + LOG.debug("Successfully updated issue {} for project {}", issue.getId(), sessionSelection.project.getName()); + } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) { // TODO: set request attribute with error text LOG.warn("Form validation failure: {}", ex.getMessage()); LOG.debug("Details:", ex); - configureEditIssueForm(req, dao, Optional.ofNullable(issue), selectedProject); + sessionSelection.selectIssue(issue); + configureEditIssueForm(req, dao, sessionSelection); } return ResponseType.HTML;