changes request mapping to contain project and version ID as path parameters (this removes the session storage)

Thu, 15 Oct 2020 20:02:30 +0200

author
Mike Becker <universe@uap-core.de>
date
Thu, 15 Oct 2020 20:02:30 +0200
changeset 131
67df332e3146
parent 130
7ef369744fd1
child 132
57e5a4624919

changes request mapping to contain project and version ID as path parameters (this removes the session storage)

src/main/java/de/uapcore/lightpit/AbstractLightPITServlet.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/Functions.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/issue-form.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/issues.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/project-details.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/project-navmenu.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/projects.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/version-form.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/versions.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jspf/issue-list.jspf file | annotate | diff | comparison | revisions
     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}">&#x270e;</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">&#x270e;</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}">&#x270e;</a></td>
   10.19 +            <td rowspan="2" style="width: 2em;"><a href="./projects/${project.id}/versions/${versionInfo.version.id}/edit">&#x270e;</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}&nbsp;-&nbsp;<c:out value="${issue.subject}" />
   11.10                      </a>
   11.11                  </span>

mercurial