changes the way how to deal with child entities + adds component lead

Thu, 15 Oct 2020 13:31:52 +0200

author
Mike Becker <universe@uap-core.de>
date
Thu, 15 Oct 2020 13:31:52 +0200
changeset 128
947d0f6a6a83
parent 127
6105ee2cceaf
child 129
a09d5c59351a

changes the way how to deal with child entities + adds component lead

setup/postgres/psql_create_tables.sql file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/ChildEntityDao.java 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/DataAccessObjects.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/GenericDao.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/IssueDao.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/RootEntityDao.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/UserDao.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/PGDataAccessObjects.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/PGUserDao.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/Version.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/version-form.jsp file | annotate | diff | comparison | revisions
--- a/setup/postgres/psql_create_tables.sql	Thu Oct 15 12:27:05 2020 +0200
+++ b/setup/postgres/psql_create_tables.sql	Thu Oct 15 13:31:52 2020 +0200
@@ -40,7 +40,8 @@
     name            varchar(20)     not null,
     color           char(6)         not null default '000000',
     ordinal         integer         not null default 0,
-    description     text
+    description     text,
+    lead            integer         references lpit_user(userid)
 );
 
 create type issue_status as enum (
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/de/uapcore/lightpit/dao/ChildEntityDao.java	Thu Oct 15 13:31:52 2020 +0200
@@ -0,0 +1,116 @@
+/*
+ * 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 java.sql.SQLException;
+import java.util.List;
+
+public interface ChildEntityDao<T, P> {
+
+    /**
+     * Lists all entities being a child of the specified parent.
+     * @param parent the parent
+     * @return the list of child instances
+     * @throws SQLException on any kind of SQL errors
+     */
+    List<T> list(P parent) throws SQLException;
+
+    /**
+     * Finds an entity by its integer ID.
+     * It is not guaranteed that referenced entities are automatically joined.
+     *
+     * @param id the id
+     * @return the enity or null if there is no such entity
+     * @throws SQLException on any kind of SQL errors
+     */
+    T find(int id) throws SQLException;
+
+    /**
+     * Inserts an instance into database.
+     * It is not guaranteed that generated fields will be updated in the instance.
+     *
+     * @param instance the instance to insert
+     * @param parent a reference to the parent
+     * @throws SQLException on any kind of SQL errors
+     */
+    void save(T instance, P parent) throws SQLException;
+
+    /**
+     * Updates an instance in the database.
+     *
+     * @param instance the instance to insert
+     * @return true if an instance has been updated, false if no instance with the specified ID was found
+     * @throws SQLException on any kind of SQL errors
+     */
+    boolean update(T instance) throws SQLException;
+
+    /**
+     * Updates an instance in the database changing the parent.
+     * This operation is not supported by default.
+     *
+     * @param instance the instance to insert
+     * @param newParent a reference to the new parent
+     * @return true if an instance has been updated, false if no instance with the specified ID was found
+     * @throws SQLException on any kind of SQL errors
+     * @see #isChangingParentSupported()
+     */
+    default boolean update(T instance, P newParent) throws SQLException {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Returns true if changing the parent is supported by this DAO.
+     * This method must return true, if {@link #update(Object, Object)} is implemented.
+     * @return true, if changing the parent is supported
+     */
+    default boolean isChangingParentSupported() {
+        return false;
+    }
+
+    /**
+     * Inserts or updates an instance in the database.
+     * Tries an update first and if that fails, performs a save.
+     * If changing a parent is not supported by this DAO,
+     * specifying an alternate parent for an existing instance has no effect.
+     *
+     * @param instance the instance to insert or update
+     * @param parent a reference to the parent
+     * @throws SQLException on any kind of SQL errors
+     * @see #update(Object)
+     * @see #update(Object, Object)
+     * @see #save(Object, Object)
+     */
+    default void saveOrUpdate(T instance, P parent) throws SQLException {
+        if (isChangingParentSupported()) {
+            if (!update(instance, parent)) save(instance, parent);
+        } else {
+            if (!update(instance)) save(instance, parent);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/de/uapcore/lightpit/dao/ComponentDao.java	Thu Oct 15 13:31:52 2020 +0200
@@ -0,0 +1,36 @@
+/*
+ * 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.Component;
+import de.uapcore.lightpit.entities.Project;
+
+public interface ComponentDao extends ChildEntityDao<Component, Project> {
+
+}
--- a/src/main/java/de/uapcore/lightpit/dao/DataAccessObjects.java	Thu Oct 15 12:27:05 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/dao/DataAccessObjects.java	Thu Oct 15 13:31:52 2020 +0200
@@ -35,5 +35,7 @@
 
     VersionDao getVersionDao();
 
+    ComponentDao getComponentDao();
+
     IssueDao getIssueDao();
 }
--- a/src/main/java/de/uapcore/lightpit/dao/GenericDao.java	Thu Oct 15 12:27:05 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,75 +0,0 @@
-/*
- * 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 java.sql.SQLException;
-
-public interface GenericDao<T> {
-
-    /**
-     * Finds an entity by its integer ID.
-     * It is not guaranteed that referenced entities are automatically joined.
-     *
-     * @param id the id
-     * @return the enity or null if there is no such entity
-     * @throws SQLException on any kind of SQL errors
-     */
-    T find(int id) throws SQLException;
-
-    /**
-     * Inserts an instance into database.
-     * It is not guaranteed that generated fields will be updated in the instance.
-     *
-     * @param instance the instance to insert
-     * @throws SQLException on any kind of SQL errors
-     */
-    void save(T instance) throws SQLException;
-
-    /**
-     * Updates an instance in the database.
-     *
-     * @param instance the instance to insert
-     * @return true if an instance has been updated, false if no instance with the specified ID was found
-     * @throws SQLException on any kind of SQL errors
-     */
-    boolean update(T instance) throws SQLException;
-
-    /**
-     * Inserts or updates an instance in the database.
-     * Tries an update first and if that fails, performs a save.
-     *
-     * @param instance the instance to insert or update
-     * @throws SQLException on any kind of SQL errors
-     * @see #update(Object)
-     * @see #save(Object)
-     */
-    default void saveOrUpdate(T instance) throws SQLException {
-        if (!update(instance)) save(instance);
-    }
-}
--- a/src/main/java/de/uapcore/lightpit/dao/IssueDao.java	Thu Oct 15 12:27:05 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/dao/IssueDao.java	Thu Oct 15 13:31:52 2020 +0200
@@ -36,18 +36,7 @@
 import java.sql.SQLException;
 import java.util.List;
 
-public interface IssueDao extends GenericDao<Issue> {
-
-    /**
-     * Lists all issues for the specified project.
-     * This is not guaranteed to contain version information.
-     * Use {@link #joinVersionInformation(Issue)} to obtain this information for a specific issue.
-     *
-     * @param project the project
-     * @return a list of issues
-     * @throws SQLException on any kind of SQL error
-     */
-    List<Issue> list(Project project) throws SQLException;
+public interface IssueDao extends ChildEntityDao<Issue, Project> {
 
     /**
      * Lists all issues that are somehow related to the specified version.
@@ -82,11 +71,12 @@
      * Implementations of this DAO must guarantee that the generated ID is stored in the instance.
      *
      * @param instance the instance to insert
+     * @param project the parent project
      * @throws SQLException on any kind of SQL error
      * @see Issue#setId(int)
      */
     @Override
-    void save(Issue instance) throws SQLException;
+    void save(Issue instance, Project project) throws SQLException;
 
     /**
      * Retrieves the affected, scheduled and resolved versions for the specified issue.
--- a/src/main/java/de/uapcore/lightpit/dao/ProjectDao.java	Thu Oct 15 12:27:05 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/dao/ProjectDao.java	Thu Oct 15 13:31:52 2020 +0200
@@ -32,10 +32,7 @@
 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;
-
+public interface ProjectDao extends RootEntityDao<Project> {
     IssueSummary getIssueSummary(Project project) throws SQLException;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/de/uapcore/lightpit/dao/RootEntityDao.java	Thu Oct 15 13:31:52 2020 +0200
@@ -0,0 +1,83 @@
+/*
+ * 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 java.sql.SQLException;
+import java.util.List;
+
+public interface RootEntityDao<T> {
+
+    /**
+     * Lists all entities.
+     * @return a list of all entities
+     * @throws SQLException on any kind of SQL errors
+     */
+    List<T> list() throws SQLException;
+
+    /**
+     * Finds an entity by its integer ID.
+     * It is not guaranteed that referenced entities are automatically joined.
+     *
+     * @param id the id
+     * @return the enity or null if there is no such entity
+     * @throws SQLException on any kind of SQL errors
+     */
+    T find(int id) throws SQLException;
+
+    /**
+     * Inserts an instance into database.
+     * It is not guaranteed that generated fields will be updated in the instance.
+     *
+     * @param instance the instance to insert
+     * @throws SQLException on any kind of SQL errors
+     */
+    void save(T instance) throws SQLException;
+
+    /**
+     * Updates an instance in the database.
+     *
+     * @param instance the instance to insert
+     * @return true if an instance has been updated, false if no instance with the specified ID was found
+     * @throws SQLException on any kind of SQL errors
+     */
+    boolean update(T instance) throws SQLException;
+
+    /**
+     * Inserts or updates an instance in the database.
+     * Tries an update first and if that fails, performs a save.
+     *
+     * @param instance the instance to insert or update
+     * @throws SQLException on any kind of SQL errors
+     * @see #update(Object)
+     * @see #save(Object)
+     */
+    default void saveOrUpdate(T instance) throws SQLException {
+        if (!update(instance)) save(instance);
+    }
+}
--- a/src/main/java/de/uapcore/lightpit/dao/UserDao.java	Thu Oct 15 12:27:05 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/dao/UserDao.java	Thu Oct 15 13:31:52 2020 +0200
@@ -31,12 +31,9 @@
 import de.uapcore.lightpit.entities.User;
 
 import java.sql.SQLException;
-import java.util.List;
 import java.util.Optional;
 
-public interface UserDao extends GenericDao<User> {
-
-    List<User> list() throws SQLException;
+public interface UserDao extends RootEntityDao<User> {
 
     /**
      * Tries to find a user by their username.
--- a/src/main/java/de/uapcore/lightpit/dao/VersionDao.java	Thu Oct 15 12:27:05 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/dao/VersionDao.java	Thu Oct 15 13:31:52 2020 +0200
@@ -31,17 +31,5 @@
 import de.uapcore.lightpit.entities.Project;
 import de.uapcore.lightpit.entities.Version;
 
-import java.sql.SQLException;
-import java.util.List;
-
-public interface VersionDao extends GenericDao<Version> {
-
-    /**
-     * Lists all versions for the specified project.
-     *
-     * @param project the project
-     * @return a list of versions
-     * @throws SQLException on any kind of SQL error
-     */
-    List<Version> list(Project project) throws SQLException;
+public interface VersionDao extends ChildEntityDao<Version, Project> {
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGComponentDao.java	Thu Oct 15 13:31:52 2020 +0200
@@ -0,0 +1,139 @@
+/*
+ * 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.ComponentDao;
+import de.uapcore.lightpit.dao.Functions;
+import de.uapcore.lightpit.entities.Component;
+import de.uapcore.lightpit.entities.Project;
+import de.uapcore.lightpit.entities.User;
+import de.uapcore.lightpit.types.WebColor;
+
+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;
+
+public final class PGComponentDao implements ComponentDao {
+
+    private final PreparedStatement insert, update, list, find;
+
+    public PGComponentDao(Connection connection) throws SQLException {
+        list = connection.prepareStatement(
+                "select id, name, color, ordinal, description, " +
+                        "userid, username, givenname, lastname, mail " +
+                        "from lpit_component " +
+                        "left join lpit_user on lead = userid " +
+                        "where project = ? " +
+                        "order by ordinal desc, lower(name) desc");
+
+        find = connection.prepareStatement(
+                "select id, name, color, ordinal, description, " +
+                        "userid, username, givenname, lastname, mail " +
+                        "from lpit_component " +
+                        "left join lpit_user on lead = userid " +
+                        "where id = ? ");
+
+        insert = connection.prepareStatement(
+                "insert into lpit_component (project, name, color, ordinal, description, lead) values (?, ?, ?, ?, ?, ?)"
+        );
+
+        update = connection.prepareStatement(
+                "update lpit_component set name = ?, color = ?, ordinal = ?, description = ?, lead = ? where id = ?"
+        );
+    }
+
+    private static Component mapColumns(ResultSet result) throws SQLException {
+        final var component = new Component(result.getInt("id"));
+        component.setName(result.getString("name"));
+        try {
+            component.setColor(new WebColor(result.getString("color")));
+        } catch (IllegalArgumentException ex) {
+            // if someone tempered with the database we default the color to black
+            component.setColor(new WebColor("000000"));
+        }
+        component.setOrdinal(result.getInt("ordinal"));
+        component.setDescription(result.getString("description"));
+        component.setLead(PGUserDao.mapColumns(result));
+        return component;
+    }
+
+    @Override
+    public void save(Component instance, Project project) throws SQLException {
+        Objects.requireNonNull(instance.getName());
+        insert.setInt(1, project.getId());
+        insert.setString(2, instance.getName());
+        insert.setString(3, instance.getColor().getHex());
+        insert.setInt(4, instance.getOrdinal());
+        Functions.setStringOrNull(insert, 5, instance.getDescription());
+        Functions.setForeignKeyOrNull(insert, 6, instance.getLead(), User::getId);
+        insert.executeUpdate();
+    }
+
+    @Override
+    public boolean update(Component instance) throws SQLException {
+        if (instance.getId() < 0) return false;
+        Objects.requireNonNull(instance.getName());
+        Objects.requireNonNull(instance.getColor());
+        update.setString(1, instance.getName());
+        update.setString(2, instance.getColor().getHex());
+        update.setInt(3, instance.getOrdinal());
+        Functions.setStringOrNull(update, 4, instance.getDescription());
+        Functions.setForeignKeyOrNull(update, 5, instance.getLead(), User::getId);
+        update.setInt(6, instance.getId());
+        return update.executeUpdate() > 0;
+    }
+
+    @Override
+    public List<Component> list(Project project) throws SQLException {
+        list.setInt(1, project.getId());
+        List<Component> components = new ArrayList<>();
+        try (var result = list.executeQuery()) {
+            while (result.next()) {
+                components.add(mapColumns(result));
+            }
+        }
+        return components;
+    }
+
+    @Override
+    public Component 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/PGDataAccessObjects.java	Thu Oct 15 12:27:05 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGDataAccessObjects.java	Thu Oct 15 13:31:52 2020 +0200
@@ -38,12 +38,14 @@
     private final UserDao userDao;
     private final ProjectDao projectDao;
     private final VersionDao versionDao;
+    private final ComponentDao componentDao;
     private final IssueDao issueDao;
 
     public PGDataAccessObjects(Connection connection) throws SQLException {
         userDao = new PGUserDao(connection);
         projectDao = new PGProjectDao(connection);
         versionDao = new PGVersionDao(connection);
+        componentDao = new PGComponentDao(connection);
         issueDao = new PGIssueDao(connection);
     }
 
@@ -58,6 +60,11 @@
     }
 
     @Override
+    public ComponentDao getComponentDao() {
+        return componentDao;
+    }
+
+    @Override
     public VersionDao getVersionDao() {
         return versionDao;
     }
--- a/src/main/java/de/uapcore/lightpit/dao/postgres/PGIssueDao.java	Thu Oct 15 12:27:05 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGIssueDao.java	Thu Oct 15 13:31:52 2020 +0200
@@ -120,20 +120,6 @@
         );
     }
 
-    private User obtainUser(ResultSet result) throws SQLException {
-        final int id = result.getInt("userid");
-        if (id != 0) {
-            final var user = new User(id);
-            user.setUsername(result.getString("username"));
-            user.setGivenname(result.getString("givenname"));
-            user.setLastname(result.getString("lastname"));
-            user.setMail(result.getString("mail"));
-            return user;
-        } else {
-            return null;
-        }
-    }
-
     private Issue mapColumns(ResultSet result) throws SQLException {
         final var project = new Project(result.getInt("project"));
         project.setName(result.getString("projectname"));
@@ -143,7 +129,7 @@
         issue.setCategory(IssueCategory.valueOf(result.getString("category")));
         issue.setSubject(result.getString("subject"));
         issue.setDescription(result.getString("description"));
-        issue.setAssignee(obtainUser(result));
+        issue.setAssignee(PGUserDao.mapColumns(result));
         issue.setCreated(result.getTimestamp("created"));
         issue.setUpdated(result.getTimestamp("updated"));
         issue.setEta(result.getDate("eta"));
@@ -176,9 +162,9 @@
     }
 
     @Override
-    public void save(Issue instance) throws SQLException {
+    public void save(Issue instance, Project project) throws SQLException {
         Objects.requireNonNull(instance.getSubject());
-        Objects.requireNonNull(instance.getProject());
+        instance.setProject(project);
         insert.setInt(1, instance.getProject().getId());
         insert.setString(2, instance.getStatus().name());
         insert.setString(3, instance.getCategory().name());
@@ -275,7 +261,7 @@
                 comment.setUpdated(result.getTimestamp("updated"));
                 comment.setUpdateCount(result.getInt("updatecount"));
                 comment.setComment(result.getString("comment"));
-                comment.setAuthor(obtainUser(result));
+                comment.setAuthor(PGUserDao.mapColumns(result));
                 comments.add(comment);
             }
         }
--- a/src/main/java/de/uapcore/lightpit/dao/postgres/PGProjectDao.java	Thu Oct 15 12:27:05 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGProjectDao.java	Thu Oct 15 13:31:52 2020 +0200
@@ -85,16 +85,7 @@
         proj.setName(result.getString("name"));
         proj.setDescription(result.getString("description"));
         proj.setRepoUrl(result.getString("repourl"));
-
-        final int id = result.getInt("userid");
-        if (id != 0) {
-            final var user = new User(id);
-            user.setUsername(result.getString("username"));
-            user.setGivenname(result.getString("givenname"));
-            user.setLastname(result.getString("lastname"));
-            user.setMail(result.getString("mail"));
-            proj.setOwner(user);
-        }
+        proj.setOwner(PGUserDao.mapColumns(result));
 
         return proj;
     }
--- a/src/main/java/de/uapcore/lightpit/dao/postgres/PGUserDao.java	Thu Oct 15 12:27:05 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGUserDao.java	Thu Oct 15 13:31:52 2020 +0200
@@ -63,7 +63,7 @@
         update = connection.prepareStatement("update lpit_user set lastname = ?, givenname = ?, mail = ? where userid = ?");
     }
 
-    private User mapColumns(ResultSet result) throws SQLException {
+    static User mapColumns(ResultSet result) throws SQLException {
         final int id = result.getInt("userid");
         if (id == 0) return null;
         final var user = new User(id);
--- a/src/main/java/de/uapcore/lightpit/dao/postgres/PGVersionDao.java	Thu Oct 15 12:27:05 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGVersionDao.java	Thu Oct 15 13:31:52 2020 +0200
@@ -47,16 +47,14 @@
 
     public PGVersionDao(Connection connection) throws SQLException {
         list = connection.prepareStatement(
-                "select versionid, project, p.name as projectname, v.name, ordinal, status " +
-                        "from lpit_version v " +
-                        "join lpit_project p on v.project = p.projectid " +
+                "select versionid, project, name, ordinal, status " +
+                        "from lpit_version " +
                         "where project = ? " +
-                        "order by ordinal desc, lower(v.name) desc");
+                        "order by ordinal desc, lower(name) desc");
 
         find = connection.prepareStatement(
-                "select versionid, project, p.name as projectname, v.name, ordinal, status " +
-                        "from lpit_version  v " +
-                        "join lpit_project p on v.project = p.projectid " +
+                "select versionid, project, name, ordinal, status " +
+                        "from lpit_version  " +
                         "where versionid = ?");
 
         insert = connection.prepareStatement(
@@ -68,10 +66,7 @@
     }
 
     private Version mapColumns(ResultSet result) throws SQLException {
-        final var project = new Project(result.getInt("project"));
-        project.setName(result.getString("projectname"));
         final var version = new Version(result.getInt("versionid"));
-        version.setProject(project);
         version.setName(result.getString("name"));
         version.setOrdinal(result.getInt("ordinal"));
         version.setStatus(VersionStatus.valueOf(result.getString("status")));
@@ -79,10 +74,9 @@
     }
 
     @Override
-    public void save(Version instance) throws SQLException {
+    public void save(Version instance, Project project) throws SQLException {
         Objects.requireNonNull(instance.getName());
-        Objects.requireNonNull(instance.getProject());
-        insert.setInt(1, instance.getProject().getId());
+        insert.setInt(1, project.getId());
         insert.setString(2, instance.getName());
         insert.setInt(3, instance.getOrdinal());
         insert.setString(4, instance.getStatus().name());
@@ -106,9 +100,7 @@
         List<Version> versions = new ArrayList<>();
         try (var result = list.executeQuery()) {
             while (result.next()) {
-                final var v = mapColumns(result);
-                v.setProject(project);
-                versions.add(v);
+                versions.add(mapColumns(result));
             }
         }
         return versions;
--- a/src/main/java/de/uapcore/lightpit/entities/Version.java	Thu Oct 15 12:27:05 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/entities/Version.java	Thu Oct 15 13:31:52 2020 +0200
@@ -33,7 +33,6 @@
 public final class Version implements Comparable<Version> {
 
     private final int id;
-    private Project project;
     private String name;
     /**
      * If we do not want versions to be ordered lexicographically we may specify an order.
@@ -49,14 +48,6 @@
         return id;
     }
 
-    public void setProject(Project project) {
-        this.project = project;
-    }
-
-    public Project getProject() {
-        return project;
-    }
-
     public String getName() {
         return name;
     }
--- a/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java	Thu Oct 15 12:27:05 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java	Thu Oct 15 13:31:52 2020 +0200
@@ -238,13 +238,7 @@
             return ResponseType.NONE;
         }
 
-        if (viewModel.getVersionFilter() == null) {
-            final var version = new Version(-1);
-            version.setProject(viewModel.getProjectInfo().getProject());
-            viewModel.setVersion(version);
-        } else {
-            viewModel.setVersion(viewModel.getVersionFilter());
-        }
+        viewModel.setVersion(Optional.ofNullable(viewModel.getVersionFilter()).orElse(new Version(-1)));
 
         return forwardView(req, viewModel, "version-form");
     }
@@ -254,15 +248,14 @@
 
         var version = new Version(-1);
         try {
+            final var project = new Project(getParameter(req, Integer.class, "pid").orElseThrow());
             version = new Version(getParameter(req, Integer.class, "id").orElseThrow());
-            version.setProject(new Project(getParameter(req, Integer.class, "pid").orElseThrow()));
             version.setName(getParameter(req, String.class, "name").orElseThrow());
             getParameter(req, Integer.class, "ordinal").ifPresent(version::setOrdinal);
             version.setStatus(VersionStatus.valueOf(getParameter(req, String.class, "status").orElseThrow()));
-            dao.getVersionDao().saveOrUpdate(version);
+            dao.getVersionDao().saveOrUpdate(version, project);
 
-            // specifying the pid parameter will purposely reset the session selected version!
-            setRedirectLocation(req, "./projects/versions?pid=" + version.getProject().getId());
+            setRedirectLocation(req, "./projects/versions?pid=" + project.getId());
             setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
         } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) {
             LOG.warn("Form validation failure: {}", ex.getMessage());
@@ -332,7 +325,7 @@
                             stream.map(Version::new).collect(Collectors.toList())
                     ).ifPresent(issue::setResolvedVersions);
 
-            dao.getIssueDao().saveOrUpdate(issue);
+            dao.getIssueDao().saveOrUpdate(issue, issue.getProject());
 
             // specifying the issue parameter keeps the edited issue as menu item
             setRedirectLocation(req, "./projects/view?pid=" + issue.getProject().getId());
--- a/src/main/webapp/WEB-INF/jsp/version-form.jsp	Thu Oct 15 12:27:05 2020 +0200
+++ b/src/main/webapp/WEB-INF/jsp/version-form.jsp	Thu Oct 15 13:31:52 2020 +0200
@@ -30,6 +30,7 @@
 
 <jsp:useBean id="viewmodel" type="de.uapcore.lightpit.viewmodel.VersionEditView" scope="request" />
 <c:set var="version" scope="page" value="${viewmodel.version}"/>
+<c:set var="project" scope="page" value="${viewmodel.projectInfo.project}"/>
 
 <form action="./projects/versions/commit" method="post">
     <table class="formtable" style="width: 35ch">
@@ -41,8 +42,8 @@
         <tr>
             <th><fmt:message key="version.project"/></th>
             <td>
-                <c:out value="${version.project.name}" />
-                <input type="hidden" name="pid" value="${version.project.id}" />
+                <c:out value="${project.name}" />
+                <input type="hidden" name="pid" value="${project.id}" />
             </td>
         </tr>
         <tr>
@@ -72,7 +73,7 @@
         <tr>
             <td colspan="2">
                 <input type="hidden" name="id" value="${version.id}"/>
-                <a href="./projects/versions?pid=${version.project.id}" class="button">
+                <a href="./projects/versions?pid=${project.id}" class="button">
                     <fmt:message bundle="${lightpit_bundle}" key="button.cancel"/>
                 </a>
                 <button type="submit"><fmt:message bundle="${lightpit_bundle}" key="button.okay"/></button>

mercurial