Mon, 18 May 2020 21:05:57 +0200
adds data model for issues
--- a/setup/postgres/psql_create_tables.sql Sun May 17 16:38:04 2020 +0200 +++ b/setup/postgres/psql_create_tables.sql Mon May 18 21:05:57 2020 +0200 @@ -32,3 +32,36 @@ ordinal integer not null default 0, status version_status not null default 'Future' ); + +create type issue_status as enum ( + 'InSpecification', + 'ToDo', + 'Scheduled', + 'InProgress', + 'InReview', + 'Done', + 'Rejected', + 'Withdrawn' +); + +create type issue_category as enum ( + 'Feature', + 'Improvement', + 'Bug', + 'Task', + 'Test' +); + +create table lpit_issue ( + id serial primary key, + project integer not null references lpit_project(id), + status issue_status not null default 'InSpecification', + category issue_category not null default 'Feature', + subject varchar(20) not null, + description text, + version_plan integer references lpit_version(id), + version_done integer references lpit_version(id), + created timestamp with time zone not null default now(), + updated timestamp with time zone not null default now(), + eta date +);
--- a/src/main/java/de/uapcore/lightpit/dao/DataAccessObjects.java Sun May 17 16:38:04 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/dao/DataAccessObjects.java Mon May 18 21:05:57 2020 +0200 @@ -32,4 +32,5 @@ UserDao getUserDao(); ProjectDao getProjectDao(); VersionDao getVersionDao(); + IssueDao getIssueDao(); }
--- a/src/main/java/de/uapcore/lightpit/dao/Functions.java Sun May 17 16:38:04 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/dao/Functions.java Mon May 18 21:05:57 2020 +0200 @@ -28,6 +28,7 @@ */ package de.uapcore.lightpit.dao; +import java.sql.Date; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Types; @@ -47,6 +48,14 @@ } } + public static void setDateOrNull(PreparedStatement stmt, int index, Date date) throws SQLException { + if (date == null) { + stmt.setNull(index, Types.DATE); + } else { + stmt.setDate(index, date); + } + } + public static <T> void setForeignKeyOrNull(PreparedStatement stmt, int index, T instance, Function<? super T, Integer> keyGetter) throws SQLException { Integer key = Optional.ofNullable(instance).map(keyGetter).orElse(null); if (key == null) {
--- a/src/main/java/de/uapcore/lightpit/dao/GenericDao.java Sun May 17 16:38:04 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/dao/GenericDao.java Mon May 18 21:05:57 2020 +0200 @@ -29,16 +29,8 @@ package de.uapcore.lightpit.dao; import java.sql.SQLException; -import java.util.List; public interface GenericDao<T> { - /** - * Returns a list of all entities. - * - * @return a list of all objects - * @throws SQLException on any kind of SQL errors - */ - List<T> list() throws SQLException; /** * Finds an entity by its integer ID.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/java/de/uapcore/lightpit/dao/IssueDao.java Mon May 18 21:05:57 2020 +0200 @@ -0,0 +1,46 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +package de.uapcore.lightpit.dao; + +import de.uapcore.lightpit.entities.Issue; +import de.uapcore.lightpit.entities.Project; + +import java.sql.SQLException; +import java.util.List; + +public interface IssueDao extends GenericDao<Issue> { + + /** + * Lists all issues for the specified project. + * @param project the project + * @return a list of issues + * @throws SQLException on any kind of SQL error + */ + List<Issue> list(Project project) throws SQLException; +}
--- a/src/main/java/de/uapcore/lightpit/dao/ProjectDao.java Sun May 17 16:38:04 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/dao/ProjectDao.java Mon May 18 21:05:57 2020 +0200 @@ -30,5 +30,9 @@ import de.uapcore.lightpit.entities.Project; +import java.sql.SQLException; +import java.util.List; + public interface ProjectDao extends GenericDao<Project> { + List<Project> list() throws SQLException; }
--- a/src/main/java/de/uapcore/lightpit/dao/UserDao.java Sun May 17 16:38:04 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/dao/UserDao.java Mon May 18 21:05:57 2020 +0200 @@ -30,6 +30,10 @@ import de.uapcore.lightpit.entities.User; +import java.sql.SQLException; +import java.util.List; + public interface UserDao extends GenericDao<User> { + List<User> list() throws SQLException; }
--- a/src/main/java/de/uapcore/lightpit/dao/VersionDao.java Sun May 17 16:38:04 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/dao/VersionDao.java Mon May 18 21:05:57 2020 +0200 @@ -34,14 +34,7 @@ import java.sql.SQLException; import java.util.List; -public interface VersionDao { - - Version find(int id) throws SQLException; - void save(Version instance) throws SQLException; - boolean update(Version instance) throws SQLException; - default void saveOrUpdate(Version instance) throws SQLException { - if (!update(instance)) save(instance); - } +public interface VersionDao extends GenericDao<Version> { /** * Lists all versions for the specified project.
--- a/src/main/java/de/uapcore/lightpit/dao/postgres/PGDataAccessObjects.java Sun May 17 16:38:04 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGDataAccessObjects.java Mon May 18 21:05:57 2020 +0200 @@ -28,10 +28,7 @@ */ package de.uapcore.lightpit.dao.postgres; -import de.uapcore.lightpit.dao.DataAccessObjects; -import de.uapcore.lightpit.dao.ProjectDao; -import de.uapcore.lightpit.dao.UserDao; -import de.uapcore.lightpit.dao.VersionDao; +import de.uapcore.lightpit.dao.*; import java.sql.Connection; import java.sql.SQLException; @@ -41,11 +38,13 @@ private final UserDao userDao; private final ProjectDao projectDao; private final VersionDao versionDao; + private final IssueDao issueDao; public PGDataAccessObjects(Connection connection) throws SQLException { userDao = new PGUserDao(connection); projectDao = new PGProjectDao(connection); versionDao = new PGVersionDao(connection); + issueDao = new PGIssueDao(connection); } @Override @@ -62,4 +61,9 @@ public VersionDao getVersionDao() { return versionDao; } + + @Override + public IssueDao getIssueDao() { + return issueDao; + } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGIssueDao.java Mon May 18 21:05:57 2020 +0200 @@ -0,0 +1,155 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +package de.uapcore.lightpit.dao.postgres; + +import de.uapcore.lightpit.dao.IssueDao; +import de.uapcore.lightpit.entities.*; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import static de.uapcore.lightpit.dao.Functions.*; + +public final class PGIssueDao implements IssueDao { + + private final PreparedStatement insert, update, list, find; + + public PGIssueDao(Connection connection) throws SQLException { + list = connection.prepareStatement( + "select id, project, status, category, subject, description, " + + "vplan.id, vplan.name, vdone.id, vdone.name, " + + "created, updated, eta " + + "from lpit_issue " + + "left join lpit_version vplan on vplan.id = version_plan " + + "left join lpit_version vdone on vdone.id = version_done " + + "where project = ? "); + + find = connection.prepareStatement( + "select id, project, status, category, subject, description, " + + "vplan.id, vplan.name, vdone.id, vdone.name, " + + "created, updated, eta " + + "from lpit_issue " + + "left join lpit_version vplan on vplan.id = version_plan " + + "left join lpit_version vdone on vdone.id = version_done " + + "where id = ? "); + + insert = connection.prepareStatement( + "insert into lpit_issue (project, status, category, subject, description, version_plan, version_done, eta) " + + "values (?, ?::issue_status, ?::issue_category, ?, ?, ?, ?, ?)" + ); + update = connection.prepareStatement( + "update lpit_issue set updated = now(), status = ?::issue_status, category = ?::issue_category, " + + "subject = ?, description = ?, version_plan = ?, version_done = ?, eta = ? where id = ?" + ); + } + + private Version obtainVersion(ResultSet result, Project project, String prefix) throws SQLException { + final int vplan = result.getInt(prefix+"id"); + if (vplan > 0) { + final var ver = new Version(vplan, project); + ver.setName(result.getString(prefix+"name")); + return ver; + } else { + return null; + } + } + + public Issue mapColumns(ResultSet result) throws SQLException { + final var project = new Project(result.getInt("project")); + final var issue = new Issue(result.getInt("id"), project); + issue.setStatus(IssueStatus.valueOf(result.getString("status"))); + issue.setCategory(IssueCategory.valueOf(result.getString("category"))); + issue.setSubject(result.getString("subject")); + issue.setDescription(result.getString("description")); + issue.setScheduledVersion(obtainVersion(result, project, "vplan.")); + issue.setResolvedVersion(obtainVersion(result, project, "vdone.")); + issue.setCreated(result.getTimestamp("created")); + issue.setUpdated(result.getTimestamp("updated")); + issue.setEta(result.getDate("eta")); + return issue; + } + + @Override + public void save(Issue instance) throws SQLException { + Objects.requireNonNull(instance.getSubject()); + Objects.requireNonNull(instance.getProject()); + insert.setInt(1, instance.getProject().getId()); + insert.setString(2, instance.getStatus().name()); + insert.setString(3, instance.getCategory().name()); + insert.setString(4, instance.getSubject()); + setStringOrNull(insert, 5, instance.getDescription()); + setForeignKeyOrNull(insert, 6, instance.getScheduledVersion(), Version::getId); + setForeignKeyOrNull(insert, 7, instance.getResolvedVersion(), Version::getId); + setDateOrNull(insert, 8, instance.getEta()); + insert.executeUpdate(); + } + + @Override + public boolean update(Issue instance) throws SQLException { + Objects.requireNonNull(instance.getSubject()); + update.setString(1, instance.getStatus().name()); + update.setString(2, instance.getCategory().name()); + update.setString(3, instance.getSubject()); + setStringOrNull(update, 4, instance.getDescription()); + setForeignKeyOrNull(update, 5, instance.getScheduledVersion(), Version::getId); + setForeignKeyOrNull(update, 6, instance.getResolvedVersion(), Version::getId); + setDateOrNull(update, 7, instance.getEta()); + update.setInt(8, instance.getId()); + return update.executeUpdate() > 0; + } + + @Override + public List<Issue> list(Project project) throws SQLException { + list.setInt(1, project.getId()); + List<Issue> versions = new ArrayList<>(); + try (var result = list.executeQuery()) { + while (result.next()) { + versions.add(mapColumns(result)); + } + } + return versions; + } + + @Override + public Issue find(int id) throws SQLException { + find.setInt(1, id); + try (var result = find.executeQuery()) { + if (result.next()) { + return mapColumns(result); + } else { + return null; + } + } + } +}
--- a/src/main/java/de/uapcore/lightpit/dao/postgres/PGVersionDao.java Sun May 17 16:38:04 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGVersionDao.java Mon May 18 21:05:57 2020 +0200 @@ -50,7 +50,7 @@ "select id, project, name, ordinal, status " + "from lpit_version " + "where project = ? " + - "order by ordinal, name"); + "order by ordinal, lower(name)"); find = connection.prepareStatement( "select id, project, name, ordinal, status " +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/java/de/uapcore/lightpit/entities/Issue.java Mon May 18 21:05:57 2020 +0200 @@ -0,0 +1,160 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +package de.uapcore.lightpit.entities; + +import java.sql.Date; +import java.sql.Timestamp; +import java.util.List; +import java.util.Objects; + +public final class Issue { + + private final int id; + private final Project project; + + private IssueStatus status; + private IssueCategory category; + + private String subject; + private String description; + + private List<Version> affectedVersions; + private Version scheduledVersion; + private Version resolvedVersion; + + private Timestamp created; + private Timestamp updated; + private Date eta; + + public Issue(int id, Project project) { + this.id = id; + this.project = project; + } + + public int getId() { + return id; + } + + public Project getProject() { + return project; + } + + public IssueStatus getStatus() { + return status; + } + + public void setStatus(IssueStatus status) { + this.status = status; + } + + public IssueCategory getCategory() { + return category; + } + + public void setCategory(IssueCategory category) { + this.category = category; + } + + public String getSubject() { + return subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List<Version> getAffectedVersions() { + return affectedVersions; + } + + public void setAffectedVersions(List<Version> affectedVersions) { + this.affectedVersions = affectedVersions; + } + + public Version getScheduledVersion() { + return scheduledVersion; + } + + public void setScheduledVersion(Version scheduledVersion) { + this.scheduledVersion = scheduledVersion; + } + + public Version getResolvedVersion() { + return resolvedVersion; + } + + public void setResolvedVersion(Version resolvedVersion) { + this.resolvedVersion = resolvedVersion; + } + + public Timestamp getCreated() { + return created; + } + + public void setCreated(Timestamp created) { + this.created = created; + } + + public Timestamp getUpdated() { + return updated; + } + + public void setUpdated(Timestamp updated) { + this.updated = updated; + } + + public Date getEta() { + return eta; + } + + public void setEta(Date eta) { + this.eta = eta; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Issue issue = (Issue) o; + return id == issue.id; + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/java/de/uapcore/lightpit/entities/IssueCategory.java Mon May 18 21:05:57 2020 +0200 @@ -0,0 +1,37 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +package de.uapcore.lightpit.entities; + +public enum IssueCategory { + Feature, + Improvement, + Bug, + Task, + Test +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/java/de/uapcore/lightpit/entities/IssueStatus.java Mon May 18 21:05:57 2020 +0200 @@ -0,0 +1,40 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +package de.uapcore.lightpit.entities; + +public enum IssueStatus { + InSpecification, + ToDo, + Scheduled, + InProgress, + InReview, + Done, + Rejected, + Withdrawn +}
--- a/src/main/java/de/uapcore/lightpit/entities/Version.java Sun May 17 16:38:04 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/entities/Version.java Mon May 18 21:05:57 2020 +0200 @@ -30,7 +30,7 @@ import java.util.Objects; -public class Version implements Comparable<Version> { +public final class Version implements Comparable<Version> { private final int id; private final Project project;