adds custom node names - fixes #27

Thu, 22 Oct 2020 13:03:26 +0200

author
Mike Becker <universe@uap-core.de>
date
Thu, 22 Oct 2020 13:03:26 +0200
changeset 138
e2aa673dd473
parent 137
a7e543ab0c5f
child 139
6abc75d213ef

adds custom node names - fixes #27

setup/postgres/psql_create_tables.sql file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/ComponentDao.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/Functions.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/ProjectDao.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/VersionDao.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/postgres/PGComponentDao.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/postgres/PGIssueDao.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/postgres/PGProjectDao.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/postgres/PGVersionDao.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/entities/Component.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/entities/Project.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/entities/Version.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java file | annotate | diff | comparison | revisions
src/main/resources/localization/projects.properties file | annotate | diff | comparison | revisions
src/main/resources/localization/projects_de.properties file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/component-form.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/project-form.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/version-form.jsp file | annotate | diff | comparison | revisions
     1.1 --- a/setup/postgres/psql_create_tables.sql	Thu Oct 22 12:00:34 2020 +0200
     1.2 +++ b/setup/postgres/psql_create_tables.sql	Thu Oct 22 13:03:26 2020 +0200
     1.3 @@ -12,6 +12,7 @@
     1.4  create table lpit_project (
     1.5      projectid       serial          primary key,
     1.6      name            varchar(20)     not null unique,
     1.7 +    node            varchar(20)     not null unique,
     1.8      description     varchar(200),
     1.9      repoUrl         varchar(50),
    1.10      owner           integer         references lpit_user(userid)
    1.11 @@ -29,21 +30,26 @@
    1.12      versionid       serial          primary key,
    1.13      project         integer         not null references lpit_project(projectid),
    1.14      name            varchar(20)     not null,
    1.15 +    node            varchar(20)     not null,
    1.16      ordinal         integer         not null default 0,
    1.17      status          version_status  not null default 'Future'
    1.18  );
    1.19  
    1.20 +create unique index lpit_version_node_unique on lpit_version(project, node);
    1.21  
    1.22  create table lpit_component (
    1.23      id              serial          primary key,
    1.24      project         integer         not null references lpit_project(projectid),
    1.25      name            varchar(20)     not null,
    1.26 +    node            varchar(20)     not null,
    1.27      color           char(6)         not null default '000000',
    1.28      ordinal         integer         not null default 0,
    1.29      description     text,
    1.30      lead            integer         references lpit_user(userid)
    1.31  );
    1.32  
    1.33 +create unique index lpit_component_node_unique on lpit_component(project, node);
    1.34 +
    1.35  create type issue_status as enum (
    1.36      'InSpecification',
    1.37      'ToDo',
     2.1 --- a/src/main/java/de/uapcore/lightpit/dao/ComponentDao.java	Thu Oct 22 12:00:34 2020 +0200
     2.2 +++ b/src/main/java/de/uapcore/lightpit/dao/ComponentDao.java	Thu Oct 22 13:03:26 2020 +0200
     2.3 @@ -31,6 +31,8 @@
     2.4  import de.uapcore.lightpit.entities.Component;
     2.5  import de.uapcore.lightpit.entities.Project;
     2.6  
     2.7 +import java.sql.SQLException;
     2.8 +
     2.9  public interface ComponentDao extends ChildEntityDao<Component, Project> {
    2.10 -
    2.11 +    Component findByNode(Project parent, String node) throws SQLException;
    2.12  }
     3.1 --- a/src/main/java/de/uapcore/lightpit/dao/Functions.java	Thu Oct 22 12:00:34 2020 +0200
     3.2 +++ b/src/main/java/de/uapcore/lightpit/dao/Functions.java	Thu Oct 22 13:03:26 2020 +0200
     3.3 @@ -28,10 +28,9 @@
     3.4   */
     3.5  package de.uapcore.lightpit.dao;
     3.6  
     3.7 -import java.sql.Date;
     3.8 -import java.sql.PreparedStatement;
     3.9 -import java.sql.SQLException;
    3.10 -import java.sql.Types;
    3.11 +import java.sql.*;
    3.12 +import java.util.ArrayList;
    3.13 +import java.util.List;
    3.14  import java.util.Optional;
    3.15  import java.util.function.Function;
    3.16  
    3.17 @@ -65,6 +64,33 @@
    3.18          }
    3.19      }
    3.20  
    3.21 +    @FunctionalInterface
    3.22 +    public interface ResultSetMapper<T> {
    3.23 +        T apply(ResultSet rs) throws SQLException;
    3.24 +    }
    3.25 +
    3.26 +    public static <T> List<T> list(PreparedStatement stmt, ResultSetMapper<T> mapper) throws SQLException {
    3.27 +        List<T> results = new ArrayList<>();
    3.28 +        try (var result = stmt.executeQuery()) {
    3.29 +            while (result.next()) {
    3.30 +                final var project = mapper.apply(result);
    3.31 +                results.add(project);
    3.32 +            }
    3.33 +        }
    3.34 +        return results;
    3.35 +    }
    3.36 +
    3.37 +    public static <T> T find(PreparedStatement stmt, ResultSetMapper<T> mapper) throws SQLException {
    3.38 +        try (var result = stmt.executeQuery()) {
    3.39 +            if (result.next()) {
    3.40 +                final var ent = mapper.apply(result);
    3.41 +                return ent;
    3.42 +            } else {
    3.43 +                return null;
    3.44 +            }
    3.45 +        }
    3.46 +    }
    3.47 +
    3.48      private Functions() {
    3.49  
    3.50      }
     4.1 --- a/src/main/java/de/uapcore/lightpit/dao/ProjectDao.java	Thu Oct 22 12:00:34 2020 +0200
     4.2 +++ b/src/main/java/de/uapcore/lightpit/dao/ProjectDao.java	Thu Oct 22 13:03:26 2020 +0200
     4.3 @@ -35,4 +35,6 @@
     4.4  
     4.5  public interface ProjectDao extends RootEntityDao<Project> {
     4.6      IssueSummary getIssueSummary(Project project) throws SQLException;
     4.7 +
     4.8 +    Project findByNode(String node) throws SQLException;
     4.9  }
     5.1 --- a/src/main/java/de/uapcore/lightpit/dao/VersionDao.java	Thu Oct 22 12:00:34 2020 +0200
     5.2 +++ b/src/main/java/de/uapcore/lightpit/dao/VersionDao.java	Thu Oct 22 13:03:26 2020 +0200
     5.3 @@ -31,5 +31,8 @@
     5.4  import de.uapcore.lightpit.entities.Project;
     5.5  import de.uapcore.lightpit.entities.Version;
     5.6  
     5.7 +import java.sql.SQLException;
     5.8 +
     5.9  public interface VersionDao extends ChildEntityDao<Version, Project> {
    5.10 +    Version findByNode(Project parent, String node) throws SQLException;
    5.11  }
     6.1 --- a/src/main/java/de/uapcore/lightpit/dao/postgres/PGComponentDao.java	Thu Oct 22 12:00:34 2020 +0200
     6.2 +++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGComponentDao.java	Thu Oct 22 13:03:26 2020 +0200
     6.3 @@ -39,42 +39,38 @@
     6.4  import java.sql.PreparedStatement;
     6.5  import java.sql.ResultSet;
     6.6  import java.sql.SQLException;
     6.7 -import java.util.ArrayList;
     6.8  import java.util.List;
     6.9 -import java.util.Objects;
    6.10  
    6.11  public final class PGComponentDao implements ComponentDao {
    6.12  
    6.13 -    private final PreparedStatement insert, update, list, find;
    6.14 +    private final PreparedStatement insert, update, list, find, findByNode;
    6.15  
    6.16      public PGComponentDao(Connection connection) throws SQLException {
    6.17 -        list = connection.prepareStatement(
    6.18 -                "select id, name, color, ordinal, description, " +
    6.19 -                        "userid, username, givenname, lastname, mail " +
    6.20 -                        "from lpit_component " +
    6.21 -                        "left join lpit_user on lead = userid " +
    6.22 -                        "where project = ? " +
    6.23 +        final var query = "select id, name, node, color, ordinal, description, " +
    6.24 +                "userid, username, givenname, lastname, mail " +
    6.25 +                "from lpit_component " +
    6.26 +                "left join lpit_user on lead = userid";
    6.27 +
    6.28 +        list = connection.prepareStatement(query + " where project = ? " +
    6.29                          "order by ordinal desc, lower(name) desc");
    6.30  
    6.31 -        find = connection.prepareStatement(
    6.32 -                "select id, name, color, ordinal, description, " +
    6.33 -                        "userid, username, givenname, lastname, mail " +
    6.34 -                        "from lpit_component " +
    6.35 -                        "left join lpit_user on lead = userid " +
    6.36 -                        "where id = ? ");
    6.37 +        find = connection.prepareStatement(query + " where id = ? ");
    6.38 +
    6.39 +        findByNode = connection.prepareStatement(query + " where project = ? and node = ?");
    6.40  
    6.41          insert = connection.prepareStatement(
    6.42 -                "insert into lpit_component (project, name, color, ordinal, description, lead) values (?, ?, ?, ?, ?, ?)"
    6.43 +                "insert into lpit_component (name, node, color, ordinal, description, lead, project) values (?, ?, ?, ?, ?, ?, ?)"
    6.44          );
    6.45  
    6.46          update = connection.prepareStatement(
    6.47 -                "update lpit_component set name = ?, color = ?, ordinal = ?, description = ?, lead = ? where id = ?"
    6.48 +                "update lpit_component set name = ?, node = ?, color = ?, ordinal = ?, description = ?, lead = ? where id = ?"
    6.49          );
    6.50      }
    6.51  
    6.52      private static Component mapColumns(ResultSet result) throws SQLException {
    6.53          final var component = new Component(result.getInt("id"));
    6.54          component.setName(result.getString("name"));
    6.55 +        component.setNode(result.getString("node"));
    6.56          try {
    6.57              component.setColor(new WebColor(result.getString("color")));
    6.58          } catch (IllegalArgumentException ex) {
    6.59 @@ -87,53 +83,49 @@
    6.60          return component;
    6.61      }
    6.62  
    6.63 +    private static int setColumns(PreparedStatement stmt, Component instance) throws SQLException {
    6.64 +        int column = 0;
    6.65 +        stmt.setString(++column, instance.getName());
    6.66 +        stmt.setString(++column, instance.getNode());
    6.67 +        stmt.setString(++column, instance.getColor().getHex());
    6.68 +        stmt.setInt(++column, instance.getOrdinal());
    6.69 +        Functions.setStringOrNull(stmt, ++column, instance.getDescription());
    6.70 +        Functions.setForeignKeyOrNull(stmt, ++column, instance.getLead(), User::getId);
    6.71 +        return column;
    6.72 +    }
    6.73 +
    6.74      @Override
    6.75      public void save(Component instance, Project project) throws SQLException {
    6.76 -        Objects.requireNonNull(instance.getName());
    6.77 -        insert.setInt(1, project.getId());
    6.78 -        insert.setString(2, instance.getName());
    6.79 -        insert.setString(3, instance.getColor().getHex());
    6.80 -        insert.setInt(4, instance.getOrdinal());
    6.81 -        Functions.setStringOrNull(insert, 5, instance.getDescription());
    6.82 -        Functions.setForeignKeyOrNull(insert, 6, instance.getLead(), User::getId);
    6.83 +        int column = setColumns(insert, instance);
    6.84 +        insert.setInt(++column, project.getId());
    6.85          insert.executeUpdate();
    6.86      }
    6.87  
    6.88      @Override
    6.89      public boolean update(Component instance) throws SQLException {
    6.90          if (instance.getId() < 0) return false;
    6.91 -        Objects.requireNonNull(instance.getName());
    6.92 -        Objects.requireNonNull(instance.getColor());
    6.93 -        update.setString(1, instance.getName());
    6.94 -        update.setString(2, instance.getColor().getHex());
    6.95 -        update.setInt(3, instance.getOrdinal());
    6.96 -        Functions.setStringOrNull(update, 4, instance.getDescription());
    6.97 -        Functions.setForeignKeyOrNull(update, 5, instance.getLead(), User::getId);
    6.98 -        update.setInt(6, instance.getId());
    6.99 +        int column = setColumns(update, instance);
   6.100 +        update.setInt(++column, instance.getId());
   6.101          return update.executeUpdate() > 0;
   6.102      }
   6.103  
   6.104 +
   6.105      @Override
   6.106      public List<Component> list(Project project) throws SQLException {
   6.107          list.setInt(1, project.getId());
   6.108 -        List<Component> components = new ArrayList<>();
   6.109 -        try (var result = list.executeQuery()) {
   6.110 -            while (result.next()) {
   6.111 -                components.add(mapColumns(result));
   6.112 -            }
   6.113 -        }
   6.114 -        return components;
   6.115 +        return Functions.list(list, PGComponentDao::mapColumns);
   6.116      }
   6.117  
   6.118      @Override
   6.119      public Component find(int id) throws SQLException {
   6.120          find.setInt(1, id);
   6.121 -        try (var result = find.executeQuery()) {
   6.122 -            if (result.next()) {
   6.123 -                return mapColumns(result);
   6.124 -            } else {
   6.125 -                return null;
   6.126 -            }
   6.127 -        }
   6.128 +        return Functions.find(find, PGComponentDao::mapColumns);
   6.129 +    }
   6.130 +
   6.131 +    @Override
   6.132 +    public Component findByNode(Project project, String node) throws SQLException {
   6.133 +        findByNode.setInt(1, project.getId());
   6.134 +        findByNode.setString(2, node);;
   6.135 +        return Functions.find(findByNode, PGComponentDao::mapColumns);
   6.136      }
   6.137  }
     7.1 --- a/src/main/java/de/uapcore/lightpit/dao/postgres/PGIssueDao.java	Thu Oct 22 12:00:34 2020 +0200
     7.2 +++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGIssueDao.java	Thu Oct 22 13:03:26 2020 +0200
     7.3 @@ -48,43 +48,29 @@
     7.4      private final PreparedStatement insertComment, updateComment, listComments;
     7.5  
     7.6      public PGIssueDao(Connection connection) throws SQLException {
     7.7 -        list = connection.prepareStatement(
     7.8 -                "select issueid, i.project, p.name as projectname, component, c.name as componentname, " +
     7.9 +        final var query = "select issueid, i.project, p.name as projectname, p.node as projectnode, "+
    7.10 +                        "component, c.name as componentname, c.node as componentnode, " +
    7.11                          "status, category, subject, i.description, " +
    7.12                          "userid, username, givenname, lastname, mail, " +
    7.13                          "created, updated, eta " +
    7.14                          "from lpit_issue i " +
    7.15                          "join lpit_project p on i.project = projectid " +
    7.16                          "left join lpit_component c on component = c.id " +
    7.17 -                        "left join lpit_user on userid = assignee " +
    7.18 +                        "left join lpit_user on userid = assignee ";
    7.19 +
    7.20 +        list = connection.prepareStatement(query +
    7.21                          "where i.project = ? and coalesce(component, -1) = coalesce(?, component, -1)");
    7.22  
    7.23          listForVersion = connection.prepareStatement(
    7.24                  "with issue_version as ( "+
    7.25                          "select issueid, versionid from lpit_issue_affected_version union "+
    7.26                          "select issueid, versionid from lpit_issue_resolved_version) "+
    7.27 -                        "select issueid, i.project, p.name as projectname, component, c.name as componentname, " +
    7.28 -                        "status, category, subject, i.description, " +
    7.29 -                        "userid, username, givenname, lastname, mail, " +
    7.30 -                        "created, updated, eta " +
    7.31 -                        "from lpit_issue i " +
    7.32 -                        "join lpit_project p on i.project = projectid " +
    7.33 -                        "left join lpit_component c on component = c.id " +
    7.34 +                        query +
    7.35                          "left join issue_version using (issueid) "+
    7.36 -                        "left join lpit_user on userid = assignee " +
    7.37                          "where coalesce(versionid,-1) = ? and coalesce(component, -1) = coalesce(?, component, -1)"
    7.38          );
    7.39  
    7.40 -        find = connection.prepareStatement(
    7.41 -                "select issueid, i.project, p.name as projectname, component, c.name as componentname, " +
    7.42 -                        "status, category, subject, i.description, " +
    7.43 -                        "userid, username, givenname, lastname, mail, " +
    7.44 -                        "created, updated, eta " +
    7.45 -                        "from lpit_issue i " +
    7.46 -                        "join lpit_project p on i.project = projectid " +
    7.47 -                        "left join lpit_component c on component = c.id " +
    7.48 -                        "left join lpit_user on userid = assignee " +
    7.49 -                        "where issueid = ? ");
    7.50 +        find = connection.prepareStatement(query + "where issueid = ? ");
    7.51  
    7.52          insert = connection.prepareStatement(
    7.53                  "insert into lpit_issue (project, component, status, category, subject, description, assignee, eta) " +
    7.54 @@ -128,11 +114,13 @@
    7.55      private Issue mapColumns(ResultSet result) throws SQLException {
    7.56          final var project = new Project(result.getInt("project"));
    7.57          project.setName(result.getString("projectname"));
    7.58 +        project.setNode(result.getString("projectnode"));
    7.59          var component = new Component(result.getInt("component"));
    7.60          if (result.wasNull()) {
    7.61              component = null;
    7.62          } else {
    7.63              component.setName(result.getString("componentname"));
    7.64 +            component.setNode(result.getString("componentnode"));
    7.65          }
    7.66          final var issue = new Issue(result.getInt("issueid"));
    7.67          issue.setProject(project);
     8.1 --- a/src/main/java/de/uapcore/lightpit/dao/postgres/PGProjectDao.java	Thu Oct 22 12:00:34 2020 +0200
     8.2 +++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGProjectDao.java	Thu Oct 22 13:03:26 2020 +0200
     8.3 @@ -28,6 +28,7 @@
     8.4   */
     8.5  package de.uapcore.lightpit.dao.postgres;
     8.6  
     8.7 +import de.uapcore.lightpit.dao.Functions;
     8.8  import de.uapcore.lightpit.dao.ProjectDao;
     8.9  import de.uapcore.lightpit.entities.IssueSummary;
    8.10  import de.uapcore.lightpit.entities.Project;
    8.11 @@ -37,32 +38,26 @@
    8.12  import java.sql.PreparedStatement;
    8.13  import java.sql.ResultSet;
    8.14  import java.sql.SQLException;
    8.15 -import java.util.ArrayList;
    8.16  import java.util.List;
    8.17 -import java.util.Objects;
    8.18  
    8.19  import static de.uapcore.lightpit.dao.Functions.setForeignKeyOrNull;
    8.20  import static de.uapcore.lightpit.dao.Functions.setStringOrNull;
    8.21  
    8.22  public final class PGProjectDao implements ProjectDao {
    8.23  
    8.24 -    private final PreparedStatement insert, update, list, find;
    8.25 +    private final PreparedStatement insert, update, list, find, findByNode;
    8.26      private final PreparedStatement issue_summary;
    8.27  
    8.28      public PGProjectDao(Connection connection) throws SQLException {
    8.29 -        list = connection.prepareStatement(
    8.30 -                "select projectid, name, description, repourl, " +
    8.31 -                        "userid, username, lastname, givenname, mail " +
    8.32 -                        "from lpit_project " +
    8.33 -                        "left join lpit_user owner on lpit_project.owner = owner.userid " +
    8.34 -                        "order by name");
    8.35 +        final var query = "select projectid, name, node, description, repourl, " +
    8.36 +                "userid, username, lastname, givenname, mail " +
    8.37 +                "from lpit_project " +
    8.38 +                "left join lpit_user owner on lpit_project.owner = owner.userid ";
    8.39  
    8.40 -        find = connection.prepareStatement(
    8.41 -                "select projectid, name, description, repourl, " +
    8.42 -                        "userid, username, lastname, givenname, mail " +
    8.43 -                        "from lpit_project " +
    8.44 -                        "left join lpit_user owner on lpit_project.owner = owner.userid " +
    8.45 -                        "where projectid = ?");
    8.46 +        list = connection.prepareStatement(query + " order by name");
    8.47 +
    8.48 +        find = connection.prepareStatement(query + " where projectid = ?");
    8.49 +        findByNode = connection.prepareStatement(query + " where node = ?");
    8.50  
    8.51          issue_summary = connection.prepareStatement(
    8.52                  "select phase, count(*) as total "+
    8.53 @@ -73,16 +68,17 @@
    8.54          );
    8.55  
    8.56          insert = connection.prepareStatement(
    8.57 -                "insert into lpit_project (name, description, repourl, owner) values (?, ?, ?, ?)"
    8.58 +                "insert into lpit_project (name, node, description, repourl, owner) values (?, ?, ?, ?, ?)"
    8.59          );
    8.60          update = connection.prepareStatement(
    8.61 -                "update lpit_project set name = ?, description = ?, repourl = ?, owner = ? where projectid = ?"
    8.62 +                "update lpit_project set name = ?, node = ?, description = ?, repourl = ?, owner = ? where projectid = ?"
    8.63          );
    8.64      }
    8.65  
    8.66 -    public Project mapColumns(ResultSet result) throws SQLException {
    8.67 +    private static Project mapColumns(ResultSet result) throws SQLException {
    8.68          final var proj = new Project(result.getInt("projectid"));
    8.69          proj.setName(result.getString("name"));
    8.70 +        proj.setNode(result.getString("node"));
    8.71          proj.setDescription(result.getString("description"));
    8.72          proj.setRepoUrl(result.getString("repourl"));
    8.73          proj.setOwner(PGUserDao.mapColumns(result));
    8.74 @@ -112,50 +108,44 @@
    8.75          return summary;
    8.76      }
    8.77  
    8.78 +    private static int setColumns(PreparedStatement stmt, Project instance) throws SQLException {
    8.79 +        int column = 0;
    8.80 +        stmt.setString(++column, instance.getName());
    8.81 +        stmt.setString(++column, instance.getNode());
    8.82 +        setStringOrNull(stmt, ++column, instance.getDescription());
    8.83 +        setStringOrNull(stmt, ++column, instance.getRepoUrl());
    8.84 +        setForeignKeyOrNull(stmt, ++column, instance.getOwner(), User::getId);
    8.85 +        return column;
    8.86 +    }
    8.87 +
    8.88      @Override
    8.89      public void save(Project instance) throws SQLException {
    8.90 -        Objects.requireNonNull(instance.getName());
    8.91 -        insert.setString(1, instance.getName());
    8.92 -        setStringOrNull(insert, 2, instance.getDescription());
    8.93 -        setStringOrNull(insert, 3, instance.getRepoUrl());
    8.94 -        setForeignKeyOrNull(insert, 4, instance.getOwner(), User::getId);
    8.95 +        setColumns(insert, instance);
    8.96          insert.executeUpdate();
    8.97      }
    8.98  
    8.99      @Override
   8.100      public boolean update(Project instance) throws SQLException {
   8.101          if (instance.getId() < 0) return false;
   8.102 -        Objects.requireNonNull(instance.getName());
   8.103 -        update.setString(1, instance.getName());
   8.104 -        setStringOrNull(update, 2, instance.getDescription());
   8.105 -        setStringOrNull(update, 3, instance.getRepoUrl());
   8.106 -        setForeignKeyOrNull(update, 4, instance.getOwner(), User::getId);
   8.107 -        update.setInt(5, instance.getId());
   8.108 +        int column = setColumns(update, instance);
   8.109 +        update.setInt(++column, instance.getId());
   8.110          return update.executeUpdate() > 0;
   8.111      }
   8.112  
   8.113      @Override
   8.114      public List<Project> list() throws SQLException {
   8.115 -        List<Project> projects = new ArrayList<>();
   8.116 -        try (var result = list.executeQuery()) {
   8.117 -            while (result.next()) {
   8.118 -                final var project = mapColumns(result);
   8.119 -                projects.add(project);
   8.120 -            }
   8.121 -        }
   8.122 -        return projects;
   8.123 +        return Functions.list(list, PGProjectDao::mapColumns);
   8.124      }
   8.125  
   8.126      @Override
   8.127      public Project find(int id) throws SQLException {
   8.128          find.setInt(1, id);
   8.129 -        try (var result = find.executeQuery()) {
   8.130 -            if (result.next()) {
   8.131 -                final var project = mapColumns(result);
   8.132 -                return project;
   8.133 -            } else {
   8.134 -                return null;
   8.135 -            }
   8.136 -        }
   8.137 +        return Functions.find(find, PGProjectDao::mapColumns);
   8.138 +    }
   8.139 +
   8.140 +    @Override
   8.141 +    public Project findByNode(String node) throws SQLException {
   8.142 +        findByNode.setString(1, node);
   8.143 +        return Functions.find(findByNode, PGProjectDao::mapColumns);
   8.144      }
   8.145  }
     9.1 --- a/src/main/java/de/uapcore/lightpit/dao/postgres/PGVersionDao.java	Thu Oct 22 12:00:34 2020 +0200
     9.2 +++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGVersionDao.java	Thu Oct 22 13:03:26 2020 +0200
     9.3 @@ -28,6 +28,7 @@
     9.4   */
     9.5  package de.uapcore.lightpit.dao.postgres;
     9.6  
     9.7 +import de.uapcore.lightpit.dao.Functions;
     9.8  import de.uapcore.lightpit.dao.VersionDao;
     9.9  import de.uapcore.lightpit.entities.Project;
    9.10  import de.uapcore.lightpit.entities.Version;
    9.11 @@ -37,84 +38,77 @@
    9.12  import java.sql.PreparedStatement;
    9.13  import java.sql.ResultSet;
    9.14  import java.sql.SQLException;
    9.15 -import java.util.ArrayList;
    9.16  import java.util.List;
    9.17 -import java.util.Objects;
    9.18  
    9.19  public final class PGVersionDao implements VersionDao {
    9.20  
    9.21 -    private final PreparedStatement insert, update, list, find;
    9.22 +    private final PreparedStatement insert, update, list, find, findByNode;
    9.23  
    9.24      public PGVersionDao(Connection connection) throws SQLException {
    9.25 -        list = connection.prepareStatement(
    9.26 -                "select versionid, project, name, ordinal, status " +
    9.27 -                        "from lpit_version " +
    9.28 -                        "where project = ? " +
    9.29 +        final var query = "select versionid, project, name, node, ordinal, status from lpit_version";
    9.30 +
    9.31 +        list = connection.prepareStatement(query + " where project = ? " +
    9.32                          "order by ordinal desc, lower(name) desc");
    9.33 -
    9.34 -        find = connection.prepareStatement(
    9.35 -                "select versionid, project, name, ordinal, status " +
    9.36 -                        "from lpit_version  " +
    9.37 -                        "where versionid = ?");
    9.38 +        find = connection.prepareStatement(query + " where versionid = ?");
    9.39 +        findByNode = connection.prepareStatement(query + " where project = ? and node = ?");
    9.40  
    9.41          insert = connection.prepareStatement(
    9.42 -                "insert into lpit_version (project, name, ordinal, status) values (?, ?, ?, ?::version_status)"
    9.43 +                "insert into lpit_version (name, node, ordinal, status, project) values (?, ?, ?, ?::version_status, ?)"
    9.44          );
    9.45          update = connection.prepareStatement(
    9.46 -                "update lpit_version set name = ?, ordinal = ?, status = ?::version_status where versionid = ?"
    9.47 +                "update lpit_version set name = ?, node = ?, ordinal = ?, status = ?::version_status where versionid = ?"
    9.48          );
    9.49      }
    9.50  
    9.51 -    private Version mapColumns(ResultSet result) throws SQLException {
    9.52 +    private static Version mapColumns(ResultSet result) throws SQLException {
    9.53          final var version = new Version(result.getInt("versionid"));
    9.54          version.setName(result.getString("name"));
    9.55 +        version.setNode(result.getString("node"));
    9.56          version.setOrdinal(result.getInt("ordinal"));
    9.57          version.setStatus(VersionStatus.valueOf(result.getString("status")));
    9.58          return version;
    9.59      }
    9.60  
    9.61 +    private static int setFields(PreparedStatement stmt, Version instance) throws SQLException {
    9.62 +        int column = 0;
    9.63 +        stmt.setString(++column, instance.getName());
    9.64 +        stmt.setString(++column, instance.getNode());
    9.65 +        stmt.setInt(++column, instance.getOrdinal());
    9.66 +        stmt.setString(++column, instance.getStatus().name());
    9.67 +        return column;
    9.68 +    }
    9.69 +
    9.70      @Override
    9.71      public void save(Version instance, Project project) throws SQLException {
    9.72 -        Objects.requireNonNull(instance.getName());
    9.73 -        insert.setInt(1, project.getId());
    9.74 -        insert.setString(2, instance.getName());
    9.75 -        insert.setInt(3, instance.getOrdinal());
    9.76 -        insert.setString(4, instance.getStatus().name());
    9.77 +        int column = setFields(insert, instance);
    9.78 +        insert.setInt(++column, project.getId());
    9.79          insert.executeUpdate();
    9.80      }
    9.81  
    9.82      @Override
    9.83      public boolean update(Version instance) throws SQLException {
    9.84          if (instance.getId() < 0) return false;
    9.85 -        Objects.requireNonNull(instance.getName());
    9.86 -        update.setString(1, instance.getName());
    9.87 -        update.setInt(2, instance.getOrdinal());
    9.88 -        update.setString(3, instance.getStatus().name());
    9.89 -        update.setInt(4, instance.getId());
    9.90 +        int column = setFields(update, instance);
    9.91 +        update.setInt(++column, instance.getId());
    9.92          return update.executeUpdate() > 0;
    9.93      }
    9.94  
    9.95      @Override
    9.96      public List<Version> list(Project project) throws SQLException {
    9.97          list.setInt(1, project.getId());
    9.98 -        List<Version> versions = new ArrayList<>();
    9.99 -        try (var result = list.executeQuery()) {
   9.100 -            while (result.next()) {
   9.101 -                versions.add(mapColumns(result));
   9.102 -            }
   9.103 -        }
   9.104 -        return versions;
   9.105 +        return Functions.list(list, PGVersionDao::mapColumns);
   9.106      }
   9.107  
   9.108      @Override
   9.109      public Version find(int id) throws SQLException {
   9.110          find.setInt(1, id);
   9.111 -        try (var result = find.executeQuery()) {
   9.112 -            if (result.next()) {
   9.113 -                return mapColumns(result);
   9.114 -            } else {
   9.115 -                return null;
   9.116 -            }
   9.117 -        }
   9.118 +        return Functions.find(find, PGVersionDao::mapColumns);
   9.119 +    }
   9.120 +
   9.121 +    @Override
   9.122 +    public Version findByNode(Project project, String node) throws SQLException {
   9.123 +        findByNode.setInt(1, project.getId());
   9.124 +        findByNode.setString(2, node);;
   9.125 +        return Functions.find(findByNode, PGVersionDao::mapColumns);
   9.126      }
   9.127  }
    10.1 --- a/src/main/java/de/uapcore/lightpit/entities/Component.java	Thu Oct 22 12:00:34 2020 +0200
    10.2 +++ b/src/main/java/de/uapcore/lightpit/entities/Component.java	Thu Oct 22 13:03:26 2020 +0200
    10.3 @@ -69,7 +69,7 @@
    10.4      }
    10.5  
    10.6      public String getNode() {
    10.7 -        return node == null ? String.valueOf(id) : node;
    10.8 +        return node;
    10.9      }
   10.10  
   10.11      public void setNode(String node) {
    11.1 --- a/src/main/java/de/uapcore/lightpit/entities/Project.java	Thu Oct 22 12:00:34 2020 +0200
    11.2 +++ b/src/main/java/de/uapcore/lightpit/entities/Project.java	Thu Oct 22 13:03:26 2020 +0200
    11.3 @@ -56,7 +56,7 @@
    11.4      }
    11.5  
    11.6      public String getNode() {
    11.7 -        return node == null ? String.valueOf(id) : node;
    11.8 +        return node;
    11.9      }
   11.10  
   11.11      public void setNode(String node) {
    12.1 --- a/src/main/java/de/uapcore/lightpit/entities/Version.java	Thu Oct 22 12:00:34 2020 +0200
    12.2 +++ b/src/main/java/de/uapcore/lightpit/entities/Version.java	Thu Oct 22 13:03:26 2020 +0200
    12.3 @@ -58,7 +58,7 @@
    12.4      }
    12.5  
    12.6      public String getNode() {
    12.7 -        return node == null ? String.valueOf(id) : node;
    12.8 +        return node;
    12.9      }
   12.10  
   12.11      public void setNode(String node) {
    13.1 --- a/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java	Thu Oct 22 12:00:34 2020 +0200
    13.2 +++ b/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java	Thu Oct 22 13:03:26 2020 +0200
    13.3 @@ -74,42 +74,44 @@
    13.4              return;
    13.5  
    13.6          // Select Project
    13.7 -        final int pid = Functions.parseIntOrZero(pathParameters.get("project"));
    13.8 -        if (pid > 0) {
    13.9 -            final var project = projectDao.find(pid);
   13.10 -            if (project != null) {
   13.11 -                final var info = new ProjectInfo(project);
   13.12 -                info.setVersions(versionDao.list(project));
   13.13 -                info.setComponents(componentDao.list(project));
   13.14 -                info.setIssueSummary(projectDao.getIssueSummary(project));
   13.15 -                viewModel.setProjectInfo(info);
   13.16 -            }
   13.17 -        }
   13.18 +        final var project = projectDao.findByNode(pathParameters.get("project"));
   13.19 +        if (project == null)
   13.20 +            return;
   13.21 +
   13.22 +        final var info = new ProjectInfo(project);
   13.23 +        info.setVersions(versionDao.list(project));
   13.24 +        info.setComponents(componentDao.list(project));
   13.25 +        info.setIssueSummary(projectDao.getIssueSummary(project));
   13.26 +        viewModel.setProjectInfo(info);
   13.27  
   13.28          // Select Version
   13.29 -        final var pathParamVersion = pathParameters.get("version");
   13.30 -        if ("no-version".equals(pathParamVersion)) {
   13.31 +        final var versionNode = pathParameters.get("version");
   13.32 +        if ("no-version".equals(versionNode)) {
   13.33              viewModel.setVersionFilter(ProjectView.NO_VERSION);
   13.34 -        } else if ("all-versions".equals(pathParamVersion)) {
   13.35 +        } else if ("all-versions".equals(versionNode)) {
   13.36              viewModel.setVersionFilter(ProjectView.ALL_VERSIONS);
   13.37          } else {
   13.38 -            final int vid = Functions.parseIntOrZero(pathParamVersion);
   13.39 -            if (vid > 0) {
   13.40 -                viewModel.setVersionFilter(versionDao.find(vid));
   13.41 -            }
   13.42 +            viewModel.setVersionFilter(versionDao.findByNode(project, versionNode));
   13.43          }
   13.44  
   13.45          // Select Component
   13.46 -        final var pathParamComponent = pathParameters.get("component");
   13.47 -        if ("no-component".equals(pathParamComponent)) {
   13.48 +        final var componentNode = pathParameters.get("component");
   13.49 +        if ("no-component".equals(componentNode)) {
   13.50              viewModel.setComponentFilter(ProjectView.NO_COMPONENT);
   13.51 -        } else if ("all-components".equals(pathParamComponent)) {
   13.52 +        } else if ("all-components".equals(componentNode)) {
   13.53              viewModel.setComponentFilter(ProjectView.ALL_COMPONENTS);
   13.54          } else {
   13.55 -            final int cid = Functions.parseIntOrZero(pathParamComponent);
   13.56 -            if (cid > 0) {
   13.57 -                viewModel.setComponentFilter(componentDao.find(cid));
   13.58 -            }
   13.59 +            viewModel.setComponentFilter(componentDao.findByNode(project, componentNode));
   13.60 +        }
   13.61 +    }
   13.62 +
   13.63 +    private static String sanitizeNode(String node, String defaultValue) {
   13.64 +        String result = node == null || node.isBlank() ? defaultValue : node;
   13.65 +        result = result.replace('/', '-');
   13.66 +        if (result.equals(".") || result.equals("..")) {
   13.67 +            return "_"+result;
   13.68 +        } else {
   13.69 +            return result;
   13.70          }
   13.71      }
   13.72  
   13.73 @@ -170,6 +172,10 @@
   13.74          try {
   13.75              final var project = new Project(getParameter(req, Integer.class, "pid").orElseThrow());
   13.76              project.setName(getParameter(req, String.class, "name").orElseThrow());
   13.77 +
   13.78 +            final var node = getParameter(req, String.class, "node").orElse(null);
   13.79 +            project.setNode(sanitizeNode(node, project.getName()));
   13.80 +
   13.81              getParameter(req, String.class, "description").ifPresent(project::setDescription);
   13.82              getParameter(req, String.class, "repoUrl").ifPresent(project::setRepoUrl);
   13.83              getParameter(req, Integer.class, "owner").map(
   13.84 @@ -301,14 +307,23 @@
   13.85      public ResponseType commitVersion(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException {
   13.86  
   13.87          try {
   13.88 -            final var project = new Project(getParameter(req, Integer.class, "pid").orElseThrow());
   13.89 +            final var project = dao.getProjectDao().find(getParameter(req, Integer.class, "pid").orElseThrow());
   13.90 +            if (project == null) {
   13.91 +                // TODO: improve error handling, because not found is not correct for this POST request
   13.92 +                resp.sendError(HttpServletResponse.SC_NOT_FOUND);
   13.93 +                return ResponseType.NONE;
   13.94 +            }
   13.95              final var version = new Version(getParameter(req, Integer.class, "id").orElseThrow());
   13.96              version.setName(getParameter(req, String.class, "name").orElseThrow());
   13.97 +
   13.98 +            final var node = getParameter(req, String.class, "node").orElse(null);
   13.99 +            version.setNode(sanitizeNode(node, version.getName()));
  13.100 +
  13.101              getParameter(req, Integer.class, "ordinal").ifPresent(version::setOrdinal);
  13.102              version.setStatus(VersionStatus.valueOf(getParameter(req, String.class, "status").orElseThrow()));
  13.103              dao.getVersionDao().saveOrUpdate(version, project);
  13.104  
  13.105 -            setRedirectLocation(req, "./projects/" + project.getId() + "/versions/");
  13.106 +            setRedirectLocation(req, "./projects/" + project.getNode() + "/versions/");
  13.107              setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
  13.108          } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) {
  13.109              resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
  13.110 @@ -373,9 +388,18 @@
  13.111      public ResponseType commitComponent(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException {
  13.112  
  13.113          try {
  13.114 -            final var project = new Project(getParameter(req, Integer.class, "pid").orElseThrow());
  13.115 +            final var project = dao.getProjectDao().find(getParameter(req, Integer.class, "pid").orElseThrow());
  13.116 +            if (project == null) {
  13.117 +                // TODO: improve error handling, because not found is not correct for this POST request
  13.118 +                resp.sendError(HttpServletResponse.SC_NOT_FOUND);
  13.119 +                return ResponseType.NONE;
  13.120 +            }
  13.121              final var component = new Component(getParameter(req, Integer.class, "id").orElseThrow());
  13.122              component.setName(getParameter(req, String.class, "name").orElseThrow());
  13.123 +
  13.124 +            final var node = getParameter(req, String.class, "node").orElse(null);
  13.125 +            component.setNode(sanitizeNode(node, component.getName()));
  13.126 +
  13.127              component.setColor(getParameter(req, WebColor.class, "color").orElseThrow());
  13.128              getParameter(req, Integer.class, "ordinal").ifPresent(component::setOrdinal);
  13.129              getParameter(req, Integer.class, "lead").map(
  13.130 @@ -385,7 +409,7 @@
  13.131  
  13.132              dao.getComponentDao().saveOrUpdate(component, project);
  13.133  
  13.134 -            setRedirectLocation(req, "./projects/" + project.getId() + "/components/");
  13.135 +            setRedirectLocation(req, "./projects/" + project.getNode() + "/components/");
  13.136              setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
  13.137          } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) {
  13.138              resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
  13.139 @@ -461,7 +485,13 @@
  13.140              } else {
  13.141                  component = null;
  13.142              }
  13.143 -            issue.setProject(new Project(getParameter(req, Integer.class, "pid").orElseThrow()));
  13.144 +            final var project = dao.getProjectDao().find(getParameter(req, Integer.class, "pid").orElseThrow());
  13.145 +            if (project == null) {
  13.146 +                // TODO: improve error handling, because not found is not correct for this POST request
  13.147 +                resp.sendError(HttpServletResponse.SC_NOT_FOUND);
  13.148 +                return ResponseType.NONE;
  13.149 +            }
  13.150 +            issue.setProject(project);
  13.151              getParameter(req, String.class, "category").map(IssueCategory::valueOf).ifPresent(issue::setCategory);
  13.152              getParameter(req, String.class, "status").map(IssueStatus::valueOf).ifPresent(issue::setStatus);
  13.153              issue.setSubject(getParameter(req, String.class, "subject").orElseThrow());
  13.154 @@ -493,7 +523,7 @@
  13.155              dao.getIssueDao().saveOrUpdate(issue, issue.getProject());
  13.156  
  13.157              // TODO: fix issue #14
  13.158 -            setRedirectLocation(req, "./projects/" + issue.getProject().getId() + "/all-components/all-versions/issues/");
  13.159 +            setRedirectLocation(req, "./projects/" + issue.getProject().getNode() + "/all-components/all-versions/issues/");
  13.160              setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
  13.161  
  13.162              return ResponseType.HTML;
  13.163 @@ -532,7 +562,7 @@
  13.164              dao.getIssueDao().saveComment(issueComment);
  13.165  
  13.166              // TODO: fix redirect location (e.g. after fixing #24)
  13.167 -            setRedirectLocation(req, "./projects/" + issue.getProject().getId()+"/issues/"+issue.getId()+"/edit");
  13.168 +            setRedirectLocation(req, "./projects/" + issue.getProject().getNode()+"/issues/"+issue.getId()+"/edit");
  13.169              setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
  13.170  
  13.171              return ResponseType.HTML;
    14.1 --- a/src/main/resources/localization/projects.properties	Thu Oct 22 12:00:34 2020 +0200
    14.2 +++ b/src/main/resources/localization/projects.properties	Thu Oct 22 13:03:26 2020 +0200
    14.3 @@ -40,6 +40,8 @@
    14.4  navmenu.all=all
    14.5  
    14.6  name=Name
    14.7 +node=Node
    14.8 +node.tooltip=Name of the path node that will be used in URL construction.
    14.9  description=Description
   14.10  repoUrl=Repository
   14.11  owner=Project Lead
    15.1 --- a/src/main/resources/localization/projects_de.properties	Thu Oct 22 12:00:34 2020 +0200
    15.2 +++ b/src/main/resources/localization/projects_de.properties	Thu Oct 22 13:03:26 2020 +0200
    15.3 @@ -40,6 +40,8 @@
    15.4  navmenu.all=Alle
    15.5  
    15.6  name=Name
    15.7 +node=Pfadname
    15.8 +node.tooltip=Name, der zur Konstruktion der URL genutzt werden soll.
    15.9  description=Beschreibung
   15.10  repoUrl=Repository
   15.11  owner=Projektleitung
    16.1 --- a/src/main/webapp/WEB-INF/jsp/component-form.jsp	Thu Oct 22 12:00:34 2020 +0200
    16.2 +++ b/src/main/webapp/WEB-INF/jsp/component-form.jsp	Thu Oct 22 13:03:26 2020 +0200
    16.3 @@ -50,6 +50,10 @@
    16.4              <th><fmt:message key="component.name"/></th>
    16.5              <td><input name="name" type="text" maxlength="20" required value="<c:out value="${component.name}"/>" /></td>
    16.6          </tr>
    16.7 +        <tr title="<fmt:message key="node.tooltip"/>">
    16.8 +            <th><fmt:message key="node"/></th>
    16.9 +            <td><input name="node" type="text" maxlength="20" required value="<c:out value="${component.node}"/>" /></td>
   16.10 +        </tr>
   16.11          <tr>
   16.12              <th><fmt:message key="component.color"/></th>
   16.13              <td><input name="color" type="color" required value="${component.color}" /></td>
    17.1 --- a/src/main/webapp/WEB-INF/jsp/project-form.jsp	Thu Oct 22 12:00:34 2020 +0200
    17.2 +++ b/src/main/webapp/WEB-INF/jsp/project-form.jsp	Thu Oct 22 13:03:26 2020 +0200
    17.3 @@ -42,6 +42,10 @@
    17.4              <th><fmt:message key="name"/></th>
    17.5              <td><input name="name" type="text" maxlength="20" required value="<c:out value="${project.name}"/>" /></td>
    17.6          </tr>
    17.7 +        <tr title="<fmt:message key="node.tooltip"/>">
    17.8 +            <th><fmt:message key="node"/></th>
    17.9 +            <td><input name="node" type="text" maxlength="20" required value="<c:out value="${project.node}"/>" /></td>
   17.10 +        </tr>
   17.11          <tr>
   17.12              <th><fmt:message key="description"/></th>
   17.13              <td><input type="text" name="description" maxlength="200" value="<c:out value="${project.description}"/>" /></td>
    18.1 --- a/src/main/webapp/WEB-INF/jsp/version-form.jsp	Thu Oct 22 12:00:34 2020 +0200
    18.2 +++ b/src/main/webapp/WEB-INF/jsp/version-form.jsp	Thu Oct 22 13:03:26 2020 +0200
    18.3 @@ -50,6 +50,10 @@
    18.4              <th><fmt:message key="version.name"/></th>
    18.5              <td><input name="name" type="text" maxlength="20" required value="<c:out value="${version.name}"/>" /></td>
    18.6          </tr>
    18.7 +        <tr title="<fmt:message key="node.tooltip"/>">
    18.8 +            <th><fmt:message key="node"/></th>
    18.9 +            <td><input name="node" type="text" maxlength="20" required value="<c:out value="${version.node}"/>" /></td>
   18.10 +        </tr>
   18.11          <tr>
   18.12              <th><fmt:message key="version.status"/></th>
   18.13              <td>

mercurial