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.1 --- a/src/main/java/de/uapcore/lightpit/AbstractLightPITServlet.java Thu Oct 15 18:36:05 2020 +0200 1.2 +++ b/src/main/java/de/uapcore/lightpit/AbstractLightPITServlet.java Thu Oct 15 20:02:30 2020 +0200 1.3 @@ -194,12 +194,15 @@ 1.4 try { 1.5 PathPattern pathPattern = new PathPattern(mapping.get().requestPath()); 1.6 1.7 - if (mappings 1.8 - .computeIfAbsent(mapping.get().method(), k -> new HashMap<>()) 1.9 - .putIfAbsent(pathPattern, method) != null) { 1.10 - LOG.warn("{} {} has multiple mappings", 1.11 + final var methodMappings = mappings.computeIfAbsent(mapping.get().method(), k -> new HashMap<>()); 1.12 + final var currentMapping = methodMappings.putIfAbsent(pathPattern, method); 1.13 + if (currentMapping != null) { 1.14 + LOG.warn("Cannot map {} {} to {} in class {} - this would override the mapping to {}", 1.15 mapping.get().method(), 1.16 - mapping.get().requestPath() 1.17 + mapping.get().requestPath(), 1.18 + method.getName(), 1.19 + getClass().getSimpleName(), 1.20 + currentMapping.getName() 1.21 ); 1.22 } 1.23 1.24 @@ -296,6 +299,32 @@ 1.25 req.setAttribute(Constants.REQ_ATTR_VIEWMODEL, viewModel); 1.26 } 1.27 1.28 + private <T> Optional<T> parseParameter(String paramValue, Class<T> clazz) { 1.29 + if (paramValue == null) return Optional.empty(); 1.30 + if (clazz.equals(Boolean.class)) { 1.31 + if (paramValue.toLowerCase().equals("false") || paramValue.equals("0")) { 1.32 + return Optional.of((T) Boolean.FALSE); 1.33 + } else { 1.34 + return Optional.of((T) Boolean.TRUE); 1.35 + } 1.36 + } 1.37 + if (clazz.equals(String.class)) return Optional.of((T) paramValue); 1.38 + if (java.sql.Date.class.isAssignableFrom(clazz)) { 1.39 + try { 1.40 + return Optional.of((T) java.sql.Date.valueOf(paramValue)); 1.41 + } catch (IllegalArgumentException ex) { 1.42 + return Optional.empty(); 1.43 + } 1.44 + } 1.45 + try { 1.46 + final Constructor<T> ctor = clazz.getConstructor(String.class); 1.47 + return Optional.of(ctor.newInstance(paramValue)); 1.48 + } catch (ReflectiveOperationException e) { 1.49 + // does not type check and is not convertible - treat as if the parameter was never set 1.50 + return Optional.empty(); 1.51 + } 1.52 + } 1.53 + 1.54 /** 1.55 * Obtains a request parameter of the specified type. 1.56 * The specified type must have a single-argument constructor accepting a string to perform conversion. 1.57 @@ -322,30 +351,7 @@ 1.58 } 1.59 return Optional.of(array); 1.60 } else { 1.61 - final String paramValue = req.getParameter(name); 1.62 - if (paramValue == null) return Optional.empty(); 1.63 - if (clazz.equals(Boolean.class)) { 1.64 - if (paramValue.toLowerCase().equals("false") || paramValue.equals("0")) { 1.65 - return Optional.of((T) Boolean.FALSE); 1.66 - } else { 1.67 - return Optional.of((T) Boolean.TRUE); 1.68 - } 1.69 - } 1.70 - if (clazz.equals(String.class)) return Optional.of((T) paramValue); 1.71 - if (java.sql.Date.class.isAssignableFrom(clazz)) { 1.72 - try { 1.73 - return Optional.of((T) java.sql.Date.valueOf(paramValue)); 1.74 - } catch (IllegalArgumentException ex) { 1.75 - return Optional.empty(); 1.76 - } 1.77 - } 1.78 - try { 1.79 - final Constructor<T> ctor = clazz.getConstructor(String.class); 1.80 - return Optional.of(ctor.newInstance(paramValue)); 1.81 - } catch (ReflectiveOperationException e) { 1.82 - // does not type check and is not convertible - treat as if the parameter was never set 1.83 - return Optional.empty(); 1.84 - } 1.85 + return parseParameter(req.getParameter(name), clazz); 1.86 } 1.87 } 1.88
2.1 --- a/src/main/java/de/uapcore/lightpit/Functions.java Thu Oct 15 18:36:05 2020 +0200 2.2 +++ b/src/main/java/de/uapcore/lightpit/Functions.java Thu Oct 15 20:02:30 2020 +0200 2.3 @@ -74,6 +74,14 @@ 2.4 return req.getServletPath() + Optional.ofNullable(req.getPathInfo()).orElse(""); 2.5 } 2.6 2.7 + public static int parseIntOrZero(String str) { 2.8 + try { 2.9 + return Integer.parseInt(str); 2.10 + } catch (NumberFormatException ex) { 2.11 + return 0; 2.12 + } 2.13 + } 2.14 + 2.15 /** 2.16 * This class is not instantiatable. 2.17 */
3.1 --- a/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java Thu Oct 15 18:36:05 2020 +0200 3.2 +++ b/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java Thu Oct 15 20:02:30 2020 +0200 3.3 @@ -44,12 +44,9 @@ 3.4 import java.sql.Date; 3.5 import java.sql.SQLException; 3.6 import java.util.NoSuchElementException; 3.7 -import java.util.Optional; 3.8 import java.util.stream.Collectors; 3.9 import java.util.stream.Stream; 3.10 3.11 -import static de.uapcore.lightpit.Functions.fqn; 3.12 - 3.13 @WebServlet( 3.14 name = "ProjectsModule", 3.15 urlPatterns = "/projects/*" 3.16 @@ -58,45 +55,26 @@ 3.17 3.18 private static final Logger LOG = LoggerFactory.getLogger(ProjectsModule.class); 3.19 3.20 - private static final String SESSION_ATTR_SELECTED_PROJECT = fqn(ProjectsModule.class, "selected_project"); 3.21 - private static final String SESSION_ATTR_SELECTED_VERSION = fqn(ProjectsModule.class, "selected_version"); 3.22 - private static final String SESSION_ATTR_SELECTED_COMPONENT = fqn(ProjectsModule.class, "selected_component"); 3.23 - private static final String PARAMETER_SELECTED_PROJECT = "pid"; 3.24 - private static final String PARAMETER_SELECTED_VERSION = "vid"; 3.25 - private static final String PARAMETER_SELECTED_COMPONENT = "cid"; 3.26 - 3.27 @Override 3.28 protected String getResourceBundleName() { 3.29 return "localization.projects"; 3.30 } 3.31 3.32 - private int syncParamWithSession(HttpServletRequest req, String param, String attr) { 3.33 - final var session = req.getSession(); 3.34 - final var idParam = getParameter(req, Integer.class, param); 3.35 - final int id; 3.36 - if (idParam.isPresent()) { 3.37 - id = idParam.get(); 3.38 - session.setAttribute(attr, id); 3.39 - } else { 3.40 - id = Optional.ofNullable(session.getAttribute(attr)).map(x->(Integer)x).orElse(-1); 3.41 - } 3.42 - return id; 3.43 - } 3.44 - 3.45 - private void populate(ProjectView viewModel, HttpServletRequest req, DataAccessObjects dao) throws SQLException { 3.46 + private void populate(ProjectView viewModel, PathParameters pathParameters, DataAccessObjects dao) throws SQLException { 3.47 final var projectDao = dao.getProjectDao(); 3.48 final var versionDao = dao.getVersionDao(); 3.49 final var componentDao = dao.getComponentDao(); 3.50 3.51 projectDao.list().stream().map(ProjectInfo::new).forEach(viewModel.getProjectList()::add); 3.52 3.53 + if (pathParameters == null) 3.54 + return; 3.55 + 3.56 // Select Project 3.57 - final int pid = syncParamWithSession(req, PARAMETER_SELECTED_PROJECT, SESSION_ATTR_SELECTED_PROJECT); 3.58 - if (pid >= 0) { 3.59 + final int pid = Functions.parseIntOrZero(pathParameters.get("project")); 3.60 + if (pid > 0) { 3.61 final var project = projectDao.find(pid); 3.62 - if (project == null) { 3.63 - req.setAttribute(SESSION_ATTR_SELECTED_PROJECT, -1); 3.64 - } else { 3.65 + if (project != null) { 3.66 final var info = new ProjectInfo(project); 3.67 info.setVersions(versionDao.list(project)); 3.68 info.setComponents(componentDao.list(project)); 3.69 @@ -106,22 +84,19 @@ 3.70 } 3.71 3.72 // Select Version 3.73 - final int vid = syncParamWithSession(req, PARAMETER_SELECTED_VERSION, SESSION_ATTR_SELECTED_VERSION); 3.74 + final int vid = Functions.parseIntOrZero(pathParameters.get("version")); 3.75 if (vid > 0) { 3.76 viewModel.setVersionFilter(versionDao.find(vid)); 3.77 - } else { 3.78 - // NULL for version means: show all unassigned 3.79 - viewModel.setVersionFilter(null); 3.80 + } 3.81 + // TODO: don't treat unknown == unassigned - send 404 for unknown and introduce special word for unassigned 3.82 + 3.83 + // Select Component 3.84 + final int cid = Functions.parseIntOrZero(pathParameters.get("component")); 3.85 + if (cid > 0) { 3.86 + viewModel.setComponentFilter(componentDao.find(cid)); 3.87 } 3.88 3.89 - // Select Component 3.90 - final int cid = syncParamWithSession(req, PARAMETER_SELECTED_COMPONENT, SESSION_ATTR_SELECTED_COMPONENT); 3.91 - if (cid > 0) { 3.92 - viewModel.setComponentFilter(componentDao.find(cid)); 3.93 - } else if (cid <= 0) { 3.94 - // -1 means: filter for unassigned, null means: show all 3.95 - viewModel.setComponentFilter(new Component(-1)); 3.96 - } 3.97 + // TODO: distinguish all/unassigned for components 3.98 } 3.99 3.100 private ResponseType forwardView(HttpServletRequest req, ProjectView viewModel, String name) { 3.101 @@ -135,7 +110,7 @@ 3.102 @RequestMapping(method = HttpMethod.GET) 3.103 public ResponseType index(HttpServletRequest req, DataAccessObjects dao) throws SQLException { 3.104 final var viewModel = new ProjectView(); 3.105 - populate(viewModel, req, dao); 3.106 + populate(viewModel, null, dao); 3.107 3.108 final var projectDao = dao.getProjectDao(); 3.109 final var versionDao = dao.getVersionDao(); 3.110 @@ -148,30 +123,38 @@ 3.111 return forwardView(req, viewModel, "projects"); 3.112 } 3.113 3.114 - private void configure(ProjectEditView viewModel, Project project, DataAccessObjects dao) throws SQLException { 3.115 + private void configureProjectEditor(ProjectEditView viewModel, Project project, DataAccessObjects dao) throws SQLException { 3.116 viewModel.setProject(project); 3.117 viewModel.setUsers(dao.getUserDao().list()); 3.118 } 3.119 3.120 - @RequestMapping(requestPath = "edit", method = HttpMethod.GET) 3.121 - public ResponseType edit(HttpServletRequest req, DataAccessObjects dao) throws SQLException { 3.122 + @RequestMapping(requestPath = "$project/edit", method = HttpMethod.GET) 3.123 + public ResponseType edit(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParams, DataAccessObjects dao) throws IOException, SQLException { 3.124 final var viewModel = new ProjectEditView(); 3.125 - populate(viewModel, req, dao); 3.126 + populate(viewModel, pathParams, dao); 3.127 3.128 - final var project = Optional.ofNullable(viewModel.getProjectInfo()) 3.129 - .map(ProjectInfo::getProject) 3.130 - .orElse(new Project(-1)); 3.131 - configure(viewModel, project, dao); 3.132 + if (viewModel.getProjectInfo() == null) { 3.133 + resp.sendError(HttpServletResponse.SC_NOT_FOUND); 3.134 + return ResponseType.NONE; 3.135 + } 3.136 3.137 + configureProjectEditor(viewModel, viewModel.getProjectInfo().getProject(), dao); 3.138 + return forwardView(req, viewModel, "project-form"); 3.139 + } 3.140 + 3.141 + @RequestMapping(requestPath = "create", method = HttpMethod.GET) 3.142 + public ResponseType create(HttpServletRequest req, DataAccessObjects dao) throws SQLException { 3.143 + final var viewModel = new ProjectEditView(); 3.144 + populate(viewModel, null, dao); 3.145 + configureProjectEditor(viewModel, new Project(-1), dao); 3.146 return forwardView(req, viewModel, "project-form"); 3.147 } 3.148 3.149 @RequestMapping(requestPath = "commit", method = HttpMethod.POST) 3.150 - public ResponseType commit(HttpServletRequest req, DataAccessObjects dao) throws SQLException { 3.151 + public ResponseType commit(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException { 3.152 3.153 - Project project = new Project(-1); 3.154 try { 3.155 - project = new Project(getParameter(req, Integer.class, "pid").orElseThrow()); 3.156 + final var project = new Project(getParameter(req, Integer.class, "pid").orElseThrow()); 3.157 project.setName(getParameter(req, String.class, "name").orElseThrow()); 3.158 getParameter(req, String.class, "description").ifPresent(project::setDescription); 3.159 getParameter(req, String.class, "repoUrl").ifPresent(project::setRepoUrl); 3.160 @@ -187,30 +170,25 @@ 3.161 3.162 return ResponseType.HTML; 3.163 } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) { 3.164 - LOG.warn("Form validation failure: {}", ex.getMessage()); 3.165 - LOG.debug("Details:", ex); 3.166 - final var viewModel = new ProjectEditView(); 3.167 - populate(viewModel, req, dao); 3.168 - configure(viewModel, project, dao); 3.169 - // TODO: error text 3.170 - return forwardView(req, viewModel, "project-form"); 3.171 + resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED); 3.172 + // TODO: implement - fix issue #21 3.173 + return ResponseType.NONE; 3.174 } 3.175 } 3.176 3.177 - @RequestMapping(requestPath = "view", method = HttpMethod.GET) 3.178 - public ResponseType view(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws SQLException, IOException { 3.179 + @RequestMapping(requestPath = "$project/versions/$version", method = HttpMethod.GET) 3.180 + public ResponseType view(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParams, DataAccessObjects dao) throws SQLException, IOException { 3.181 final var viewModel = new ProjectDetailsView(); 3.182 - populate(viewModel, req, dao); 3.183 + populate(viewModel, pathParams, dao); 3.184 + final var version = viewModel.getVersionFilter(); 3.185 3.186 - if (viewModel.getProjectInfo() == null) { 3.187 - resp.sendError(HttpServletResponse.SC_NOT_FOUND, "No project selected."); 3.188 + if (viewModel.getProjectInfo() == null || version == null) { 3.189 + resp.sendError(HttpServletResponse.SC_NOT_FOUND); 3.190 return ResponseType.NONE; 3.191 } 3.192 3.193 final var issueDao = dao.getIssueDao(); 3.194 3.195 - final var version = viewModel.getVersionFilter(); 3.196 - 3.197 final var detailView = viewModel.getProjectDetails(); 3.198 final var issues = issueDao.list(version); 3.199 for (var issue : issues) issueDao.joinVersionInformation(issue); 3.200 @@ -224,15 +202,14 @@ 3.201 return forwardView(req, viewModel, "project-details"); 3.202 } 3.203 3.204 - @RequestMapping(requestPath = "versions", method = HttpMethod.GET) 3.205 - public ResponseType versions(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException, SQLException { 3.206 + @RequestMapping(requestPath = "$project/versions/", method = HttpMethod.GET) 3.207 + public ResponseType versions(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObjects dao) throws IOException, SQLException { 3.208 final var viewModel = new VersionsView(); 3.209 - populate(viewModel, req, dao); 3.210 - viewModel.setVersionFilter(null); 3.211 + populate(viewModel, pathParameters, dao); 3.212 3.213 final var projectInfo = viewModel.getProjectInfo(); 3.214 if (projectInfo == null) { 3.215 - resp.sendError(HttpServletResponse.SC_NOT_FOUND, "No project selected."); 3.216 + resp.sendError(HttpServletResponse.SC_NOT_FOUND); 3.217 return ResponseType.NONE; 3.218 } 3.219 3.220 @@ -244,49 +221,60 @@ 3.221 return forwardView(req, viewModel, "versions"); 3.222 } 3.223 3.224 - @RequestMapping(requestPath = "versions/edit", method = HttpMethod.GET) 3.225 - public ResponseType editVersion(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException, SQLException { 3.226 + @RequestMapping(requestPath = "$project/versions/$version/edit", method = HttpMethod.GET) 3.227 + public ResponseType editVersion(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObjects dao) throws IOException, SQLException { 3.228 final var viewModel = new VersionEditView(); 3.229 - populate(viewModel, req, dao); 3.230 + populate(viewModel, pathParameters, dao); 3.231 3.232 - if (viewModel.getProjectInfo() == null) { 3.233 - resp.sendError(HttpServletResponse.SC_NOT_FOUND, "No project selected."); 3.234 + if (viewModel.getProjectInfo() == null || viewModel.getVersionFilter() == null) { 3.235 + resp.sendError(HttpServletResponse.SC_NOT_FOUND); 3.236 return ResponseType.NONE; 3.237 } 3.238 3.239 - viewModel.setVersion(Optional.ofNullable(viewModel.getVersionFilter()).orElse(new Version(-1))); 3.240 + viewModel.setVersion(viewModel.getVersionFilter()); 3.241 3.242 return forwardView(req, viewModel, "version-form"); 3.243 } 3.244 3.245 - @RequestMapping(requestPath = "versions/commit", method = HttpMethod.POST) 3.246 - public ResponseType commitVersion(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws SQLException { 3.247 + @RequestMapping(requestPath = "$project/create-version", method = HttpMethod.GET) 3.248 + public ResponseType createVersion(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObjects dao) throws IOException, SQLException { 3.249 + final var viewModel = new VersionEditView(); 3.250 + populate(viewModel, pathParameters, dao); 3.251 3.252 - var version = new Version(-1); 3.253 + if (viewModel.getProjectInfo() == null) { 3.254 + resp.sendError(HttpServletResponse.SC_NOT_FOUND); 3.255 + return ResponseType.NONE; 3.256 + } 3.257 + 3.258 + viewModel.setVersion(viewModel.getVersionFilter()); 3.259 + 3.260 + return forwardView(req, viewModel, "version-form"); 3.261 + } 3.262 + 3.263 + @RequestMapping(requestPath = "commit-version", method = HttpMethod.POST) 3.264 + public ResponseType commitVersion(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException { 3.265 + 3.266 try { 3.267 final var project = new Project(getParameter(req, Integer.class, "pid").orElseThrow()); 3.268 - version = new Version(getParameter(req, Integer.class, "id").orElseThrow()); 3.269 + final var version = new Version(getParameter(req, Integer.class, "id").orElseThrow()); 3.270 version.setName(getParameter(req, String.class, "name").orElseThrow()); 3.271 getParameter(req, Integer.class, "ordinal").ifPresent(version::setOrdinal); 3.272 version.setStatus(VersionStatus.valueOf(getParameter(req, String.class, "status").orElseThrow())); 3.273 dao.getVersionDao().saveOrUpdate(version, project); 3.274 3.275 - setRedirectLocation(req, "./projects/versions?pid=" + project.getId()); 3.276 + // TODO: improve building the redirect location 3.277 + setRedirectLocation(req, "./projects/" + project.getId() + "/versions/"); 3.278 setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL); 3.279 } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) { 3.280 - LOG.warn("Form validation failure: {}", ex.getMessage()); 3.281 - LOG.debug("Details:", ex); 3.282 - final var viewModel = new VersionEditView(); 3.283 - populate(viewModel, req, dao); 3.284 - viewModel.setVersion(version); 3.285 - // TODO: set Error Text 3.286 - return forwardView(req, viewModel, "version-form"); 3.287 + resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED); 3.288 + // TODO: implement - fix issue #21 3.289 + return ResponseType.NONE; 3.290 } 3.291 3.292 return ResponseType.HTML; 3.293 } 3.294 3.295 - private void configure(IssueEditView viewModel, Issue issue, DataAccessObjects dao) throws SQLException { 3.296 + private void configureProjectEditor(IssueEditView viewModel, Issue issue, DataAccessObjects dao) throws SQLException { 3.297 issue.setProject(viewModel.getProjectInfo().getProject()); 3.298 viewModel.setIssue(issue); 3.299 viewModel.configureVersionSelectors(viewModel.getProjectInfo().getVersions()); 3.300 @@ -296,30 +284,52 @@ 3.301 } 3.302 } 3.303 3.304 - @RequestMapping(requestPath = "issues/edit", method = HttpMethod.GET) 3.305 - public ResponseType editIssue(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws SQLException { 3.306 + @RequestMapping(requestPath = "$project/issues/$issue/edit", method = HttpMethod.GET) 3.307 + public ResponseType editIssue(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObjects dao) throws IOException, SQLException { 3.308 final var viewModel = new IssueEditView(); 3.309 - populate(viewModel, req, dao); 3.310 + populate(viewModel, pathParameters, dao); 3.311 3.312 - final var issueParam = getParameter(req, Integer.class, "issue"); 3.313 - if (issueParam.isPresent()) { 3.314 - final var issueDao = dao.getIssueDao(); 3.315 - final var issue = issueDao.find(issueParam.get()); 3.316 - issueDao.joinVersionInformation(issue); 3.317 - req.getSession().setAttribute(SESSION_ATTR_SELECTED_PROJECT, issue.getProject().getId()); 3.318 - configure(viewModel, issue, dao); 3.319 - } else { 3.320 - configure(viewModel, new Issue(-1), dao); 3.321 + final var projectInfo = viewModel.getProjectInfo(); 3.322 + if (projectInfo == null) { 3.323 + resp.sendError(HttpServletResponse.SC_NOT_FOUND); 3.324 + return ResponseType.NONE; 3.325 } 3.326 3.327 + final var issueDao = dao.getIssueDao(); 3.328 + final var issue = issueDao.find(Functions.parseIntOrZero(pathParameters.get("issue"))); 3.329 + if (issue == null) { 3.330 + resp.sendError(HttpServletResponse.SC_NOT_FOUND); 3.331 + return ResponseType.NONE; 3.332 + } 3.333 + 3.334 + issueDao.joinVersionInformation(issue); 3.335 + configureProjectEditor(viewModel, issue, dao); 3.336 + 3.337 return forwardView(req, viewModel, "issue-form"); 3.338 } 3.339 3.340 - @RequestMapping(requestPath = "issues/commit", method = HttpMethod.POST) 3.341 - public ResponseType commitIssue(HttpServletRequest req, DataAccessObjects dao) throws SQLException { 3.342 - Issue issue = new Issue(-1); 3.343 + @RequestMapping(requestPath = "$project/create-issue", method = HttpMethod.GET) 3.344 + public ResponseType createIssue(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObjects dao) throws IOException, SQLException { 3.345 + final var viewModel = new IssueEditView(); 3.346 + populate(viewModel, pathParameters, dao); 3.347 + 3.348 + final var projectInfo = viewModel.getProjectInfo(); 3.349 + if (projectInfo == null) { 3.350 + resp.sendError(HttpServletResponse.SC_NOT_FOUND); 3.351 + return ResponseType.NONE; 3.352 + } 3.353 + 3.354 + final var issue = new Issue(-1); 3.355 + issue.setProject(projectInfo.getProject()); 3.356 + configureProjectEditor(viewModel, issue, dao); 3.357 + 3.358 + return forwardView(req, viewModel, "issue-form"); 3.359 + } 3.360 + 3.361 + @RequestMapping(requestPath = "commit-issue", method = HttpMethod.POST) 3.362 + public ResponseType commitIssue(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException { 3.363 try { 3.364 - issue = new Issue(getParameter(req, Integer.class, "id").orElseThrow()); 3.365 + final var issue = new Issue(getParameter(req, Integer.class, "id").orElseThrow()); 3.366 issue.setProject(new Project(getParameter(req, Integer.class, "pid").orElseThrow())); 3.367 getParameter(req, String.class, "category").map(IssueCategory::valueOf).ifPresent(issue::setCategory); 3.368 getParameter(req, String.class, "status").map(IssueStatus::valueOf).ifPresent(issue::setStatus); 3.369 @@ -343,24 +353,19 @@ 3.370 3.371 dao.getIssueDao().saveOrUpdate(issue, issue.getProject()); 3.372 3.373 - // specifying the issue parameter keeps the edited issue as menu item 3.374 - setRedirectLocation(req, "./projects/view?pid=" + issue.getProject().getId()); 3.375 + // TODO: fix issue #14 3.376 + setRedirectLocation(req, "./projects/" + issue.getProject().getId() + "/versions/"); 3.377 setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL); 3.378 3.379 return ResponseType.HTML; 3.380 } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) { 3.381 - // TODO: set request attribute with error text 3.382 - LOG.warn("Form validation failure: {}", ex.getMessage()); 3.383 - LOG.debug("Details:", ex); 3.384 - final var viewModel = new IssueEditView(); 3.385 - populate(viewModel, req, dao); 3.386 - configure(viewModel, issue, dao); 3.387 - // TODO: set Error Text 3.388 - return forwardView(req, viewModel, "issue-form"); 3.389 + resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED); 3.390 + // TODO: implement - fix issue #21 3.391 + return ResponseType.NONE; 3.392 } 3.393 } 3.394 3.395 - @RequestMapping(requestPath = "issues/comment", method = HttpMethod.POST) 3.396 + @RequestMapping(requestPath = "commit-issue-comment", method = HttpMethod.POST) 3.397 public ResponseType commentIssue(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws SQLException, IOException { 3.398 final var issueIdParam = getParameter(req, Integer.class, "issueid"); 3.399 if (issueIdParam.isEmpty()) { 3.400 @@ -383,20 +388,15 @@ 3.401 3.402 dao.getIssueDao().saveComment(issueComment); 3.403 3.404 - // specifying the issue parameter keeps the edited issue as menu item 3.405 - setRedirectLocation(req, "./projects/issues/edit?issue=" + issue.getId()); 3.406 + // TODO: fix redirect location (e.g. after fixing #24) 3.407 + setRedirectLocation(req, "./projects/" + issue.getProject().getId()+"/issues/"+issue.getId()+"/edit"); 3.408 setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL); 3.409 3.410 return ResponseType.HTML; 3.411 } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) { 3.412 - // TODO: set request attribute with error text 3.413 - LOG.warn("Form validation failure: {}", ex.getMessage()); 3.414 - LOG.debug("Details:", ex); 3.415 - final var viewModel = new IssueEditView(); 3.416 - populate(viewModel, req, dao); 3.417 - configure(viewModel, issue, dao); 3.418 - // TODO: set Error Text 3.419 - return forwardView(req, viewModel, "issue-form"); 3.420 + resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED); 3.421 + // TODO: implement - fix issue #21 3.422 + return ResponseType.NONE; 3.423 } 3.424 } 3.425 }
4.1 --- a/src/main/webapp/WEB-INF/jsp/issue-form.jsp Thu Oct 15 18:36:05 2020 +0200 4.2 +++ b/src/main/webapp/WEB-INF/jsp/issue-form.jsp Thu Oct 15 20:02:30 2020 +0200 4.3 @@ -31,7 +31,8 @@ 4.4 <jsp:useBean id="viewmodel" type="de.uapcore.lightpit.viewmodel.IssueEditView" scope="request"/> 4.5 <c:set var="issue" scope="page" value="${viewmodel.issue}" /> 4.6 4.7 -<form action="./projects/issues/commit" method="post"> 4.8 +<%-- TODO: change to ./issues/commit --%> 4.9 +<form action="./projects/commit-issue" method="post"> 4.10 <table class="formtable fullwidth"> 4.11 <colgroup> 4.12 <col> 4.13 @@ -152,15 +153,8 @@ 4.14 <tr> 4.15 <td colspan="2"> 4.16 <input type="hidden" name="id" value="${issue.id}"/> 4.17 - <c:choose> 4.18 - <c:when test="${not empty issue.project}"> 4.19 - <c:set var="cancelUrl">./projects/view?pid=${issue.project.id}</c:set> 4.20 - </c:when> 4.21 - <c:otherwise> 4.22 - <c:set var="cancelUrl">./projects/</c:set> 4.23 - </c:otherwise> 4.24 - </c:choose> 4.25 - <a href="${cancelUrl}" class="button"> 4.26 + <%-- TODO: fix #14 --%> 4.27 + <a href="./projects/${issue.project.id}/versions/" class="button"> 4.28 <fmt:message bundle="${lightpit_bundle}" key="button.cancel"/> 4.29 </a> 4.30 <button type="submit"><fmt:message bundle="${lightpit_bundle}" key="button.okay"/></button> 4.31 @@ -172,7 +166,7 @@ 4.32 <hr class="comments-separator"/> 4.33 <h2><fmt:message key="issue.comments"/></h2> 4.34 <c:if test="${viewmodel.issue.id ge 0}"> 4.35 -<form id="comment-form" action="./projects/issues/comment" method="post"> 4.36 +<form id="comment-form" action="./projects/commit-issue-comment" method="post"> 4.37 <table class="formtable fullwidth"> 4.38 <tbody> 4.39 <tr>
5.1 --- a/src/main/webapp/WEB-INF/jsp/issues.jsp Thu Oct 15 18:36:05 2020 +0200 5.2 +++ b/src/main/webapp/WEB-INF/jsp/issues.jsp Thu Oct 15 20:02:30 2020 +0200 5.3 @@ -28,6 +28,9 @@ 5.4 <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 5.5 <%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> 5.6 5.7 +<h1>TODO: REWRITE THIS PAGE</h1> 5.8 +<%-- 5.9 +TODO: rewrite 5.10 <jsp:useBean id="viewmodel" type="de.uapcore.lightpit.viewmodel.IssuesView" scope="request"/> 5.11 <c:set var="project" scope="page" value="${viewmodel.project}"/> 5.12 <c:set var="version" scope="page" value="${viewmodel.version}"/> 5.13 @@ -50,4 +53,5 @@ 5.14 </div> 5.15 5.16 <c:set var="issues" value="${viewmodel.issues}"/> 5.17 -<%@include file="../jspf/issue-list.jspf"%> 5.18 \ No newline at end of file 5.19 +<%@include file="../jspf/issue-list.jspf"%> 5.20 +--%> 5.21 \ No newline at end of file
6.1 --- a/src/main/webapp/WEB-INF/jsp/project-details.jsp Thu Oct 15 18:36:05 2020 +0200 6.2 +++ b/src/main/webapp/WEB-INF/jsp/project-details.jsp Thu Oct 15 20:02:30 2020 +0200 6.3 @@ -35,10 +35,10 @@ 6.4 6.5 <div id="tool-area"> 6.6 <c:if test="${not empty viewmodel.versionFilter}"> 6.7 - <a href="./projects/versions/edit?vid=${viewmodel.versionFilter.id}" class="button"><fmt:message key="button.version.edit"/></a> 6.8 + <a href="./projects/${project.id}/versions/${viewmodel.versionFilter.id}/edit" class="button"><fmt:message key="button.version.edit"/></a> 6.9 </c:if> 6.10 - <a href="./projects/versions/edit?vid=-1" class="button"><fmt:message key="button.version.create"/></a> 6.11 - <a href="./projects/issues/edit?pid=${project.id}" class="button"><fmt:message key="button.issue.create"/></a> 6.12 + <a href="./projects/${project.id}/create-version" class="button"><fmt:message key="button.version.create"/></a> 6.13 + <a href="./projects/${project.id}/create-issue" class="button"><fmt:message key="button.issue.create"/></a> 6.14 </div> 6.15 6.16 <h2><fmt:message key="progress" /></h2>
7.1 --- a/src/main/webapp/WEB-INF/jsp/project-navmenu.jsp Thu Oct 15 18:36:05 2020 +0200 7.2 +++ b/src/main/webapp/WEB-INF/jsp/project-navmenu.jsp Thu Oct 15 20:02:30 2020 +0200 7.3 @@ -33,20 +33,20 @@ 7.4 <c:forEach var="projectInfo" items="${viewmodel.projectList}"> 7.5 <c:set var="isActive" value="${viewmodel.projectInfo.project eq projectInfo.project}" /> 7.6 <div class="menuEntry level-0" <c:if test="${isActive}">data-active</c:if> > 7.7 - <a href="projects/versions?pid=${projectInfo.project.id}"> 7.8 + <a href="projects/${projectInfo.project.id}/versions/"> 7.9 <c:out value="${projectInfo.project.name}"/> 7.10 </a> 7.11 </div> 7.12 <c:if test="${isActive}"> 7.13 <!-- VERSIONS --> 7.14 <div class="menuEntry level-1"> 7.15 - <a href="projects/versions?pid=${projectInfo.project.id}"> 7.16 + <a href="projects/${projectInfo.project.id}/versions/"> 7.17 <fmt:message key="navmenu.versions"/> 7.18 </a> 7.19 </div> 7.20 <div class="menuEntry level-2"> 7.21 <div class="navmenu-icon" style="background: black"></div> 7.22 - <a href="projects/view?pid=${projectInfo.project.id}&vid=-1"> 7.23 + <a href="projects/${projectInfo.project.id}/versions/unassigned"> 7.24 <fmt:message key="navmenu.unassigned" /> 7.25 </a> 7.26 </div> 7.27 @@ -55,26 +55,27 @@ 7.28 <div class="menuEntry level-2" <c:if test="${isVersionActive}">data-active</c:if> 7.29 title="<fmt:message key="version.status.${version.status}" />"> 7.30 <div class="navmenu-icon version-${version.status}"></div> 7.31 - <a href="projects/view?pid=${projectInfo.project.id}&vid=${version.id}"> 7.32 + <a href="projects/${projectInfo.project.id}/versions/${version.id}"> 7.33 <c:out value="${version.name}"/> 7.34 </a> 7.35 </div> 7.36 </c:forEach> 7.37 - <!-- COMPONENTS --> 7.38 + <%-- COMPONENTS 7.39 + TODO: find a way to combine version and component into one URL 7.40 <div class="menuEntry level-1"> 7.41 - <a href="projects/components?pid=${projectInfo.project.id}"> 7.42 + <a href="projects/${projectInfo.project.id}/components/"> 7.43 <fmt:message key="navmenu.components"/> 7.44 </a> 7.45 </div> 7.46 <div class="menuEntry level-2"> 7.47 <div class="navmenu-icon" style="background: black"></div> 7.48 - <a href="projects/view?pid=${projectInfo.project.id}&cid=0"> 7.49 + <a href="projects/${projectInfo.project.id}/components/"> 7.50 <fmt:message key="navmenu.all" /> 7.51 </a> 7.52 </div> 7.53 <div class="menuEntry level-2"> 7.54 <div class="navmenu-icon" style="background: black"></div> 7.55 - <a href="projects/view?pid=${projectInfo.project.id}&cid=-1"> 7.56 + <a href="projects/${projectInfo.project.id}/components/unassigned"> 7.57 <fmt:message key="navmenu.unassigned" /> 7.58 </a> 7.59 </div> 7.60 @@ -87,5 +88,6 @@ 7.61 </a> 7.62 </div> 7.63 </c:forEach> 7.64 + --%> 7.65 </c:if> 7.66 </c:forEach>
8.1 --- a/src/main/webapp/WEB-INF/jsp/projects.jsp Thu Oct 15 18:36:05 2020 +0200 8.2 +++ b/src/main/webapp/WEB-INF/jsp/projects.jsp Thu Oct 15 20:02:30 2020 +0200 8.3 @@ -37,7 +37,7 @@ 8.4 </c:if> 8.5 8.6 <div id="tool-area"> 8.7 - <a href="./projects/edit?pid=-1" class="button"><fmt:message key="button.create"/></a> 8.8 + <a href="./projects/create" class="button"><fmt:message key="button.create"/></a> 8.9 </div> 8.10 8.11 <c:if test="${not empty viewmodel.projectList}"> 8.12 @@ -68,8 +68,8 @@ 8.13 <c:forEach var="projectInfo" items="${viewmodel.projectList}"> 8.14 <c:set var="project" scope="page" value="${projectInfo.project}"/> 8.15 <tr class="nowrap"> 8.16 - <td style="width: 2em;"><a href="./projects/edit?pid=${project.id}">✎</a></td> 8.17 - <td><a href="./projects/versions?pid=${project.id}"><c:out value="${project.name}"/></a> 8.18 + <td style="width: 2em;"><a href="./projects/${project.id}/edit">✎</a></td> 8.19 + <td><a href="./projects/${project.id}/versions/"><c:out value="${project.name}"/></a> 8.20 </td> 8.21 <td> 8.22 <c:if test="${not empty project.repoUrl}"> 8.23 @@ -79,12 +79,12 @@ 8.24 </td> 8.25 <td class="hright"> 8.26 <c:if test="${not empty projectInfo.latestVersion}"> 8.27 - <a href="./projects/view?pid=${project.id}&vid=${projectInfo.latestVersion.id}"><c:out value="${projectInfo.latestVersion.name}"/></a> 8.28 + <a href="./projects/${project.id}/versions/${projectInfo.latestVersion.id}/"><c:out value="${projectInfo.latestVersion.name}"/></a> 8.29 </c:if> 8.30 </td> 8.31 <td class="hright"> 8.32 <c:if test="${not empty projectInfo.nextVersion}"> 8.33 - <a href="./projects/view?pid=${project.id}&vid=${projectInfo.nextVersion.id}"><c:out value="${projectInfo.nextVersion.name}"/></a> 8.34 + <a href="./projects/${project.id}/versions/${projectInfo.nextVersion.id}/"><c:out value="${projectInfo.nextVersion.name}"/></a> 8.35 </c:if> 8.36 </td> 8.37 <td class="hright">${projectInfo.issueSummary.open}</td>
9.1 --- a/src/main/webapp/WEB-INF/jsp/version-form.jsp Thu Oct 15 18:36:05 2020 +0200 9.2 +++ b/src/main/webapp/WEB-INF/jsp/version-form.jsp Thu Oct 15 20:02:30 2020 +0200 9.3 @@ -32,7 +32,7 @@ 9.4 <c:set var="version" scope="page" value="${viewmodel.version}"/> 9.5 <c:set var="project" scope="page" value="${viewmodel.projectInfo.project}"/> 9.6 9.7 -<form action="./projects/versions/commit" method="post"> 9.8 +<form action="./projects/commit-version" method="post"> 9.9 <table class="formtable" style="width: 35ch"> 9.10 <colgroup> 9.11 <col> 9.12 @@ -73,7 +73,7 @@ 9.13 <tr> 9.14 <td colspan="2"> 9.15 <input type="hidden" name="id" value="${version.id}"/> 9.16 - <a href="./projects/versions?pid=${project.id}" class="button"> 9.17 + <a href="./projects/${project.id}/versions/" class="button"> 9.18 <fmt:message bundle="${lightpit_bundle}" key="button.cancel"/> 9.19 </a> 9.20 <button type="submit"><fmt:message bundle="${lightpit_bundle}" key="button.okay"/></button>
10.1 --- a/src/main/webapp/WEB-INF/jsp/versions.jsp Thu Oct 15 18:36:05 2020 +0200 10.2 +++ b/src/main/webapp/WEB-INF/jsp/versions.jsp Thu Oct 15 20:02:30 2020 +0200 10.3 @@ -34,8 +34,8 @@ 10.4 <%@include file="../jspf/project-header.jspf"%> 10.5 10.6 <div id="tool-area"> 10.7 - <a href="./projects/versions/edit?vid=-1" class="button"><fmt:message key="button.version.create"/></a> 10.8 - <a href="./projects/issues/edit?pid=${project.id}" class="button"><fmt:message key="button.issue.create"/></a> 10.9 + <a href="./projects/${project.id}/create-version" class="button"><fmt:message key="button.version.create"/></a> 10.10 + <a href="./projects/${project.id}/create-issue" class="button"><fmt:message key="button.issue.create"/></a> 10.11 </div> 10.12 10.13 <h2><fmt:message key="progress" /></h2> 10.14 @@ -78,9 +78,9 @@ 10.15 <tbody> 10.16 <c:forEach var="versionInfo" items="${viewmodel.versionInfo}" > 10.17 <tr> 10.18 - <td rowspan="2" style="width: 2em;"><a href="./projects/versions/edit?vid=${versionInfo.version.id}">✎</a></td> 10.19 + <td rowspan="2" style="width: 2em;"><a href="./projects/${project.id}/versions/${versionInfo.version.id}/edit">✎</a></td> 10.20 <td rowspan="2"> 10.21 - <a href="projects/view?pid=${viewmodel.projectInfo.project.id}&vid=${versionInfo.version.id}"> 10.22 + <a href="./projects/${project.id}/versions/${versionInfo.version.id}"> 10.23 <c:out value="${versionInfo.version.name}"/> 10.24 </a> 10.25 <div class="version-tag version-${versionInfo.version.status}">
11.1 --- a/src/main/webapp/WEB-INF/jspf/issue-list.jspf Thu Oct 15 18:36:05 2020 +0200 11.2 +++ b/src/main/webapp/WEB-INF/jspf/issue-list.jspf Thu Oct 15 20:02:30 2020 +0200 11.3 @@ -18,7 +18,7 @@ 11.4 <tr> 11.5 <td> 11.6 <span class="phase-${issue.status.phase}"> 11.7 - <a href="./projects/issues/edit?issue=${issue.id}"> 11.8 + <a href="./projects/${issue.project.id}/issues/${issue.id}/edit"> 11.9 #${issue.id} - <c:out value="${issue.subject}" /> 11.10 </a> 11.11 </span>