universe@38: /* universe@38: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. universe@38: * universe@38: * Copyright 2018 Mike Becker. All rights reserved. universe@38: * universe@38: * Redistribution and use in source and binary forms, with or without universe@38: * modification, are permitted provided that the following conditions are met: universe@38: * universe@38: * 1. Redistributions of source code must retain the above copyright universe@38: * notice, this list of conditions and the following disclaimer. universe@38: * universe@38: * 2. Redistributions in binary form must reproduce the above copyright universe@38: * notice, this list of conditions and the following disclaimer in the universe@38: * documentation and/or other materials provided with the distribution. universe@38: * universe@38: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" universe@38: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE universe@38: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE universe@38: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE universe@38: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR universe@38: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF universe@38: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS universe@38: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN universe@38: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) universe@38: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE universe@38: * POSSIBILITY OF SUCH DAMAGE. universe@38: * universe@38: */ universe@38: package de.uapcore.lightpit.dao.postgres; universe@38: universe@62: import de.uapcore.lightpit.dao.IssueDao; universe@62: import de.uapcore.lightpit.entities.*; universe@38: universe@134: import java.sql.*; universe@47: import java.util.ArrayList; universe@47: import java.util.List; universe@38: import java.util.Objects; universe@134: import java.util.Optional; universe@38: universe@62: import static de.uapcore.lightpit.dao.Functions.*; universe@62: universe@62: public final class PGIssueDao implements IssueDao { universe@38: universe@86: private final PreparedStatement insert, update, list, listForVersion, find; universe@88: private final PreparedStatement affectedVersions, resolvedVersions; universe@88: private final PreparedStatement clearAffected, clearResolved; universe@88: private final PreparedStatement insertAffected, insertResolved; universe@124: private final PreparedStatement insertComment, updateComment, listComments; universe@38: universe@62: public PGIssueDao(Connection connection) throws SQLException { universe@47: list = connection.prepareStatement( universe@134: "select issueid, i.project, p.name as projectname, component, c.name as componentname, " + universe@134: "status, category, subject, i.description, " + universe@75: "userid, username, givenname, lastname, mail, " + universe@75: "created, updated, eta " + universe@83: "from lpit_issue i " + universe@134: "join lpit_project p on i.project = projectid " + universe@134: "left join lpit_component c on component = c.id " + universe@75: "left join lpit_user on userid = assignee " + universe@134: "where i.project = ? and coalesce(component, -1) = coalesce(?, component, -1)"); universe@86: universe@86: listForVersion = connection.prepareStatement( universe@86: "with issue_version as ( "+ universe@86: "select issueid, versionid from lpit_issue_affected_version union "+ universe@86: "select issueid, versionid from lpit_issue_resolved_version) "+ universe@134: "select issueid, i.project, p.name as projectname, component, c.name as componentname, " + universe@134: "status, category, subject, i.description, " + universe@86: "userid, username, givenname, lastname, mail, " + universe@86: "created, updated, eta " + universe@86: "from lpit_issue i " + universe@134: "join lpit_project p on i.project = projectid " + universe@134: "left join lpit_component c on component = c.id " + universe@105: "left join issue_version using (issueid) "+ universe@86: "left join lpit_user on userid = assignee " + universe@134: "where coalesce(versionid,-1) = ? and coalesce(component, -1) = coalesce(?, component, -1)" universe@86: ); universe@47: universe@47: find = connection.prepareStatement( universe@134: "select issueid, i.project, p.name as projectname, component, c.name as componentname, " + universe@134: "status, category, subject, i.description, " + universe@75: "userid, username, givenname, lastname, mail, " + universe@75: "created, updated, eta " + universe@83: "from lpit_issue i " + universe@134: "join lpit_project p on i.project = projectid " + universe@134: "left join lpit_component c on component = c.id " + universe@75: "left join lpit_user on userid = assignee " + universe@75: "where issueid = ? "); universe@38: universe@38: insert = connection.prepareStatement( universe@134: "insert into lpit_issue (project, component, status, category, subject, description, assignee, eta) " + universe@135: "values (?, ?, ?::issue_status, ?::issue_category, ?, ?, ?, ?) returning issueid" universe@38: ); universe@38: update = connection.prepareStatement( universe@134: "update lpit_issue set " + universe@134: "updated = now(), component = ?, status = ?::issue_status, category = ?::issue_category, " + universe@75: "subject = ?, description = ?, assignee = ?, eta = ? where issueid = ?" universe@75: ); universe@75: universe@75: affectedVersions = connection.prepareStatement( universe@83: "select versionid, name, status, ordinal " + universe@83: "from lpit_version join lpit_issue_affected_version using (versionid) " + universe@75: "where issueid = ? " + universe@83: "order by ordinal, name" universe@75: ); universe@83: clearAffected = connection.prepareStatement("delete from lpit_issue_affected_version where issueid = ?"); universe@83: insertAffected = connection.prepareStatement("insert into lpit_issue_affected_version (issueid, versionid) values (?,?)"); universe@75: universe@75: resolvedVersions = connection.prepareStatement( universe@83: "select versionid, name, status, ordinal " + universe@75: "from lpit_version v join lpit_issue_resolved_version using (versionid) " + universe@75: "where issueid = ? " + universe@83: "order by ordinal, name" universe@38: ); universe@83: clearResolved = connection.prepareStatement("delete from lpit_issue_resolved_version where issueid = ?"); universe@83: insertResolved = connection.prepareStatement("insert into lpit_issue_resolved_version (issueid, versionid) values (?,?)"); universe@124: universe@124: insertComment = connection.prepareStatement( universe@124: "insert into lpit_issue_comment (issueid, comment, userid) values (?, ? ,?)" universe@124: ); universe@124: updateComment = connection.prepareStatement( universe@124: "update lpit_issue_comment set comment = ?, updated = now(), updatecount = updatecount+1 where commentid = ?" universe@124: ); universe@124: listComments = connection.prepareStatement( universe@124: "select * from lpit_issue_comment left join lpit_user using (userid) where issueid = ? order by created" universe@124: ); universe@38: } universe@38: universe@75: private Issue mapColumns(ResultSet result) throws SQLException { universe@75: final var project = new Project(result.getInt("project")); universe@83: project.setName(result.getString("projectname")); universe@134: var component = new Component(result.getInt("component")); universe@134: if (result.wasNull()) { universe@134: component = null; universe@134: } else { universe@134: component.setName(result.getString("componentname")); universe@134: } universe@86: final var issue = new Issue(result.getInt("issueid")); universe@86: issue.setProject(project); universe@134: issue.setComponent(component); universe@75: issue.setStatus(IssueStatus.valueOf(result.getString("status"))); universe@75: issue.setCategory(IssueCategory.valueOf(result.getString("category"))); universe@75: issue.setSubject(result.getString("subject")); universe@75: issue.setDescription(result.getString("description")); universe@128: issue.setAssignee(PGUserDao.mapColumns(result)); universe@75: issue.setCreated(result.getTimestamp("created")); universe@75: issue.setUpdated(result.getTimestamp("updated")); universe@75: issue.setEta(result.getDate("eta")); universe@62: return issue; universe@38: } universe@38: universe@86: private Version mapVersion(ResultSet result) throws SQLException { universe@86: final var version = new Version(result.getInt("versionid")); universe@83: version.setName(result.getString("name")); universe@83: version.setOrdinal(result.getInt("ordinal")); universe@83: version.setStatus(VersionStatus.valueOf(result.getString("status"))); universe@75: return version; universe@75: } universe@75: universe@83: private void updateVersionLists(Issue instance) throws SQLException { universe@83: clearAffected.setInt(1, instance.getId()); universe@83: clearResolved.setInt(1, instance.getId()); universe@83: insertAffected.setInt(1, instance.getId()); universe@83: insertResolved.setInt(1, instance.getId()); universe@83: clearAffected.executeUpdate(); universe@83: clearResolved.executeUpdate(); universe@83: for (Version v : instance.getAffectedVersions()) { universe@83: insertAffected.setInt(2, v.getId()); universe@83: insertAffected.executeUpdate(); universe@83: } universe@83: for (Version v : instance.getResolvedVersions()) { universe@83: insertResolved.setInt(2, v.getId()); universe@83: insertResolved.executeUpdate(); universe@83: } universe@83: } universe@83: universe@134: private int setData(PreparedStatement stmt, int column, Issue instance) throws SQLException { universe@134: setForeignKeyOrNull(stmt, ++column, instance.getComponent(), Component::getId); universe@134: stmt.setString(++column, instance.getStatus().name()); universe@134: stmt.setString(++column, instance.getCategory().name()); universe@134: stmt.setString(++column, instance.getSubject()); universe@134: setStringOrNull(stmt, ++column, instance.getDescription()); universe@134: setForeignKeyOrNull(stmt, ++column, instance.getAssignee(), User::getId); universe@134: setDateOrNull(stmt, ++column, instance.getEta()); universe@134: return column; universe@134: } universe@134: universe@38: @Override universe@128: public void save(Issue instance, Project project) throws SQLException { universe@62: Objects.requireNonNull(instance.getSubject()); universe@128: instance.setProject(project); universe@134: int column = 0; universe@134: insert.setInt(++column, instance.getProject().getId()); universe@134: setData(insert, column, instance); universe@75: // insert and retrieve the ID universe@75: final var rs = insert.executeQuery(); universe@75: rs.next(); universe@75: instance.setId(rs.getInt(1)); universe@83: updateVersionLists(instance); universe@38: } universe@38: universe@38: @Override universe@62: public boolean update(Issue instance) throws SQLException { universe@75: if (instance.getId() < 0) return false; universe@62: Objects.requireNonNull(instance.getSubject()); universe@134: int column = setData(update, 0, instance); universe@134: update.setInt(++column, instance.getId()); universe@83: boolean success = update.executeUpdate() > 0; universe@83: if (success) { universe@83: updateVersionLists(instance); universe@83: return true; universe@83: } else { universe@83: return false; universe@83: } universe@38: } universe@47: universe@134: private List executeQuery(PreparedStatement query) throws SQLException { universe@75: List issues = new ArrayList<>(); universe@86: try (var result = query.executeQuery()) { universe@47: while (result.next()) { universe@75: issues.add(mapColumns(result)); universe@47: } universe@47: } universe@75: return issues; universe@47: } universe@47: universe@47: @Override universe@86: public List list(Project project) throws SQLException { universe@134: list.setInt(1, project.getId()); universe@134: list.setNull(2, Types.INTEGER); universe@134: return executeQuery(list); universe@86: } universe@86: universe@86: @Override universe@134: public List list(Project project, Component component, Version version) throws SQLException { universe@134: listForVersion.setInt(1, Optional.ofNullable(version).map(Version::getId).orElse(-1)); universe@134: listForVersion.setInt(2, Optional.ofNullable(component).map(Component::getId).orElse(-1)); universe@134: return executeQuery(listForVersion); universe@134: } universe@134: universe@134: @Override universe@134: public List list(Project project, Version version) throws SQLException { universe@134: listForVersion.setInt(1, Optional.ofNullable(version).map(Version::getId).orElse(-1)); universe@134: listForVersion.setNull(2, Types.INTEGER); universe@134: return executeQuery(listForVersion); universe@134: } universe@134: universe@134: @Override universe@134: public List list(Project project, Component component) throws SQLException { universe@134: list.setInt(1, project.getId()); universe@134: list.setInt(2, Optional.ofNullable(component).map(Component::getId).orElse(-1)); universe@134: return executeQuery(list); universe@86: } universe@86: universe@86: @Override universe@62: public Issue find(int id) throws SQLException { universe@47: find.setInt(1, id); universe@47: try (var result = find.executeQuery()) { universe@47: if (result.next()) { universe@47: return mapColumns(result); universe@47: } else { universe@47: return null; universe@47: } universe@47: } universe@47: } universe@75: universe@75: private List listVersions(PreparedStatement stmt, Issue issue) throws SQLException { universe@75: stmt.setInt(1, issue.getId()); universe@75: List versions = new ArrayList<>(); universe@75: try (var result = stmt.executeQuery()) { universe@75: while (result.next()) { universe@86: versions.add(mapVersion(result)); universe@75: } universe@75: } universe@75: return versions; universe@75: } universe@75: universe@75: @Override universe@75: public void joinVersionInformation(Issue issue) throws SQLException { universe@75: Objects.requireNonNull(issue.getProject()); universe@75: issue.setAffectedVersions(listVersions(affectedVersions, issue)); universe@75: issue.setResolvedVersions(listVersions(resolvedVersions, issue)); universe@75: } universe@124: universe@124: @Override universe@124: public List listComments(Issue issue) throws SQLException { universe@124: listComments.setInt(1, issue.getId()); universe@124: List comments = new ArrayList<>(); universe@124: try (var result = listComments.executeQuery()) { universe@124: while (result.next()) { universe@124: final var comment = new IssueComment(result.getInt("commentid"), issue); universe@124: comment.setCreated(result.getTimestamp("created")); universe@124: comment.setUpdated(result.getTimestamp("updated")); universe@124: comment.setUpdateCount(result.getInt("updatecount")); universe@124: comment.setComment(result.getString("comment")); universe@128: comment.setAuthor(PGUserDao.mapColumns(result)); universe@124: comments.add(comment); universe@124: } universe@124: } universe@124: return comments; universe@124: } universe@124: universe@124: @Override universe@124: public void saveComment(IssueComment comment) throws SQLException { universe@124: Objects.requireNonNull(comment.getComment()); universe@124: Objects.requireNonNull(comment.getIssue()); universe@124: if (comment.getId() >= 0) { universe@124: updateComment.setString(1, comment.getComment()); universe@124: updateComment.setInt(2, comment.getId()); universe@124: updateComment.execute(); universe@124: } else { universe@124: insertComment.setInt(1, comment.getIssue().getId()); universe@124: insertComment.setString(2, comment.getComment()); universe@124: setForeignKeyOrNull(insertComment, 3, comment.getAuthor(), User::getId); universe@124: insertComment.execute(); universe@124: } universe@124: } universe@38: }